Routing & Serving
The HttpRouter module is the low-level, hand-written way to build an HTTP server with
Effect. You register routes (method + path + handler) into a router Layer, then hand the
finished router to a platform server with HttpRouter.serve, or turn it into a Web
fetch handler with HttpRouter.toWebHandler.
All of the routing and serving APIs are unstable and import from
effect/unstable/http. The host-specific server Layer (NodeHttpServer, BunHttpServer)
comes from the platform packages — see Platform.
Mental model
Section titled “Mental model”Route layers do not handle requests when they are constructed. Each HttpRouter.add
or HttpRouter.addAll layer registers a Route into the current HttpRouter service while
the application Layer is being built. Only when you call serve, toHttpEffect, or
toWebHandler is the router read back and turned into an effect that, for each incoming
request, finds the matching route and runs its handler.
During a request the router provides these services into the handler’s context:
HttpServerRequest— the incoming request.Scope— a request-scopedScope.HttpServerRequest.ParsedSearchParams— parsed query string.HttpRouter.RouteContext— the matched route and its captured path params.
Building a router
Section titled “Building a router”The common case: define route layers with HttpRouter.add, merge them, and serve.
import { Effect, Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
// Each route is a Layer that registers itself with the current HttpRouter.const HelloRoute = HttpRouter.add( "GET", "/hello", // A handler can be a plain HttpServerResponse, an Effect of one, or a // function from the request to an Effect of one. Effect.succeed(HttpServerResponse.text("Hello, World!")))
const HealthRoute = HttpRouter.add( "GET", "/health", HttpServerResponse.json({ status: "ok" }))
// Merge the route layers into the application layer.export const App = Layer.mergeAll(HelloRoute, HealthRoute)HTTP methods and path patterns
Section titled “HTTP methods and path patterns”The method is one of "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS", or "*" to
match any method. Paths must be absolute (begin with /) or the bare wildcard "*".
"/users"— exact match."/users/:id"— captures a path parameter namedid."/files/*"— wildcard; a path ending in/*also matches the prefix itself (/filesmatches too).
import { Effect } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const UserRoute = HttpRouter.add( "GET", "/users/:id", // The handler form that receives the request directly. (request) => Effect.gen(function* () { // Read the captured ":id" param from RouteContext. const { id } = yield* HttpRouter.params return HttpServerResponse.text(`user ${id} from ${request.url}`) }))Using the router service directly
Section titled “Using the router service directly”HttpRouter.add / addAll are convenience layers built on HttpRouter.use, which gives
you the HttpRouter service so you can add several routes inside one Effect.gen:
import { Effect, Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const App = Layer.effectDiscard( Effect.gen(function* () { const router = yield* HttpRouter.HttpRouter
yield* router.add("GET", "/", HttpServerResponse.text("home")) yield* router.add("POST", "/echo", (request) => Effect.map(request.text, (body) => HttpServerResponse.text(body)) ) }))Batch registration with addAll and route
Section titled “Batch registration with addAll and route”Use HttpRouter.route to build standalone Route values, then register them all at once
with HttpRouter.addAll. You can also pass a { prefix } option to mount the whole batch
under a path.
import { Effect } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const ApiRoutes = HttpRouter.addAll( [ HttpRouter.route("GET", "/users", HttpServerResponse.text("list users")), HttpRouter.route("POST", "/users", HttpServerResponse.text("create user")) ], { prefix: "/api" } // -> GET /api/users, POST /api/users)Reading route params
Section titled “Reading route params”Handlers read captured path params, query params, and the request body through the
HttpRouter schema helpers. Each decodes with a Schema, so you get parsed, typed values
and a Schema.SchemaError (mapped to a 400) when input is invalid.
import { Effect, Schema } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
// :id is a string in the URL; Schema coerces and validates it as a number.const GetUser = HttpRouter.add( "GET", "/users/:id", Effect.gen(function* () { const { id } = yield* HttpRouter.schemaPathParams( Schema.Struct({ id: Schema.FiniteFromString }) ) // HttpServerResponse.json returns an Effect (it can fail to serialize), // so yield* it to get the response. return yield* HttpServerResponse.json({ id }) }))params— rawRecord<string, string | undefined>of path params (no decoding).schemaPathParams— decode the path params only.schemaParams— decode path and search params merged together (path wins on a key clash).schemaNoBody— decode method, url, headers, cookies, path params, and search params, without reading the body.schemaJson— decode all of the above plus the parsed JSON body.
import { Effect, Schema } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const CreateUser = HttpRouter.add( "POST", "/users", Effect.gen(function* () { // Pull the typed JSON body out of the request. const decoded = yield* HttpRouter.schemaJson( Schema.Struct({ body: Schema.Struct({ name: Schema.String, age: Schema.Number }) }) ) return yield* HttpServerResponse.json({ created: decoded.body.name }) }))Prefixing & composition
Section titled “Prefixing & composition”Mount a group of routes under a shared path. addAll(..., { prefix }) is the common path;
under the hood it uses router.prefixed, prefixPath, and prefixRoute.
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
// Mount each batch under its own prefix and merge.const v1 = HttpRouter.addAll( [HttpRouter.route("GET", "/ping", HttpServerResponse.text("v1 pong"))], { prefix: "/v1" })const v2 = HttpRouter.addAll( [HttpRouter.route("GET", "/ping", HttpServerResponse.text("v2 pong"))], { prefix: "/v2" })Router-level middleware
Section titled “Router-level middleware”Router middleware wraps the response effect of the routes it is provided to. Unlike the
serve middleware (which wraps the whole server chain and cannot change the sent response),
router middleware can provide services to handlers, handle configured errors, and modify
the response.
import { Context, Effect, Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
class CurrentSession extends Context.Service<CurrentSession, { readonly token: string}>()("CurrentSession") {}
// Middleware that provides CurrentSession to the routes it wraps.const SessionMiddleware = HttpRouter.middleware<{ provides: CurrentSession}>()( Effect.succeed((httpEffect) => Effect.provideService(httpEffect, CurrentSession, { token: "dummy-token" }) )).layer
const Route = HttpRouter.add( "GET", "/me", Effect.gen(function* () { const session = yield* CurrentSession return HttpServerResponse.text(`token: ${session.token}`) })).pipe(Layer.provide(SessionMiddleware))Pass { global: true } to middleware to apply it to every route in the router. The
two most common globals have dedicated helpers: HttpRouter.cors and
HttpRouter.disableLogger.
import { HttpRouter } from "effect/unstable/http"
// Apply CORS to all routes.const Cors = HttpRouter.cors({ allowedOrigins: ["https://example.com"] })provideRequest — provide a Layer per request
Section titled “provideRequest — provide a Layer per request”HttpRouter.provideRequest adapts a normal Layer so its services are provided to the
routes during request handling, satisfying their request-level requirements.
import { Context, Effect, Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
class Db extends Context.Service<Db, { readonly url: string }>()("Db") {}const DbLive = Layer.succeed(Db)({ url: "postgres://localhost" })
const Route = HttpRouter.add( "GET", "/db", Effect.map(Db, (db) => HttpServerResponse.text(db.url))).pipe(HttpRouter.provideRequest(DbLive))Serving on a platform server
Section titled “Serving on a platform server”HttpRouter.serve turns the application Layer into a server Layer. It still requires the
HttpServer service, which is provided by a host-specific platform layer. Provide
NodeHttpServer.layer or BunHttpServer.layer and launch it.
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"import { Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"import { createServer } from "node:http"
const App = HttpRouter.add( "GET", "/", HttpServerResponse.text("Hello from Node!"))
const ServerLayer = HttpRouter.serve(App).pipe( Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })))
Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)import { BunHttpServer, BunRuntime } from "@effect/platform-bun"import { Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const App = HttpRouter.add( "GET", "/", HttpServerResponse.text("Hello from Bun!"))
const ServerLayer = HttpRouter.serve(App).pipe( Layer.provide(BunHttpServer.layer({ port: 3000 })))
Layer.launch(ServerLayer).pipe(BunRuntime.runMain)By default serve installs the request logger and logs the listening address. Disable
those with the disableLogger and disableListenLog options. The middleware option wraps
the whole server chain.
Serving as a Web fetch handler
Section titled “Serving as a Web fetch handler”For serverless / edge runtimes, HttpRouter.toWebHandler builds a (request: Request) => Promise<Response> handler plus a dispose function for the layer scope. Provide
HttpServer.layerServices so the platform-neutral services (HttpPlatform, Path, ETag,
no-op FileSystem) are available.
import { Layer } from "effect"import { HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
const App = HttpRouter.add( "GET", "/", HttpServerResponse.text("Hello from fetch!")).pipe(Layer.provide(HttpServer.layerServices))
export const { handler, dispose } = HttpRouter.toWebHandler(App)
// const response = await handler(new Request("http://localhost/"))// => Response with body "Hello from fetch!"HttpRouter reference
Section titled “HttpRouter reference”The HttpRouter module, exhaustively. Import from effect/unstable/http.
HttpRouter
Section titled “HttpRouter”The service tag (a Context.Service) for the router being assembled. Use
yield* HttpRouter.HttpRouter inside a layer effect to get the router and call add /
addAll / prefixed on it.
import { Effect } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const useRouter = Effect.gen(function* () { const router = yield* HttpRouter.HttpRouter yield* router.add("GET", "/", HttpServerResponse.text("ok"))})Effect that constructs a fresh, empty HttpRouter service. Used internally by layer; you
rarely call it directly.
import { HttpRouter } from "effect/unstable/http"
const router = HttpRouter.make// => Effect<HttpRouter, never, RouterConfig>A Layer that provides a newly constructed HttpRouter. serve and toWebHandler
include it for you.
import { HttpRouter } from "effect/unstable/http"
const RouterLayer = HttpRouter.layer// => Layer<HttpRouter>Builds a standalone Route value from a method, path, and handler (without registering it).
Feed an array of routes to addAll.
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const r = HttpRouter.route("GET", "/ping", HttpServerResponse.text("pong"))// => Route<never, never>Layer that registers a single route. Accepts a static response, an Effect of a response, or
a function from the request to an Effect of a response. Optional { uninterruptible } keeps
the handler from being made interruptible.
import { Effect } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const Route = HttpRouter.add( "GET", "/hello", Effect.succeed(HttpServerResponse.text("Hello, World!")))// => Layer<never, never, HttpRouter | ...>addAll
Section titled “addAll”Layer that registers many routes at once. Accepts an array of Route (or an Effect that
produces one) and an optional { prefix } that mounts them all under a path.
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const Routes = HttpRouter.addAll( [HttpRouter.route("GET", "/a", HttpServerResponse.text("a"))], { prefix: "/v1" })// => registers GET /v1/aBuilds a route-registration Layer from a function that receives the HttpRouter service.
The building block behind add and addAll.
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const Route = HttpRouter.use((router) => router.add("GET", "/", HttpServerResponse.text("home")))// => Layer<never, never, HttpRouter>middleware
Section titled “middleware”Creates route-scoped (default) or global ({ global: true }) middleware. The type
parameters provides / handles declare which services the middleware adds to handlers and
which errors it handles. .layer is the layer you provide to routes; .combine composes
two middlewares.
import { Effect } from "effect"import { HttpMiddleware, HttpRouter } from "effect/unstable/http"
// Wrap CORS as router middleware.const Cors = HttpRouter.middleware(HttpMiddleware.cors()).layer// => Layer<...> to Layer.provide onto routesprefixPath
Section titled “prefixPath”Pure helper that prefixes a path string. Trailing slashes are trimmed from the prefix;
"/" becomes the prefix itself and "*" becomes a wildcard under the prefix. Dual.
import { HttpRouter } from "effect/unstable/http"
HttpRouter.prefixPath("/users", "/api") // => "/api/users"HttpRouter.prefixPath("*", "/api") // => "/api/*"prefixRoute
Section titled “prefixRoute”Returns a copy of a Route with its path prefixed, also tracking the prefix so it can be
stripped from the request URL at handle time. Dual.
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const base = HttpRouter.route("GET", "/ping", HttpServerResponse.text("pong"))const prefixed = HttpRouter.prefixRoute(base, "/api")// => Route matching GET /api/pingprovideRequest
Section titled “provideRequest”Adapts a Layer so its services are provided to routes during request handling, satisfying
their request-level requirements.
import { Context, Effect, Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
class Db extends Context.Service<Db, { readonly url: string }>()("Db") {}
const Route = HttpRouter.add("GET", "/db", Effect.map(Db, (db) => HttpServerResponse.text(db.url))).pipe(HttpRouter.provideRequest(Layer.succeed(Db)({ url: "..." })))Global CORS middleware layer. Options: allowedOrigins, allowedMethods,
allowedHeaders, exposedHeaders, maxAge, credentials.
import { HttpRouter } from "effect/unstable/http"
const Cors = HttpRouter.cors({ allowedOrigins: ["https://example.com"], credentials: true})// => Layer<never, never, HttpRouter>disableLogger
Section titled “disableLogger”A middleware layer that disables the request logger for the routes it is provided to.
import { Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const Route = HttpRouter.add( "GET", "/quiet", HttpServerResponse.text("shh")).pipe(Layer.provide(HttpRouter.disableLogger))params
Section titled “params”Effect returning the raw captured path params for the current matched route.
import { HttpRouter } from "effect/unstable/http"
const eff = HttpRouter.params// => Effect<ReadonlyRecord<string, string | undefined>, never, RouteContext>// for GET /users/:id matching /users/42 => { id: "42" }schemaParams
Section titled “schemaParams”Decodes a Schema from the merged search params and path params (path wins on a clash).
import { Schema } from "effect"import { HttpRouter } from "effect/unstable/http"
const decode = HttpRouter.schemaParams( Schema.Struct({ id: Schema.FiniteFromString, q: Schema.optional(Schema.String) }))// for /users/42?q=hi => { id: 42, q: "hi" }schemaPathParams
Section titled “schemaPathParams”Decodes a Schema from the path params only.
import { Schema } from "effect"import { HttpRouter } from "effect/unstable/http"
const decode = HttpRouter.schemaPathParams( Schema.Struct({ id: Schema.FiniteFromString }))// for /users/42 => { id: 42 }schemaJson
Section titled “schemaJson”Decodes a Schema from the full request envelope including the parsed JSON body
(method, url, headers, cookies, pathParams, searchParams, body). Fails with
HttpServerError if the body cannot be parsed, or Schema.SchemaError on decode failure.
import { Schema } from "effect"import { HttpRouter } from "effect/unstable/http"
const decode = HttpRouter.schemaJson( Schema.Struct({ body: Schema.Struct({ name: Schema.String }) }))// for POST {"name":"Ada"} => { body: { name: "Ada" } }schemaNoBody
Section titled “schemaNoBody”Like schemaJson but never reads the request body — decodes method, url, headers,
cookies, pathParams, and searchParams.
import { Schema } from "effect"import { HttpRouter } from "effect/unstable/http"
const decode = HttpRouter.schemaNoBody( Schema.Struct({ headers: Schema.Struct({ "x-trace": Schema.String }) }))// reads the x-trace header without consuming the bodyRouteContext
Section titled “RouteContext”The Context.Service holding the matched route and its captured params. The schema
helpers and params read from it; you can also access it directly.
import { Effect } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const Route = HttpRouter.add("GET", "/u/:id", Effect.gen(function* () { const ctx = yield* HttpRouter.RouteContext return HttpServerResponse.text(`${ctx.route.path} -> ${ctx.params.id}`) // => "/u/:id -> 7" for /u/7}))RouterConfig
Section titled “RouterConfig”A Context.Reference for low-level route-matcher configuration, defaulting to {}. Override
it via the routerConfig option on serve / toWebHandler, or by providing it as a layer.
import { HttpRouter } from "effect/unstable/http"
const config = HttpRouter.RouterConfig// => Context.Reference<Partial<FindMyWay.RouterConfig>>Turns an application Layer into a server Layer. Requires HttpServer (provided by a
platform layer). Options: routerConfig, disableLogger, disableListenLog, and
middleware (wraps the whole server chain).
import { NodeHttpServer } from "@effect/platform-node"import { Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"import { createServer } from "node:http"
const App = HttpRouter.add("GET", "/", HttpServerResponse.text("ok"))
const ServerLayer = HttpRouter.serve(App, { disableListenLog: true }).pipe( Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })))toHttpEffect
Section titled “toHttpEffect”Builds the application Layer and returns the router as a single handler Effect
(HttpServerResponse from HttpServerRequest + Scope). Useful for embedding the router
into a larger server effect.
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const App = HttpRouter.add("GET", "/", HttpServerResponse.text("ok"))
const handlerEffect = HttpRouter.toHttpEffect(App)// => Effect<Effect<HttpServerResponse, ..., Scope | HttpServerRequest | ...>, ...>toWebHandler
Section titled “toWebHandler”Builds a Web fetch-compatible handler ((request: Request) => Promise<Response>) plus a
dispose function. Options: memoMap, routerConfig, disableLogger, middleware.
import { Layer } from "effect"import { HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
const App = HttpRouter.add("GET", "/", HttpServerResponse.text("ok")).pipe( Layer.provide(HttpServer.layerServices))
export const { handler, dispose } = HttpRouter.toWebHandler(App)// const res = await handler(new Request("http://x/")) // => 200 "ok"Route / Route.Error / Route.Context
Section titled “Route / Route.Error / Route.Context”Route<E, R> is the description of a registered route (method, path, handler, prefix,
interruptibility). Route.Error<R> and Route.Context<T> extract a route’s error and
requirement types at the type level.
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
const r = HttpRouter.route("GET", "/", HttpServerResponse.text("x"))type E = HttpRouter.Route.Error<typeof r> // => nevertype R = HttpRouter.Route.Context<typeof r> // => neverPathInput
Section titled “PathInput”The path type accepted by the router: `/${string}` or "*".
import { HttpRouter } from "effect/unstable/http"
const path: HttpRouter.PathInput = "/users/:id"HttpServer reference
Section titled “HttpServer reference”The runtime that actually listens for connections and supplies HttpServerRequest per
request. Platform packages implement it; you typically consume it through HttpRouter.serve.
HttpServer
Section titled “HttpServer”The service tag. Exposes serve (run a response effect per request) and address (where it
is listening).
import { Effect } from "effect"import { HttpServer } from "effect/unstable/http"
const addr = Effect.map(HttpServer.HttpServer, (s) => s.address)// => Effect<Address, never, HttpServer>Constructs an HttpServer service from a serve implementation and an address. Used by
platform adapters.
import { Effect } from "effect"import { HttpServer } from "effect/unstable/http"
const service = HttpServer.make({ serve: (_httpEffect, _middleware) => Effect.void, address: { _tag: "TcpAddress", hostname: "127.0.0.1", port: 3000 }})Creates a Layer that starts serving a response effect with the current HttpServer. The
request service is supplied by the server; an optional middleware is applied at the server
boundary. (HttpRouter.serve wraps this for router layers.)
import { HttpServer, HttpServerResponse } from "effect/unstable/http"
const ServeLayer = HttpServer.serve( HttpServerResponse.text("hello"))// => Layer<never, never, HttpServer | ...>serveEffect
Section titled “serveEffect”Like serve but returns an Effect (requiring Scope) instead of a Layer.
import { HttpServer, HttpServerResponse } from "effect/unstable/http"
const eff = HttpServer.serveEffect(HttpServerResponse.text("hi"))// => Effect<void, never, Scope | HttpServer | ...>formatAddress
Section titled “formatAddress”Formats an Address as a display string: TCP becomes http://host:port, Unix becomes
unix://path.
import { HttpServer } from "effect/unstable/http"
HttpServer.formatAddress({ _tag: "TcpAddress", hostname: "localhost", port: 3000 })// => "http://localhost:3000"addressFormattedWith
Section titled “addressFormattedWith”Reads the current server address, formats it, and passes the string to your effectful function.
import { Effect } from "effect"import { HttpServer } from "effect/unstable/http"
const log = HttpServer.addressFormattedWith((url) => Effect.log(`up at ${url}`))// => Effect<void, never, HttpServer>logAddress
Section titled “logAddress”Effect that logs Listening on <formatted address> for the current server.
import { HttpServer } from "effect/unstable/http"
const eff = HttpServer.logAddress// => logs e.g. "Listening on http://localhost:3000"withLogAddress
Section titled “withLogAddress”Wraps a server-providing Layer so it logs the listening address on startup. serve
applies this by default unless disableListenLog is set.
import { NodeHttpServer } from "@effect/platform-node"import { HttpServer } from "effect/unstable/http"import { createServer } from "node:http"
const ServerLayer = HttpServer.withLogAddress( NodeHttpServer.layer(createServer, { port: 3000 }))makeTestClient
Section titled “makeTestClient”Builds an HttpClient whose requests are prepended with the running server’s URL (and
0.0.0.0 rewritten to 127.0.0.1). TCP only — Unix sockets are not supported.
import { HttpServer } from "effect/unstable/http"
const clientEffect = HttpServer.makeTestClient// => Effect<HttpClient, never, HttpServer | HttpClient>layerTestClient
Section titled “layerTestClient”Layer that provides the test HttpClient from makeTestClient.
import { HttpServer } from "effect/unstable/http"
const TestClient = HttpServer.layerTestClient// => Layer<HttpClient, never, HttpServer | HttpClient>layerServices
Section titled “layerServices”Layer bundling the platform-neutral services HTTP servers commonly need: HttpPlatform,
Path, a weak ETag generator, and a no-op FileSystem. Provide it to toWebHandler apps.
import { HttpServer } from "effect/unstable/http"
const Services = HttpServer.layerServices// => Layer<Path | HttpPlatform | FileSystem | Etag.Generator>Address / TcpAddress / UnixAddress
Section titled “Address / TcpAddress / UnixAddress”Address is the union TcpAddress | UnixAddress. TcpAddress has hostname and port;
UnixAddress has path. Both carry a _tag discriminator.
import type { HttpServer } from "effect/unstable/http"
const tcp: HttpServer.TcpAddress = { _tag: "TcpAddress", hostname: "0.0.0.0", port: 3000 }const unix: HttpServer.UnixAddress = { _tag: "UnixAddress", path: "/tmp/app.sock" }Web handler interop (HttpEffect)
Section titled “Web handler interop (HttpEffect)”HttpRouter.toWebHandler delegates to the lower-level HttpEffect module, which you can use
directly to bridge Effect HTTP programs and Web-standard Request / Response. Import from
effect/unstable/http.
HttpEffect.toWebHandler
Section titled “HttpEffect.toWebHandler”Converts an HTTP server effect (requiring only HttpServerRequest | Scope) into a
(request: Request) => Promise<Response> handler with an empty base context.
import { Effect } from "effect"import { HttpEffect, HttpServerResponse } from "effect/unstable/http"
const app = Effect.succeed(HttpServerResponse.text("ok"))const handler = HttpEffect.toWebHandler(app)// const res = await handler(new Request("http://x/")) // => 200 "ok"HttpEffect.toWebHandlerWith
Section titled “HttpEffect.toWebHandlerWith”Same as toWebHandler but you supply a base Context whose services are available to every
request.
import { Context, Effect } from "effect"import { HttpEffect, HttpServerResponse } from "effect/unstable/http"
const handlerFactory = HttpEffect.toWebHandlerWith(Context.empty())const handler = handlerFactory(Effect.succeed(HttpServerResponse.text("ok")))HttpEffect.toWebHandlerLayer
Section titled “HttpEffect.toWebHandlerLayer”Builds a web handler whose services come from a Layer, returning { handler, dispose }.
Call dispose to release the layer scope at shutdown.
import { Effect } from "effect"import { HttpEffect, HttpServer, HttpServerResponse } from "effect/unstable/http"
const { handler, dispose } = HttpEffect.toWebHandlerLayer( Effect.succeed(HttpServerResponse.text("ok")), HttpServer.layerServices)HttpEffect.toWebHandlerLayerWith
Section titled “HttpEffect.toWebHandlerLayerWith”The most general form: a Layer plus a toHandler factory that builds the response effect
from the layer’s context. HttpRouter.toWebHandler is implemented with this.
import { Effect } from "effect"import { HttpEffect, HttpServer, HttpServerResponse } from "effect/unstable/http"
const { handler, dispose } = HttpEffect.toWebHandlerLayerWith( HttpServer.layerServices, { toHandler: () => Effect.succeed(Effect.succeed(HttpServerResponse.text("ok"))) })HttpEffect.fromWebHandler
Section titled “HttpEffect.fromWebHandler”The inverse: embeds an existing Web-standard handler inside an Effect HTTP server, producing
an HttpServerResponse for the current HttpServerRequest.
import { HttpEffect } from "effect/unstable/http"
const eff = HttpEffect.fromWebHandler( async (_req) => new Response("from web handler"))// => Effect<HttpServerResponse, HttpServerError, HttpServerRequest>HttpEffect.PreResponseHandler
Section titled “HttpEffect.PreResponseHandler”The type of a function run with the current request and response just before the response is
sent; it may replace the response or fail with HttpServerError.
import { Effect } from "effect"import type { HttpEffect } from "effect/unstable/http"import { HttpServerResponse } from "effect/unstable/http"
const handler: HttpEffect.PreResponseHandler = (_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "x-served-by", "effect"))HttpEffect.appendPreResponseHandler
Section titled “HttpEffect.appendPreResponseHandler”Registers a pre-response handler for the current request. Runs before the bytes are sent (not after a streaming body starts).
import { Effect } from "effect"import { HttpEffect, HttpServerResponse } from "effect/unstable/http"
const eff = HttpEffect.appendPreResponseHandler((_req, res) => Effect.succeed(HttpServerResponse.setHeader(res, "x-app", "demo")))// => Effect<void, never, HttpServerRequest>HttpEffect.withPreResponseHandler
Section titled “HttpEffect.withPreResponseHandler”Runs an effect after registering a pre-response handler for the current request. Dual.
import { Effect } from "effect"import { HttpEffect, HttpServerResponse } from "effect/unstable/http"
const eff = HttpEffect.withPreResponseHandler( Effect.succeed(HttpServerResponse.text("ok")), (_req, res) => Effect.succeed(HttpServerResponse.setHeader(res, "x-app", "demo")))Server errors reference
Section titled “Server errors reference”Failures raised by the server runtime live in HttpServerError. Import from
effect/unstable/http.
HttpServerError
Section titled “HttpServerError”The wrapper tagged error (_tag: "HttpServerError"). It carries a reason and exposes
request and optional response, and is Respondable so it can convert itself to an HTTP
response.
import { HttpServerError } from "effect/unstable/http"
declare const u: unknownif (HttpServerError.isHttpServerError(u)) { // u.reason, u.request, u.response}isHttpServerError
Section titled “isHttpServerError”Refinement predicate for HttpServerError.
import { HttpServerError } from "effect/unstable/http"
HttpServerError.isHttpServerError(new Error("x")) // => falseRouteNotFound
Section titled “RouteNotFound”Reason raised when no route matched. Converts to an empty 404 and is ignored by the error
reporter.
import { HttpServerError } from "effect/unstable/http"
declare const request: anyconst reason = new HttpServerError.RouteNotFound({ request })// => 404RequestParseError
Section titled “RequestParseError”Reason for a failure to parse or read the incoming request. Converts to an empty 400.
import { HttpServerError } from "effect/unstable/http"
declare const request: anyconst reason = new HttpServerError.RequestParseError({ request, description: "bad body" })// => 400ResponseError
Section titled “ResponseError”Reason tied to a response that was being built or sent; carries both request and
response. Converts to an empty 500 (it does not reuse the failed response, which may be
partly sent).
import { HttpServerError } from "effect/unstable/http"
declare const request: anydeclare const response: anyconst reason = new HttpServerError.ResponseError({ request, response })// => 500InternalError
Section titled “InternalError”Reason for an unexpected server-side failure while handling a request. Converts to an empty
500.
import { HttpServerError } from "effect/unstable/http"
declare const request: anyconst reason = new HttpServerError.InternalError({ request, cause: new Error("boom") })// => 500ServeError
Section titled “ServeError”Tagged error wrapping a low-level failure from the server implementation itself (outside an individual handler response) — e.g. the port could not be bound.
import { HttpServerError } from "effect/unstable/http"
const err = new HttpServerError.ServeError({ cause: new Error("EADDRINUSE") })ClientAbort
Section titled “ClientAbort”A context annotation marking an interrupt as caused by the client aborting the request.
causeResponse maps it to a 499 instead of a server-abort 503.
import { HttpServerError } from "effect/unstable/http"
const annotation = HttpServerError.ClientAbort.annotation// used to annotate an interrupt => maps to 499RequestError / HttpServerErrorReason
Section titled “RequestError / HttpServerErrorReason”Type aliases: RequestError = RequestParseError | RouteNotFound | InternalError, and
HttpServerErrorReason = RequestError | ResponseError — the value carried as the reason of
an HttpServerError.
import type { HttpServerError } from "effect/unstable/http"
declare const reason: HttpServerError.HttpServerErrorReasoncauseResponse
Section titled “causeResponse”Converts a failed handler Cause into the [response, cause] pair to send and report.
Respondable failures choose their own response; pure interrupts become 499 (client abort)
or 503 (server abort); everything else defaults to 500.
import { Cause } from "effect"import { HttpServerError } from "effect/unstable/http"
const eff = HttpServerError.causeResponse(Cause.die("boom"))// => Effect<[HttpServerResponse (500), Cause]>exitResponse
Section titled “exitResponse”Extracts the HttpServerResponse from a successful handler Exit, or derives one
(stripping any embedded response defect, else 500) from the failure cause.
import { Exit } from "effect"import { HttpServerError, HttpServerResponse } from "effect/unstable/http"
const res = HttpServerError.exitResponse(Exit.succeed(HttpServerResponse.text("ok")))// => HttpServerResponse "ok"Where to next
Section titled “Where to next”- HttpApi — schema-first APIs with typed clients and OpenAPI, built on this router.
- HTTP Client — make outbound requests (and test your server).
- Platform — the host adapters (
NodeHttpServer,BunHttpServer) that provide theHttpServerservice.