Hono Integration

Register routes and handle errors with Hono framework.

Installation

bun add hono @hono/node-server

Import the Hono integration:

import {
  registerHonoRoutes,
  onionErrorHandler,
} from '@cosmneo/onion-lasagna/http/frameworks/hono';

Quick Start

import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { registerHonoRoutes, onionErrorHandler } from '@cosmneo/onion-lasagna/http/frameworks/hono';
import { bootstrapProjectManagement } from './bootstrap';

// Bootstrap your bounded context
const { routes } = bootstrapProjectManagement();

const app = new Hono();

// Apply error handler
app.onError((err, c) => onionErrorHandler(err, c));

// Register routes
registerHonoRoutes(app, routes);

serve({ fetch: app.fetch, port: 3000 });

Registering Routes

The registerHonoRoutes function registers routes created with serverRoutes():

import { serverRoutes } from '@cosmneo/onion-lasagna/http/server';
import { registerHonoRoutes } from '@cosmneo/onion-lasagna/http/frameworks/hono';

// Create routes using the builder
const routes = serverRoutes(userRouter)
  .handle('users.get', {
    requestMapper: (req) => ({ userId: req.pathParams.userId }),
    useCase: getUserQuery,
    responseMapper: (output) => ({ status: 200 as const, body: output }),
  })
  .build();

// Register with Hono
registerHonoRoutes(app, routes);

Options

interface RegisterHonoRoutesOptions {
  prefix?: string;                    // Route prefix (e.g., '/api/v1')
  middlewares?: HonoMiddleware[];     // Middlewares applied to all routes
  contextExtractor?: HonoContextExtractor;  // Extract auth context
}

registerHonoRoutes(app, routes, {
  prefix: '/api',
  middlewares: [authMiddleware],
  contextExtractor: (c) => ({
    userId: c.get('jwtPayload')?.sub,
  }),
});

Context Extraction (Protected Routes)

For authenticated routes, provide a contextExtractor to pass auth data to handlers:

import { jwt } from 'hono/jwt';

// JWT middleware
const authMiddleware = jwt({
  secret: process.env.JWT_SECRET,
});

// Define context type
interface AuthContext {
  userId: string;
}

// Register with context extraction
registerHonoRoutes(app, routes, {
  middlewares: [authMiddleware],
  contextExtractor: (c): AuthContext => ({
    userId: c.get('jwtPayload')?.sub ?? '',
  }),
});

The context is passed to your handler's requestMapper:

.handle('projects.create', {
  requestMapper: (req, ctx) => ({
    name: req.body.name,
    createdBy: ctx.userId,  // From contextExtractor
  }),
  useCase: createProjectUseCase,
  responseMapper: (output) => ({
    status: 201 as const,
    body: { projectId: output.projectId },
  }),
})

Error Handler

The onionErrorHandler maps domain errors to HTTP responses:

app.onError((err, c) => onionErrorHandler(err, c));

Error Mapping

Error TypeHTTP StatusBody
ObjectValidationError400{ errorCode, message, details }
InvalidRequestError400{ errorCode, message, details }
UseCaseError400{ errorCode, message }
AccessDeniedError403{ errorCode, message }
NotFoundError404{ errorCode, message }
ConflictError409{ errorCode, message }
UnprocessableError422{ errorCode, message }
DomainError500Masked
InfraError500Masked
Unknown500Masked

Warning:

Masked errors return { "message": "An unexpected error occurred", "errorCode": "INTERNAL_ERROR" }. Domain and infrastructure errors are masked to prevent leaking implementation details.


Custom Error Handling

Log errors before handling:

app.onError((err, c) => {
  console.error('Error:', err);
  return onionErrorHandler(err, c);
});

Complete Example

import 'dotenv/config';
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt';
import { registerHonoRoutes, onionErrorHandler } from '@cosmneo/onion-lasagna/http/frameworks/hono';
import { bootstrapProjectManagement } from './bootstrap';

// Bootstrap
const { routes } = bootstrapProjectManagement();

// Create app
const app = new Hono();

// Middleware
app.use('*', logger());
app.use('*', cors());

// Error handling
app.onError((err, c) => {
  console.error('Error:', err);
  return onionErrorHandler(err, c);
});

// Health check (no auth)
app.get('/health', (c) => c.json({ status: 'ok' }));

// Auth middleware
const authMiddleware = jwt({
  secret: process.env.JWT_SECRET ?? 'dev-secret',
});

// API routes with auth
registerHonoRoutes(app, routes, {
  middlewares: [authMiddleware],
  contextExtractor: (c) => ({
    userId: c.get('jwtPayload')?.sub,
  }),
});

// Start server
serve({ fetch: app.fetch, port: 3000 });
console.log('Server running on http://localhost:3000');

Cloudflare Workers

import { Hono } from 'hono';
import { registerHonoRoutes, onionErrorHandler } from '@cosmneo/onion-lasagna/http/frameworks/hono';

const app = new Hono();

app.onError((err, c) => onionErrorHandler(err, c));
registerHonoRoutes(app, routes);

export default app;
# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"