HTTP overview
The HTTP module is separate from the I/O broker actors — HTTP servers don’t fit the “one connection many messages” shape, so they get their own DSL and backend abstraction.
import { ActorSystem, HttpExtensionId } from 'actor-ts';import { path, get, post, completeJson, entity } from 'actor-ts/http';
const system = ActorSystem.create('my-app');const http = system.extension(HttpExtensionId);
const routes = path('api', path('orders', concat( get(async () => completeJson(200, { orders: [] })), post(async (req) => { const order = entity<NewOrder>(req); // ... handle ... return completeJson(201, { id: 'o-1' }); }), ), ),);
const binding = await http.newServerAt('0.0.0.0', 8080).bind(routes);console.log(`bound on ${binding.host}:${binding.port}`);Three things going on:
- The route DSL —
path,get,post,concatcompose a tree of routes. Type-safe; compiles to a flat list at bind time. - The marshaller —
entity<T>(req)decodes the request body by Content-Type;completeJson/completeTextencode the response. - The extension —
system.extension(HttpExtensionId).newServerAt(host, port).bind(routes)starts a server.
The three pieces
Section titled “The three pieces”| Piece | Lives in | Page |
|---|---|---|
| Routing | actor-ts/http exports path, get, post, complete*, etc. | Route DSL |
| Marshalling | entity<T>(req) decode + completeJson encode. | Marshalling |
| Backend | Pluggable HTTP server — Fastify by default, Bun.serve, Express. | Backends |
The DSL builds an in-memory route tree; the extension’s
newServerAt(...).bind(routes) flattens it and registers
everything with the chosen backend.
The backends
Section titled “The backends”import { FastifyBackend, BunServeBackend, ExpressBackend } from 'actor-ts/http';
// Default — no setup needed:await http.newServerAt(host, port).bind(routes);// → uses FastifyBackend
// Explicit choice:await http.newServerAt(host, port) .useBackend(new BunServeBackend()) .bind(routes);| Backend | Runtime fit | When |
|---|---|---|
FastifyBackend (default) | Bun, Node | Production default — Fastify is the most-battle-tested. |
BunServeBackend | Bun only | When you want zero peer dependencies; uses Bun’s native Bun.serve. |
ExpressBackend | Node, Bun | Existing Express middleware ecosystem fits cleanly. |
All three implement the same HttpServerBackend interface — the
route DSL compiles identically; the backend just owns the
listen/dispatch loop.
See the backend pages for the configuration options each accepts.
The HTTP client
Section titled “The HTTP client”const response = await http.singleRequest({ method: 'POST', url: 'https://api.example.com/orders', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sku: 'book-1' }),});
console.log(response.status, response.body);The shared client wraps the runtime’s native fetch. Works
identically on Bun, Node 20+, and Deno (every supported runtime
has fetch built in).
For more elaborate client patterns (retries, caching), wrap the client in a retry + circuit-breaker call.
Middleware
Section titled “Middleware”import { responseCacheMiddleware, rateLimitMiddleware } from 'actor-ts/http';
const routes = concat( rateLimitMiddleware({ rps: 100 })( responseCacheMiddleware({ ttlMs: 30_000 })( path('api', /* ... */), ), ),);The framework ships three:
| Middleware | What it does |
|---|---|
| Response cache | Caches GET responses keyed by URL + Vary headers. |
| Rate limit | Per-IP / per-key token-bucket rate limiter. |
| Idempotency key | De-duplicates writes based on an Idempotency-Key header. |
Each is a Route -> Route transformer — wraps a sub-tree of routes
with its behavior. Compose by nesting. See
middleware pages.
Connecting HTTP to actors
Section titled “Connecting HTTP to actors”The route handler returns a Promise<HttpResponse> — you can
freely await actor calls inside:
import { ask } from 'actor-ts';
const routes = path('orders', path(':id', get(async (req) => { const id = req.path.split('/').pop(); const order = await ask( orderRegistry, { kind: 'get', id, replyTo: undefined as any }, 5_000, ); return completeJson(200, order); }), ),);This is the common pattern — HTTP handlers act as a thin adapter: parse the request, ask an actor, marshal the reply. Keep business logic in actors; HTTP handlers stay short.
Where to next
Section titled “Where to next”- Route DSL — the full DSL:
path,get/post/put/etc.,concat,complete*,redirect,reject. - Marshalling —
entity<T>, Content-Type-driven decoding, response encoding. - Fastify backend — default backend’s configuration.
- Bun.serve backend / Express backend — alternatives.
- Response cache middleware — caching for GET responses.
- Rate limit middleware — per-key rate limiting.
- Idempotency-key middleware — write-deduplication for mutations.
The HttpExtension API
reference covers the full surface.