--- title: Build a Basic HTTP Server id: build-a-basic-http-server skillLevel: advanced applicationPatternId: making-http-requests summary: >- Combine Layer, Runtime, and Effect to create a simple, robust HTTP server using Node.js's built-in http module. tags: - http - server - api - runtime - layer - end-to-end rule: description: >- Use a managed Runtime created from a Layer to handle requests in a Node.js HTTP server. related: - create-reusable-runtime-from-layers - create-managed-runtime-for-scoped-resources - implement-graceful-shutdown author: effect_website lessonOrder: 1 --- ## Guideline To build an HTTP server, create a main `AppLayer` that provides all your application's services. Compile this layer into a managed `Runtime` at startup. Use this runtime to execute an `Effect` for each incoming HTTP request, ensuring all logic is handled within the Effect system. --- ## Rationale This pattern demonstrates the complete lifecycle of a long-running Effect application. 1. **Setup Phase:** You define all your application's dependencies (database connections, clients, config) in `Layer`s and compose them into a single `AppLayer`. 2. **Runtime Creation:** You use `Layer.toRuntime(AppLayer)` to create a highly-optimized `Runtime` object. This is done _once_ when the server starts. 3. **Request Handling:** For each incoming request, you create an `Effect` that describes the work to be done (e.g., parse request, call services, create response). 4. **Execution:** You use the `Runtime` you created in the setup phase to execute the request-handling `Effect` using `Runtime.runPromise`. This architecture ensures that your request handling logic is fully testable, benefits from structured concurrency, and is completely decoupled from the server's setup and infrastructure. --- ## Good Example This example creates a simple server with a `Greeter` service. The server starts, creates a runtime containing the `Greeter`, and then uses that runtime to handle requests. ```typescript import { HttpServer, HttpServerResponse } from "@effect/platform"; import { NodeHttpServer } from "@effect/platform-node"; import { Duration, Effect, Fiber, Layer } from "effect"; import { createServer } from "node:http"; // Create a server layer using Node's built-in HTTP server const ServerLive = NodeHttpServer.layer(() => createServer(), { port: 3001 }); // Define your HTTP app (here responding "Hello World" to every request) const app = Effect.gen(function* () { yield* Effect.logInfo("Received HTTP request"); return yield* HttpServerResponse.text("Hello World"); }); const serverLayer = HttpServer.serve(app).pipe(Layer.provide(ServerLive)); const program = Effect.gen(function* () { yield* Effect.logInfo("Server starting on http://localhost:3001"); const fiber = yield* Layer.launch(serverLayer).pipe(Effect.fork); yield* Effect.sleep(Duration.seconds(2)); yield* Fiber.interrupt(fiber); yield* Effect.logInfo("Server shutdown complete"); }); Effect.runPromise(program as unknown as Effect.Effect); ``` --- ## Anti-Pattern Creating a new runtime or rebuilding layers for every single incoming request. This is extremely inefficient and defeats the purpose of Effect's `Layer` system. ```typescript import * as http from "http"; import { Effect, Layer } from "effect"; import { GreeterLive } from "./somewhere"; // ❌ WRONG: This rebuilds the GreeterLive layer on every request. const server = http.createServer((_req, res) => { const requestEffect = Effect.succeed("Hello!").pipe( Effect.provide(GreeterLive) // Providing the layer here is inefficient ); Effect.runPromise(requestEffect).then((msg) => res.end(msg)); }); ```