npm package: @femitubosun/hono-router
Description: Type-safe routing layer for Hono with Zod validation and OpenAPI documentation support
Author: femitubosun
Latest version: 1.0.4

# @femitubosun/hono-router

Type-safe routing layer for [Hono](https://hono.dev) with [Zod](https://zod.dev) validation and automatic [OpenAPI](https://www.openapis.org/) documentation.

Built on top of `@hono/zod-openapi`, this library provides a fluent builder pattern for defining routes with full TypeScript inference.

## Features

- **Type-safe routing** - Full TypeScript inference for request/response bodies, params, query, and headers
- **Zod validation** - Runtime validation with Zod schemas
- **Automatic OpenAPI docs** - Generate OpenAPI specifications from your route definitions
- **Builder pattern API** - Fluent interface for route configuration
- **Error handling** - Typed HTTP errors with middleware support
- **Cloudflare Workers ready** - Works in any Hono environment

## Installation

```bash
npm install @femitubosun/hono-router
```

## Quick Start

```typescript
import { CreateRouter, Route, RouterZod as z } from "@femitubosun/hono-router";

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.email(),
});

const app = CreateRouter({
  openapiInfo: { title: "My API", version: "1.0.0" },
});

// Define route with builder pattern
const getUserRoute = Route.get("/users/:id")
  .params(z.object({ id: z.string() }))
  .jsonResponse(200, "User found", UserSchema);

// Add route with handler
app.addRoute(getUserRoute, async (c) => {
  const { id } = c.req.valid("param");
  return c.json({ id, name: "John", email: "john@example.com" });
});

export default app;
```

## Route Builder

Routes are defined using a fluent builder pattern:

```typescript
const route = Route.get("/users/:id")
  .params(z.object({ id: z.string() }))           // Path parameters
  .query(z.object({ expand: z.boolean() }))       // Query parameters
  .headers(z.object({ "x-api-key": z.string() })) // Headers
  .jsonBody(CreateUserSchema)                     // Request body
  .jsonResponse(200, "Success", UserSchema)       // JSON response
  .jsonResponse(404, "Not found", ErrorSchema)
  .middleware(authMiddleware)                     // Middleware
  .build();
```

### Available Methods

| Method | Description |
|--------|-------------|
| `path(path)` | Set route path |
| `method(method)` | Set HTTP method |
| `summary(text)` | OpenAPI summary |
| `description(text)` | OpenAPI description |
| `tags(tags)` | OpenAPI tags |
| `params(schema)` | Path parameters validation |
| `query(schema)` | Query parameters validation |
| `headers(schema)` | Headers validation |
| `cookies(schema)` | Cookies validation |
| `jsonBody(schema)` | JSON request body |
| `formBody(schema)` | Form request body |
| `response(status, desc, content)` | Custom response |
| `jsonResponse(status, desc, schema)` | JSON response |
| `middleware(handler)` | Add middleware |
| `hide(boolean)` | Hide from OpenAPI docs |
| `build()` | Build the route config |

## Router

Create a router with optional configuration:

```typescript
const app = CreateRouter({
  hook: customValidationHook,     // Custom validation hook
  strict: true,                   // Strict routing
  openapiInfo: {                  // OpenAPI metadata
    title: "API",
    version: "1.0.0",
    description: "My API",
  },
  enableErrorHandler: true,       // Enable built-in error handling
});
```

### Adding Routes

```typescript
// Using addRoute with RouteBuilder
app.addRoute(routeBuilder, handler);

// Or use openapi directly
app.openapi(routeConfig, handler);
```

## Error Handling

Built-in typed errors with automatic error responses:

```typescript
import { NotFoundError, BadRequestError } from "@femitubosun/hono-router";

app.addRoute(getUserRoute, async (c) => {
  const { id } = c.req.valid("param");
  const user = await getUser(id);
  
  if (!user) {
    throw new NotFoundError("User not found");
  }
  
  return c.json(user);
});
```

### Available Errors

- `BadRequestError` (400)
- `UnauthorizedError` (401)
- `ForbiddenError` (403)
- `NotFoundError` (404)
- `ValidationError` (422)
- `InternalServerError` (500)

### Custom Errors

```typescript
import { HttpError } from "@femitubosun/hono-router";

throw new HttpError(418, "I'm a teapot", "TEAPOT_ERROR");
```

## Middleware

```typescript
import { requestLogger, cors } from "@femitubosun/hono-router";

// Apply globally
app.use(requestLogger());
app.use(cors({ origin: "*" }));

// Apply to specific route
const route = Route.get("/protected")
  .middleware(authMiddleware)
  .jsonResponse(200, "OK", z.object({}));
```

## Module Pattern

Organize routes into modules:

```typescript
import { Module, Route, RouterZod as z } from "@femitubosun/hono-router";

const UserRoutes = {
  list: Route.get("/users")
    .jsonResponse(200, "List", z.array(UserSchema))
    .build(),
  
  getById: Route.get("/users/:id")
    .params(z.object({ id: z.string() }))
    .jsonResponse(200, "User", UserSchema)
    .build(),
};

const UserHandlers = {
  list: async (c) => c.json([{ id: "1", name: "John" }]),
  getById: async (c) => {
    const { id } = c.req.valid("param");
    return c.json({ id, name: "John" });
  },
};

// Register all routes
Module.registerRoutes(app, UserRoutes, UserHandlers);

// Or create a module instance
const userModule = Module.create({
  routes: UserRoutes,
  handlers: UserHandlers,
});

userModule.register(app);
```

## Environment Bindings

Full support for Cloudflare Workers bindings:

```typescript
import { CreateRouter, type RouterBindings } from "@femitubosun/hono-router";

interface CFBindings extends RouterBindings {
  Variables: { userId: string };
  Bindings: {
    USER_KV: KVNamespace;
    AUTH_SECRET: string;
  };
}

const app = CreateRouter<CFBindings>();

app.addRoute(getUserRoute, async (c) => {
  const { id } = c.req.valid("param");
  const user = await c.env.USER_KV.get(id);
  // ...
});
```

## OpenAPI Documentation

When you provide `openapiInfo`, a `/doc` endpoint is automatically created:

```typescript
const app = CreateRouter({
  openapiInfo: {
    title: "My API",
    version: "1.0.0",
    description: "API documentation",
  },
});

// GET /doc returns OpenAPI 3.0 spec
```

## License

MIT