v0.2.1 ยท npm package ยท open source

Hono with
NestJS-style decorators.

Controllers, DI, guards, SSE, WebSocket, channels, rate limiting, OpenAPI, structured errors, and trace IDs โ€” all declarative. Install one package. No boilerplate.

View on GitHub โ†—Quick start โ†’
$bun add hono-forge hono zod
Hono 4.0+Bun 1.0+TypeScript 5.0+ZodSSEWebSocketRedisOpenAPI 3.1
14k+
req / sec
50+
decorators
...
GitHub stars
...
npm downloads
what's inside

Everything you need.
Nothing you don't.

One package. No boilerplate. Focus on business logic.

๐ŸŽจ
Decorator-Based Routing
NestJS-inspired decorators for clean, declarative controllers. Metadata-driven routing with full TypeScript inference.
@Controller@Get @Post@Put @Delete @All
๐Ÿ”
Auth & Authorization
Pluggable guard executor with role-based and permission-based access control. Throws 401/403 automatically.
@RequireAuth@RequireRole@Public
โœ…
Zod Validation
Type-safe parameter binding for body, query, and route params. Schema-first with full TypeScript inference.
@Body(schema)@Query(schema)@Param
๐Ÿ“ก
SSE & WebSocket
First-class streaming with @Sse and @WebSocket decorators. Pluggable upgrader for any Hono runtime.
@Sse@WebSocket@SseStream
๐Ÿ”
Pub/Sub Channels
Broadcast events to SSE and WebSocket clients. In-memory by default, swap to Redis for multi-instance deployments.
channels.publishSseChannelClientRedisChannelAdapter
๐Ÿ›ก๏ธ
Rate Limiting
Per-route rate limiting with custom window, max requests, and key generators. Pluggable for any backend.
@RateLimitcustom keyGen
๐Ÿ“Š
Request Logging
Pluggable logger with IP extraction, device detection, and duration tracking. Inject IP/device directly into handlers.
@Ip@Device@UserAgent
๐Ÿ’‰
Dependency Injection
Lightweight DI container with singleton, transient, and request-scoped lifetimes. Lifecycle hooks, circular dependency detection, and auto-resolution.
@Injectable@Singleton@RequestScoped@Inject
๐Ÿ“–
OpenAPI 3.1 + Scalar
Auto-generate a full OpenAPI spec from your decorators. Serve interactive Scalar docs with one line.
OpenAPIGenerator@ApiDoc@ApiTags
โšก
Interceptors
Cross-cutting concerns without middleware clutter. Retry, timeout, transform, cache, and metrics decorators.
@Retry@Timeout@Transform@Cache@TrackMetrics
๐ŸŒ
Built-in Middleware
First-class decorators for the most common Hono middleware. No boilerplate โ€” just stack and go.
@Cors@Compress@SecureHeaders@PrettyJson
๐Ÿšจ
Structured Error Handling
HttpException with static factories for every HTTP error. Auto-serialized to consistent JSON. Optional stack trace exposure for development.
HttpException.badRequest().notFound()exposeStack
๐Ÿ”ญ
Observability
Every request gets a trace ID from X-Request-ID or auto-generated UUID. Access it anywhere โ€” services, repos, loggers โ€” without passing it explicitly.
getTraceId()onRequestStartX-Request-ID
quick start

From zero to API
in minutes.

Install one package, decorate your controllers, mount the routes.

1. Install
terminal
$ bun add hono-forge hono zod
2. No setup needed
tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext"
  }
}
3. Write your controller
controller.ts
import { Hono } from 'hono';
import type { Context } 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')
@Injectable([UserService])
class UserController {
  constructor(private userService: UserService) {}

  @Get() @Public()
  list() { return this.userService.getAll(); }

  @Post() @RequireAuth()
  async create(c: Context) {
    const body = await Body(c, CreateUserSchema);
    return this.userService.create(body);
  }

  @Patch('/:id') @RequireAuth()
  async update(c: Context) {
    const id   = Param(c, 'id');
    const body = await Body(c, UpdateUserSchema);
    return this.userService.update(id, body);
  }

  @Delete('/:id') @RequireAuth()
  remove(c: Context) {
    return this.userService.delete(Param(c, 'id'));
  }
}

const app = new Hono();
app.route('/', HonoRouteBuilder.build(UserController));
export default app;
reference

All decorators.

Every decorator and API available in hono-forge, organized by category. Hover any item to see a usage example.

