Http Server
Building HttpApi servers
Section titled “Building HttpApi servers”HttpApi gives you schema-first, type-safe HTTP APIs with runtime validation, typed clients, and OpenAPI docs from one definition.
Getting started with HttpApi
Section titled “Getting started with HttpApi”Define a schema-first API, implement handlers, secure endpoints with middleware, serve it over HTTP, and call it using a generated typed client.
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"import { Context, Effect, flow, Layer, Schedule } from "effect"import { FetchHttpClient, HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http"import { HttpApiBuilder, HttpApiClient, HttpApiMiddleware, HttpApiScalar } from "effect/unstable/httpapi"import { createServer } from "node:http"// Api definitions should **always** be seperate from the server implementation,// so that they can be shared between the server and client without leaking// server code into clients.// Ideally, the would use a seperate package in a monorepo.import { Api } from "./fixtures/api/Api.ts"import { Authorization } from "./fixtures/api/Authorization.ts"import { UsersApiHandlers } from "./fixtures/server/Users/http.ts"
// This walkthrough focuses on runtime wiring and typed client usage.// See the fixture files for the API schemas, endpoint definitions and handlers:
const SystemApiHandlers = HttpApiBuilder.group( Api, "system", Effect.fn(function*(handlers) { return handlers.handle("health", () => Effect.void) }))
const ApiRoutes = HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json"}).pipe( // Provide all the handler Layers for the API. Layer.provide([UsersApiHandlers, SystemApiHandlers]))
// Define a /docs route that serves scalar documentationconst DocsRoute = HttpApiScalar.layer(Api, { path: "/docs"})
// Merge all the http routes togetherconst AllRoutes = Layer.mergeAll(ApiRoutes, DocsRoute)
// Create an HTTP server Layer that serves the API routes.//// Here we are using the NodeHttpServer, but you could also use the// BunHttpServerexport const HttpServerLayer = HttpRouter.serve(AllRoutes).pipe( Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })))
// Then run the server using Layer.launchLayer.launch(HttpServerLayer).pipe( NodeRuntime.runMain)
// Or create a web handler, which can be used in serverless environmentsexport const { handler, dispose } = HttpRouter.toWebHandler(AllRoutes.pipe( Layer.provide(HttpServer.layerServices)))
// -----------------// Client side setup// -----------------
export const AuthorizationClient = HttpApiMiddleware.layerClient( Authorization, Effect.fn(function*({ next, request }) { // Here you can modify the request and pass it down the middleware chain. // This is where you would add authentication tokens, custom headers, etc. // For this example, we just add a hardcoded bearer token to all requests. return yield* next(HttpClientRequest.bearerToken(request, "dev-token")) }))
// Define the HttpApiClient service, which will be used to make requests to the// API.export class ApiClient extends Context.Service<ApiClient, HttpApiClient.ForApi<typeof Api>>()("acme/ApiClient") { static readonly layer = Layer.effect( ApiClient, HttpApiClient.make(Api, { // Use transformClient to apply middleware to the generated client. This // is useful for settings the base url and applying retry policies. transformClient: (client) => client.pipe( HttpClient.mapRequest(flow( HttpClientRequest.prependUrl("http://localhost:3000") )), HttpClient.retryTransient({ schedule: Schedule.exponential(100), times: 3 }) ) }) ).pipe( // Provide the client implementation of the Authorization middleware, which // is required. Layer.provide(AuthorizationClient), // Supply a HttpClient implementation to use for making requests. Here we // use the FetchHttpClient, but you could also use the NodeHttpClient or // BunHttpClient. Layer.provide(FetchHttpClient.layer) )}
// The generated client mirrors your API definition, so renames and schema// changes are checked end-to-end at compile time.export const callApi = Effect.gen(function*() { const client = yield* ApiClient
yield* client.health()}).pipe( Effect.provide(ApiClient.layer))