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.
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:
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. 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
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:
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:
-
Verify the backend is running
# The backend should be accessible at the configured URL
curl http://localhost:3000
-
Check BACKEND_URL configuration
Ensure your
.env file has the correct URL:
BACKEND_URL=http://localhost:3000
-
Verify credentials
Authentication failures (401 errors) indicate credential mismatch:
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
- Clone and set up the backend repository
- Configure backend environment variables
- Start the backend server (typically on port 3000)
- Verify the backend is accessible
Starting the Frontend
- Configure frontend environment variables (see Environment Variables)
- Install dependencies:
pnpm install
- Start the dev server:
pnpm run dev
- Access the frontend at
http://localhost:3001