Skip to content

Latest commit

 

History

History
97 lines (76 loc) · 3.65 KB

File metadata and controls

97 lines (76 loc) · 3.65 KB
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 Layers 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.

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>);

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.

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));
});