| 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 |
|
||||||
| rule |
|
||||||
| related |
|
||||||
| author | effect_website | ||||||
| lessonOrder | 1 |
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.
This pattern demonstrates the complete lifecycle of a long-running Effect application.
- Setup Phase: You define all your application's dependencies (database connections, clients, config) in
Layers and compose them into a singleAppLayer. - Runtime Creation: You use
Layer.toRuntime(AppLayer)to create a highly-optimizedRuntimeobject. This is done once when the server starts. - Request Handling: For each incoming request, you create an
Effectthat describes the work to be done (e.g., parse request, call services, create response). - Execution: You use the
Runtimeyou created in the setup phase to execute the request-handlingEffectusingRuntime.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.
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.
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<void, unknown, never>);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.
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));
});