routing
@Controller(path?, opts?)hover
Registers a class as a controller. Accepts basePath, platform, and version.
@Get(path?)hover
Maps method to HTTP GET.
@Post(path?)hover
Maps method to HTTP POST.
@Put(path?)hover
Maps method to HTTP PUT.
@Delete(path?)hover
Maps method to HTTP DELETE.
@Patch(path?)hover
Maps method to HTTP PATCH.
@Head(path?)hover
Registered as GET; Hono handles HEAD requests via fallback.
@Options(path?)hover
Maps method to HTTP OPTIONS.
@All(path?)hover
Matches all HTTP methods on the given path.
real-time
@Sse(path?)hover
Registers a GET endpoint for SSE. Handler receives (c: Context, stream: SSEStreamingApi) โ€” no decorator needed for the stream.
@WebSocket(path?)hover
Registers a WebSocket upgrade endpoint. Handler receives (c: Context) and returns { onOpen, onMessage, onClose }.
@Middleware(fn)hover
Attaches Hono middleware at class level (all routes) or method level (one route).
@Use(fn)hover
Alias for @Middleware โ€” familiar for NestJS/Express users.
built-in middleware
@Cors(opts?)hover
Applies CORS headers. Options match hono/cors โ€” origin, allowMethods, maxAge, etc.
@Compress(opts?)hover
Applies gzip/deflate response compression. Works at class or method level.
@SecureHeaders(opts?)hover
Adds security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, etc.
@PrettyJson(opts?)hover
Pretty-prints JSON responses when ?pretty query param is present.
auth & security
@RequireAuth()hover
Attaches AuthGuard โ€” your guardExecutor validates the token.
@RequireRole(...roles)hover
Requires user to have ONE of the specified roles.
@RequireAllRoles(...roles)hover
Requires user to have ALL specified roles.
@RequirePermission(...perms)hover
Requires ALL listed permissions.
@RequireAnyPermission(...perms)hover
Requires at least ONE of the listed permissions.
@Public()hover
Marks route as public, bypassing all auth guards.
@Private()hover
Marks route as internal-only. Excluded when build() is called with { excludePrivate: true }.
@RateLimit(opts)hover
Rate-limits the route. Options: max, windowMs, message, keyGenerator.
context helpers
Body(c, schema?)hover
Reads + validates request body. Pass a Zod schema for validation โ€” returns 400 on failure.
Query(c, schema?)hover
Reads query params. Pass a Zod schema to validate and type the result.
Param(c, name)hover
Reads a route path parameter by name.
Headers(c, name?)hover
Reads a request header. Omit name to get all headers as an object.
User<T>(c)hover
Returns the authenticated user object set by the guard.
Ip(c)hover
Returns the real client IP (CF-Connecting-IP โ†’ X-Real-IP โ†’ X-Forwarded-For).
Device(c)hover
Returns detected device type: 'mobile' | 'tablet' | 'desktop' | 'bot'.
UserAgent(c)hover
Returns the raw User-Agent header string.
Cookie(c, name)hover
Returns a single cookie value by name.
Cookies(c)hover
Returns all cookies as Record<string, string>.
Req(c)hover
Returns the Hono HonoRequest object.
Ctx(c) / Res(c)hover
Returns the full Hono Context c โ€” for redirect, set-cookie, etc.
ValidatedBody(c, schema)hover
Alias for Body(c, schema) โ€” emphasises validation intent.
ValidatedQuery(c, schema)hover
Alias for Query(c, schema).
ValidatedParam(c, name, schema)hover
Reads a route param and validates it through a Zod schema.
UploadedFile(c, field)hover
Returns a single File from multipart form data by field name. Returns File | null.
UploadedFiles(c, field?)hover
Returns all File objects for a field. Omit field to collect every file in the form.
FormBody(c)hover
Returns the raw FormData object.
channels (pub/sub)
channels.subscribe(ch, client)hover
Add a client to a channel. Client can be SseChannelClient or WsChannelClient.
channels.unsubscribe(ch, id)hover
Remove a client from a channel by clientId.
channels.publish(ch, event, data)hover
Broadcast an event+data payload to all subscribers of a channel.
channels.use(adapter)hover
Swap the channel adapter at startup. Default: InMemoryChannelAdapter.
new RedisChannelAdapter(pub, sub)hover
Redis-backed adapter for multi-instance SSE/WS broadcasting.
interceptors
@Retry(opts)hover
Retries the method on failure. Options: attempts, delay, backoff (linear | exponential).
@Timeout(ms)hover
Rejects with an error if the method takes longer than ms milliseconds.
@Transform(fn)hover
Applies a transform function to the return value before sending.
@Cache(opts)hover
Stores cache metadata. Options: ttl, key. Integrate with your own cache layer.
@TrackMetrics(opts)hover
Records method duration via this.metrics.trackMethodDuration(). Options: name.
@Throttle(ms)hover
Prevents the method from being called more than once per ms window. Throws if called too soon.
@Memoize(opts?)hover
Caches the return value in memory. Options: ttl (ms), scope ("global" | "request").
@ValidateResult(schema)hover
Validates the return value against a Zod schema. Throws ZodError on mismatch.
@Audit(opts)hover
Logs an audit entry via this.logger before the method runs. Options: action.
@Transaction(exec?)hover
Wraps method in this.db.transaction(). Pass a custom TransactionExecutor for Prisma ($transaction) or Kysely.
api documentation
@ApiTags(...tags)hover
Groups controller endpoints under named tags in the OpenAPI spec.
@ApiDoc(opts)hover
Documents an endpoint. Options: summary, description.
@ApiResponse(status, msg)hover
Describes a possible HTTP response for this endpoint.
@ApiDeprecated()hover
Marks the endpoint as deprecated in the generated spec.
OpenAPIGenerator.generate()hover
Generates a full OpenAPI 3.1 spec from your controller classes.
OpenAPIGenerator.mount()hover
Serves /openapi.json and Scalar UI at /docs on a Hono app.
dependency injection
@Injectable(tokens?)hover
Registers a class in the DI container. Pass explicit dependency tokens: @Injectable([Database, Logger]).
@Singleton()hover
Returns the same instance on every resolve().
@RequestScoped()hover
Fresh instance per request, destroyed automatically when the request ends. Shared within the same request.
@Stateless()hover
Enforces that a @Singleton holds no mutable per-request state. Writes to the instance throw at runtime.
container.boot()hover
Calls onInit() on all registered singletons that implement OnInit. Call once at app startup before starting the server.
container.shutdown()hover
Calls onDestroy() on all singletons in reverse registration order. Call in SIGTERM/SIGINT handler.
container.runInScope(fn)hover
Runs fn in a new request scope. onDestroy is called on all scoped instances in the finally block.
container.registerInstance(token, value)hover
Registers a pre-built external object (Drizzle, Prisma, Redis, etc.) under a symbol token.
error handling
HttpExceptionhover
Structured HTTP error with status, code, message, and optional meta payload. Auto-handled by the route builder.
HttpException.notFound(msg?)hover
404 NOT_FOUND
HttpException.unauthorized(msg?)hover
401 UNAUTHORIZED
HttpException.forbidden(msg?)hover
403 FORBIDDEN
HttpException.conflict(msg?)hover
409 CONFLICT
HttpException.badRequest(msg?)hover
400 BAD_REQUEST
HttpException.internal(msg?)hover
500 INTERNAL_SERVER_ERROR
observability & config
getTraceId()hover
Returns the active trace/correlation ID. Callable from anywhere in the call chain โ€” services, repos, loggers โ€” without passing it explicitly.
runWithTraceId(id, fn)hover
Runs fn within a trace context. Use when running handlers outside HonoRouteBuilder.
onRequestStart hookhover
Called before middleware and guards on every request. Receives { method, path, traceId, ip, userAgent }.
exposeStack optionhover
Controls stack trace exposure in HttpException responses. Values: false (default, safe) | true | "development".
how it works

