@dunx/http

Bun.serve adapter for the dunx framework: controllers, middleware and WebSocket gateways

@dunx/http
@dunx/http/client
@dunx/http/connect
@dunx/http/internal

Class-based controllers and WebSocket gateways for dunx, served by Bun.serve with standard decorators. There is no JavaScript router: Bun's built-in routes handles path parameters and HTTP methods natively.

HTTP and WebSockets share one listen(), one server and one port, without express, ws or socket.io.

Usage

TS
import { inject, Module } from '@dunx/core';
import { Controller, Get, HttpFactory, Post, type Input } from '@dunx/http';
import { z } from 'zod'; // or Valibot, or ArkType, or none at all

const createUser = { body: z.object({ name: z.string() }) } as const;
const oneUser = { params: z.object({ id: z.coerce.number() }) } as const;

@Controller('users')
export class UsersController {
  readonly #users = inject(UsersService);

  @Get('/')
  list() {
    return this.#users.findAll(); // plain values become Response.json()
  }

  @Get('/:id', oneUser)
  one(input: Input<typeof oneUser>) {
    return this.#users.find(input.params.id); // a number, already validated
  }

  @Post('/', createUser)
  create(input: Input<typeof createUser>) {
    return this.#users.create(input.body.name); // 201, no Response.json()
  }
}

@Module({ controllers: [UsersController], providers: [UsersService] })
export class UsersModule {}

const app = await HttpFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(3000);

What is here

Each row links to the guide that covers it.

AreaWhat it coversGuide
Controllers and routingVerb decorators, path params, prefixes, status codesControllers
Typed inputbody, query, params over Standard SchemaValidation
Middleware and guardsOne extension point, @UseGuards, @Roles, @PublicMiddleware and guards
WebSocket gateways@Gateway, handlers, PubSub, multi-node relayWebSockets
Server-sent events@Sse, SseStream, framing, heartbeats, Last-Event-IDControllers
Request loggingOne structured entry per request, on by defaultLogging
Trace contextW3C traceparent adopted and propagated, on by defaultLogging
SpansA SERVER span per request and a CLIENT span per call, with OtelModuleTracing
MetricsPer-route counts and timings, off by defaultMetrics
Health and draining/health/live, /health/ready, readiness during a rolloutHealth checks
Throttling@Throttle, @SkipThrottle, memory and Redis countersMiddleware and guards
Outbound resilienceHttpRetryClassifier: which statuses retry, and Retry-AfterResilience
Static filesBun.file behind a mount, with a cache policyDeployment
Compressionzstd, brotli and gzip on Bun's own compressorsDeployment
RPCprotobuf over Connect and gRPC-Web, as middlewareRPC

Subpaths

SubpathContains
@dunx/httpEverything above
@dunx/http/clientThe outbound half: HttpService, retry with backoff, HttpModule
@dunx/http/connectprotobuf services over Connect and gRPC-Web, mounted as middleware
@dunx/http/internalThe framework's own plumbing. No stability promise

@dunx/http/internal is for dunx's own packages: @dunx/dashboard, @dunx/mcp and @dunx/openapi. Do not import it from an app, since it can change in any release.

Notes

  • Routes are found at boot, including those inherited from a base class, so a subclass of an abstract controller serves its parent's @Get methods too.
  • Two handlers for the same method and path fail at boot, and the error names both. Bun would otherwise keep one of them without saying so.
  • A handler can return a Response, any JSON-serialisable value, or undefined for a 204.
  • Authorize and gate() protect admin pages. They receive the raw request and answer 404 when access is refused, or send a Response you return as it is, such as a sign-in page. @dunx/dashboard and @dunx/openapi both accept one, so you write the access rule once.
  • Schemas, parsers and status codes are worked out once at boot. Handling a request does no metadata lookups.
  • Every request joins the caller's W3C trace (traceparent). Its log lines carry traceId, spanId, parentSpanId and traceFlags, and the response carries a traceresponse header. requestLogging: { trace: false } turns both off. { traceResponse: false } keeps the trace fields and drops only the header, which saves about 500 ns a request. There is no separate correlation id.
  • metrics: true adds request counts and a latency histogram per route, for about 35 ns a request.
  • @dunx/http/connect serves Connect and gRPC-Web, but not native gRPC. Native gRPC sends its status in an HTTP trailer, which Bun.serve cannot send, so a request with content-type: application/grpc gets a 415 explaining why. @connectrpc/connect and @bufbuild/protobuf are optional peer dependencies, and you keep your own .proto toolchain. ThrottleGuard applies to RPC paths as well.