Controllers, DI, guards, SSE, WebSocket, channels, rate limiting, OpenAPI, structured errors, and trace IDs โ all declarative. Install one package. No boilerplate.
One package. No boilerplate. Focus on business logic.
Install one package, decorate your controllers, mount the routes.
$ bun add hono-forge hono zod
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}import 'reflect-metadata'; import { Hono } from 'hono'; import { Controller, Get, Post, Patch, Delete, Body, Param, Public, RequireAuth, Injectable, HonoRouteBuilder, } from 'hono-forge'; import { z } from 'zod'; const CreateUserSchema = z.object({ name: z.string().min(1), email: z.string().email(), }); const UpdateUserSchema = CreateUserSchema.partial(); @Injectable() class UserService { getAll() { return [{ id: 1, name: 'Alice' }]; } create(data: any) { return { id: 2, ...data }; } } @Controller('/users') class UserController { constructor(private userService: UserService) {} @Get() @Public() list() { return this.userService.getAll(); } @Post() @RequireAuth() create(@Body(CreateUserSchema) body: any) { return this.userService.create(body); } @Patch('/:id') @RequireAuth() update(@Param('id') id: string, @Body(UpdateUserSchema) body: any) { return this.userService.update(id, body); } @Delete('/:id') @RequireAuth() remove(@Param('id') id: string) { return this.userService.delete(id); } } const app = new Hono(); app.route('/', HonoRouteBuilder.build(UserController)); export default app;
Every decorator and API available in hono-forge, organized by category.
Every request passes through a clean, layered middleware pipeline before reaching your controller.
HonoRouteBuilder.build() reads decorator metadata (@Controller, @Get, @Post, @Sse, @WebSocket, etc.) and registers all routes onto a Hono app instance at startup.
A trace/correlation ID is assigned from X-Request-ID or auto-generated UUID and echoed back on the response. The onRequestStart hook fires here โ use it to start OTel spans or attach logger context.
@Middleware decorators at class or method level are resolved and prepended to the handler chain. Runs before guards and rate limiters.
@RateLimit triggers the pluggable rateLimiterFactory. Returns 429 if the limit is exceeded before the request reaches your handler.
@RequireAuth, @RequireRole, @RequirePermission are passed to your pluggable guardExecutor. Throws 401 for "Unauthorized", 403 for "Forbidden". @Public skips all guards.
@Body(schema) and @Query(schema) validate via Zod โ returns 400 on failure. @Param, @User, @Ip, @Device, @UserAgent, @Headers, @Req, @Res inject context values. @SseStream injects the SSE stream for streaming handlers.
Your business logic executes with DI-injected services resolved from the container. Request-scoped instances (@RequestScoped) are created fresh and shared within this request. getTraceId() is available anywhere in the call chain.
Return value is serialized to JSON. HttpException is auto-caught and returned as structured JSON at the correct status. onDestroy() is called on all @RequestScoped instances. requestLogger fires with method, path, statusCode, durationMs, ip, traceId.
All notable changes to hono-forge are documented here. Follows Keep a Changelog and Semantic Versioning.Loading...
Have feedback, questions, or suggestions? Drop a message below.
See what others have shared.