Request flow.

Every request passes through a clean, layered middleware pipeline before reaching your controller.

01

Route Registration

HonoRouteBuilder.build() reads decorator metadata (@Controller, @Get, @Post, @Sse, @WebSocket, etc.) and registers all routes onto a Hono app instance at startup.

02

Trace ID & onRequestStart

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.

03

Middleware

@Middleware decorators at class or method level are resolved and prepended to the handler chain. Runs before guards and rate limiters.

04

Rate Limiting

@RateLimit triggers the pluggable rateLimiterFactory. Returns 429 if the limit is exceeded before the request reaches your handler.

05

Auth Guards

@RequireAuth, @RequireRole, @RequirePermission are passed to your pluggable guardExecutor. Throws 401 for "Unauthorized", 403 for "Forbidden". @Public skips all guards.

06

Parameter Resolution

@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.

07

Controller Method

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.

08

Response & Cleanup

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.

changelog

What's new

All notable changes to hono-forge are documented here. Follows Keep a Changelog and Semantic Versioning.Loading...

๐Ÿ”ง

@Stateless runtime enforcement

Resolved @Stateless @Singleton instances are now wrapped in a Proxy

feedback

Share Your Thoughts

Have feedback, questions, or suggestions? Drop a message below.

messages

Recent Feedback

See what others have shared.