Skip to main content
The Bun Hono Frontend Next.js application connects to the Bun Hono backend using server actions and the native Fetch API.

Architecture Overview

The frontend uses Next.js Server Actions to communicate with the backend API. This architecture provides:
  • Server-side execution - API calls happen on the server, keeping credentials secure
  • Type safety - Zod schemas validate data before sending to the backend
  • Automatic revalidation - Cache is updated after mutations using revalidatePath
  • Error handling - Consistent error responses across all actions

Authentication

All requests to the backend use HTTP Basic Authentication. Credentials are configured via environment variables and encoded in Base64 format.

Authentication Headers

Every request includes an Authorization header:
headers: {
  Authorization: `Basic ${Buffer.from(
    `${process.env.USERNAME}:${process.env.PASSWORD}`
  ).toString("base64")}`,
  "Content-Type": "application/json",
}
The credentials are read from environment variables:
  • process.env.USERNAME - Backend username
  • process.env.PASSWORD - Backend password
These credentials must match the username and password configured in your Bun Hono backend. See Environment Variables for setup instructions.

Server Actions Pattern

All backend communication happens through server actions located in src/lib/actions/. Here’s the standard pattern:

Creating Resources

Example from src/lib/actions/message/createMessage.ts:
src/lib/actions/message/createMessage.ts
"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";

interface MessagePrevState {
  success?: boolean;
  error?: string;
}

// Define Zod schema
const messageSchema = z.object({
  content: z.string().min(1, "Content cannot be empty"),
  sendToPhone: z
    .string()
    .regex(/^8801\d{9}$/, "Invalid phone number format. Must be 8801XXXXXXXXX"),
  sendAfter: z
    .number()
    .min(1, "Send after is required and minimum after 1 day"),
});

export default async function createMessage(
  prevState: MessagePrevState,
  formData: FormData
): Promise<MessagePrevState> {
  try {
    // Validate form data
    const parsedData = messageSchema.safeParse({
      content: formData.get("content"),
      sendToPhone: formData.get("sendToPhone"),
      sendAfter: Number(formData.get("sendAfter")),
    });

    if (!parsedData.success) {
      return {
        error: parsedData.error.errors.map((err) => err.message).join(", "),
      };
    }

    const response = await fetch(
      `${process.env.BACKEND_URL}/messages/create-one`,
      {
        method: "POST",
        headers: {
          Authorization: `Basic ${Buffer.from(
            `${process.env.USERNAME}:${process.env.PASSWORD}`
          ).toString("base64")}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(parsedData.data),
      }
    );

    if (!response.ok) {
      return {
        error: `Backend Error: ${response.status} ${response.statusText}`,
      };
    }
    revalidatePath("/");
    return { success: true };
  } catch (error) {
    return {
      error: error instanceof Error ? error.message : "Something went wrong",
    };
  }
}

Deleting Resources

Example from src/lib/actions/message/deleteMessage.ts:
src/lib/actions/message/deleteMessage.ts
"use server"
import { revalidatePath } from "next/cache";

export default async function deleteMessage(
  prevState: MessagePrevState,
  formData: FormData
): Promise<MessagePrevState> {
  try {
    const id = formData.get("id");
    if (!id) {
      return {
        success: false,
        error: "ID is required",
      };
    }
    const response = await fetch(
      `${process.env.BACKEND_URL}/messages/delete-one/${id}`,
      {
        method: "POST",
        headers: {
          Authorization: `Basic ${Buffer.from(
            `${process.env.USERNAME}:${process.env.PASSWORD}`
          ).toString("base64")}`,
        },
      }
    );
    if (!response.ok) {
      return {
        success: false,
        error: `Backend Error: ${response.status} ${response.statusText}`,
      };
    }
    revalidatePath("/");
    return { success: true };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : "Something went wrong",
    };
  }
}

API Endpoints

The application communicates with these backend endpoints:

Messages

`${process.env.BACKEND_URL}/messages/create-one`
// Method: POST
// Body: { content, sendToPhone, sendAfter }

People

`${process.env.BACKEND_URL}/people/create-one`
// Method: POST
// Body: { name, phone }

Error Handling

The application implements comprehensive error handling for backend communication:
1

Validation Errors

Before sending data to the backend, Zod schemas validate the input:
if (!parsedData.success) {
  return {
    error: parsedData.error.errors.map((err) => err.message).join(", "),
  };
}
This catches issues like empty fields or invalid phone numbers before making the API call.
2

HTTP Errors

If the backend returns a non-200 status code:
if (!response.ok) {
  return {
    error: `Backend Error: ${response.status} ${response.statusText}`,
  };
}
Common errors:
  • 401 - Authentication failed (check credentials)
  • 404 - Resource not found
  • 500 - Backend server error
3

Network Errors

Network failures and other exceptions are caught:
catch (error) {
  return {
    error: error instanceof Error ? error.message : "Something went wrong",
  };
}
This handles scenarios like:
  • Backend server is down
  • Network connectivity issues
  • DNS resolution failures

Cache Revalidation

After successful mutations, the application revalidates the Next.js cache:
revalidatePath("/");
This ensures that:
  • The UI automatically reflects the latest data
  • Users see updates without manually refreshing
  • The application maintains consistency with the backend

Troubleshooting

Backend Connection Issues

Make sure your backend server is running before starting the frontend application.
If you encounter connection errors:
  1. Verify the backend is running
    # The backend should be accessible at the configured URL
    curl http://localhost:3000
    
  2. Check BACKEND_URL configuration Ensure your .env file has the correct URL:
    .env
    BACKEND_URL=http://localhost:3000
    
  3. Verify credentials Authentication failures (401 errors) indicate credential mismatch:
    .env
    USERNAME="your-backend-username"
    PASSWORD="your-backend-password"
    

Common Error Messages

Fetch failed: connect ECONNREFUSED 127.0.0.1:3000

→ The backend server is not running
→ Check BACKEND_URL points to the correct host and port

Prerequisites

The backend server must be running before you can use the frontend application.
Refer to the Bun Hono Backend repository for backend setup instructions.

Starting the Backend

  1. Clone and set up the backend repository
  2. Configure backend environment variables
  3. Start the backend server (typically on port 3000)
  4. Verify the backend is accessible

Starting the Frontend

  1. Configure frontend environment variables (see Environment Variables)
  2. Install dependencies: pnpm install
  3. Start the dev server: pnpm run dev
  4. Access the frontend at http://localhost:3001

Build docs developers (and LLMs) love