Home

API Hono Overview

Hono structure in create-light-stack.

Project Structure

Code is organised inside src for clear separation of concerns.

src/
├── controllers/    # Request handlers (business logic)
├── middlewares/     # Interceptors (Auth, Errors)
├── routes/         # Route definitions and method mapping
├── utils/          # Shared utilities (AppError)
└── index.ts        # Entry point and server configuration

Entry Point

The server initializes in src/index.ts.
It configures global middleware, sets up CORS, registers routes, attaches the error handler, and starts the HTTP server using @hono/node-server.

Key Configuration

OptionValue
Port3001 (or process.env.PORT)
CORSCredentials enabled, origin restricted to process.env.APP_ORIGIN
Route PrefixAll routes mounted under /
// src/index.ts
import "dotenv/config";
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { errorMiddleware } from "./middlewares/error.middleware.js";
import routes from "./routes/example.router.js";

const app = new Hono();

/* ---------- global middleware ---------- */
app.use(
  "/*",
  cors({
    origin: process.env.APP_ORIGIN || "http://localhost:3000",
    credentials: true,
  })
);
app.use(logger());
app.onError(errorMiddleware);

/* ---------- routes ---------- */
app.get("/health", (c) =>
  c.json({ status: "ok", timestamp: new Date().toISOString() })
);
app.route("/", routes);

/* ---------- start server ---------- */
serve(
  { fetch: app.fetch, port: Number(process.env.PORT || 3001) },
  (info) => console.log(`Server running on http://localhost:${info.port}`)
);

Routing & Controllers

Routes are defined in src/routes and delegate to handler functions in src/controllers.

Defining Routes

Use a standard Hono instance:

import { Hono } from "hono";
import { exampleController } from "../controllers/example.controller.js";

const router = new Hono();

router.get("/", exampleController);

export default router;

Controllers

Controllers are async functions that contain request-level logic.

import type { Context } from "hono";
import { AppError } from "../utils/AppError.js";

export const exampleController = async (c: Context) => {
  return c.json({
    status: "success",
    message: "Hono API is working!",
    timestamp: new Date().toISOString(),
  });
};

Validation

The database package exposes Zod validation schemas derived from your database models. These schemas ensure type-safe and validated inputs in your Hono controllers.

// drizzle
// src/controllers/example.controller.ts 
import { db, example, insertExampleSchema } from "@light/database";

export const createExample = async (c: Context) => {
  try {
    const body = await c.req.json();
    const validData = insertExampleSchema.parse(body); 

    const [newItem] = await db.insert(example)
      .values(validData) 
      .returning();

    return c.json({ status: "success", data: newItem }, 201);
  } catch (err) {
    throw err;
  }
};
// mongoose
// src/controllers/example.controller.ts 
import { example, insertExampleSchema } from "@light/database";

export const createExample = async (c: Context) => {
  try {
    const body = await c.req.json();
    const validData = insertExampleSchema.parse(body);

    const newItem = await example.create(validData);
    return c.json({ status: "success", data: newItem }, 201);
  } catch (err) {
    throw err;
  }
};

Error Handling

Hono centralizes all API error responses using a single global errormiddleware(src/middlewares/error.middleware.ts). This ensures consistent, predictable, and type-safe error shapes across the API.

The Error Middleware

Error KindStatusResponse Body
ZodError400{ status: "fail", errors: zod.flatten() }
AppErrorcustom{ status: "error", message }
Unknown500{ status: "error", message: "Internal server error" }

Using AppError

Any route, controller, or utility can throw an AppError and the middleware will reply with the supplied status code.

import { AppError } from '../utils/AppError.js';

// 404 Not Found
throw new AppError(404, "Resource not found");

// 403 Forbidden
throw new AppError(403,'You do not have permission');

On this page