Skip to main content
The HttpServer module provides infrastructure for serving HTTP applications built with Effect. It integrates with HttpRouter to handle requests and supports multiple runtime environments.

Overview

HttpServer offers:
  • Platform-agnostic: Works with Node.js, Bun, and serverless environments
  • Type-safe routing: Full TypeScript integration
  • Middleware support: Composable request/response processing
  • Effect integration: Leverage Effect’s error handling and resource management

Basic Server

Creating a Server

import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { Effect, Layer } from "effect"
import { HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
import { createServer } from "node:http"

// Define routes
const router = HttpRouter.empty.pipe(
  HttpRouter.get("/", Effect.succeed(HttpServerResponse.text("Hello, World!"))),
  HttpRouter.get("/health", Effect.succeed(HttpServerResponse.empty({ status: 200 })))
)

// Create server layer
const ServerLayer = HttpRouter.serve(router).pipe(
  Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)

// Run the server
Layer.launch(ServerLayer).pipe(
  NodeRuntime.runMain
)

Server Address

Access and log the server address:
import { HttpServer } from "effect/unstable/http"
import { Effect } from "effect"

const logAddress = HttpServer.logAddress

const ServerLayer = HttpRouter.serve(router).pipe(
  HttpServer.withLogAddress,
  Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)

Routing

Route Handlers

Define routes with HttpRouter:
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { Effect, Schema } from "effect"

const router = HttpRouter.empty.pipe(
  // Simple text response
  HttpRouter.get("/", 
    Effect.succeed(HttpServerResponse.text("Welcome"))
  ),
  
  // JSON response
  HttpRouter.get("/api/status",
    Effect.succeed(HttpServerResponse.json({ status: "ok" }))
  ),
  
  // Access request
  HttpRouter.post("/api/echo",
    Effect.gen(function*() {
      const request = yield* HttpServerRequest.HttpServerRequest
      const body = yield* request.json
      return HttpServerResponse.json(body)
    })
  ),
  
  // Path parameters
  HttpRouter.get("/users/:id",
    Effect.gen(function*() {
      const { params } = yield* HttpRouter.RouteContext
      return HttpServerResponse.json({ userId: params.id })
    })
  )
)

Route Groups

Organize routes with prefixes:
const apiRoutes = HttpRouter.empty.pipe(
  HttpRouter.get("/users", getUsersHandler),
  HttpRouter.post("/users", createUserHandler),
  HttpRouter.get("/users/:id", getUserHandler)
)

const router = HttpRouter.empty.pipe(
  HttpRouter.mount("/api/v1", apiRoutes)
)

Request Handling

Accessing Request Data

import { HttpServerRequest } from "effect/unstable/http"
import { Effect } from "effect"

const handler = Effect.gen(function*() {
  const request = yield* HttpServerRequest.HttpServerRequest
  
  // Headers
  const authHeader = request.headers.authorization
  
  // Query parameters
  const query = yield* HttpServerRequest.ParsedSearchParams
  
  // JSON body
  const body = yield* request.json
  
  // Form data
  const formData = yield* request.formData
  
  // Multipart data
  const multipart = yield* request.multipart
  
  return HttpServerResponse.json({ received: true })
})

Schema Validation

Validate request data with schemas:
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { Effect, Schema } from "effect"

const CreateUserSchema = Schema.Struct({
  name: Schema.String,
  email: Schema.String.pipe(Schema.filter(v => v.includes("@")))
})

const createUser = Effect.gen(function*() {
  const body = yield* HttpServerRequest.schemaBodyJson(CreateUserSchema)
  
  // body is now typed and validated
  console.log(body.name, body.email)
  
  return HttpServerResponse.json({ id: 1, ...body })
})

Response Types

Common Responses

import { HttpServerResponse } from "effect/unstable/http"

// Text
HttpServerResponse.text("Hello")

// JSON
HttpServerResponse.json({ message: "Success" })

// Empty (for 204, etc.)
HttpServerResponse.empty({ status: 204 })

// HTML
HttpServerResponse.html("<h1>Welcome</h1>")

// Binary data
HttpServerResponse.uint8Array(data)

// File
HttpServerResponse.file(path)

// Stream
HttpServerResponse.stream(stream)

Setting Headers and Status

import { HttpServerResponse } from "effect/unstable/http"

const response = HttpServerResponse.json({ data: "value" }).pipe(
  HttpServerResponse.setStatus(201),
  HttpServerResponse.setHeader("X-Custom", "value"),
  HttpServerResponse.setCookie("session", "abc123", {
    httpOnly: true,
    secure: true
  })
)

Error Handling

Global Error Handler

import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
import { Effect } from "effect"

const router = HttpRouter.empty.pipe(
  HttpRouter.get("/", riskyHandler),
  HttpRouter.catchAll((error) =>
    Effect.succeed(
      HttpServerResponse.json(
        { error: error.message },
        { status: 500 }
      )
    )
  )
)

Typed Errors

import { Schema } from "effect"

class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()("UserNotFound", {
  userId: Schema.String
}) {}

const getUser = (id: string) =>
  Effect.gen(function*() {
    const user = yield* findUser(id)
    if (!user) {
      return yield* Effect.fail(new UserNotFound({ userId: id }))
    }
    return HttpServerResponse.json(user)
  }).pipe(
    Effect.catchTag("UserNotFound", (error) =>
      Effect.succeed(
        HttpServerResponse.json(
          { error: "User not found", userId: error.userId },
          { status: 404 }
        )
      )
    )
  )

Middleware

Request Middleware

import { HttpRouter, HttpServerRequest, HttpMiddleware } from "effect/unstable/http"
import { Effect } from "effect"

const loggingMiddleware = HttpMiddleware.make((app) =>
  Effect.gen(function*() {
    const request = yield* HttpServerRequest.HttpServerRequest
    yield* Effect.log(`${request.method} ${request.url}`)
    return yield* app
  })
)

const router = HttpRouter.empty.pipe(
  HttpRouter.get("/", handler),
  HttpRouter.use(loggingMiddleware)
)

Authentication Middleware

import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { Effect } from "effect"

const authMiddleware = HttpMiddleware.make((app) =>
  Effect.gen(function*() {
    const request = yield* HttpServerRequest.HttpServerRequest
    const token = request.headers.authorization?.replace("Bearer ", "")
    
    if (!token) {
      return HttpServerResponse.json(
        { error: "Unauthorized" },
        { status: 401 }
      )
    }
    
    // Validate token and provide user context
    const user = yield* validateToken(token)
    yield* Effect.provideService(app, User, user)
  })
)

Platform Support

Node.js

import { NodeHttpServer } from "@effect/platform-node"
import { createServer } from "node:http"

const ServerLayer = HttpRouter.serve(router).pipe(
  Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)

Bun

import { BunHttpServer } from "@effect/platform-bun"

const ServerLayer = HttpRouter.serve(router).pipe(
  Layer.provide(BunHttpServer.layer({ port: 3000 }))
)

Serverless / Web Handler

import { HttpRouter, HttpServer } from "effect/unstable/http"

const routes = HttpRouter.empty.pipe(
  HttpRouter.get("/", handler)
)

export const { handler, dispose } = HttpRouter.toWebHandler(
  routes.pipe(
    Layer.provide(HttpServer.layerServices)
  )
)

Testing

Test Client

Create a test client for your server:
import { HttpServer, HttpClient } from "effect/unstable/http"
import { Effect } from "effect"

const testClient = Effect.gen(function*() {
  const client = yield* HttpServer.makeTestClient
  
  // Client automatically connects to your server
  const response = yield* client.get("/api/users")
  const users = yield* response.json
  
  return users
})

Real-World Example

Complete server with routing, validation, and error handling:
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { Effect, Layer, Schema } from "effect"
import { 
  HttpRouter, 
  HttpServer, 
  HttpServerRequest, 
  HttpServerResponse 
} from "effect/unstable/http"
import { createServer } from "node:http"

// Domain models
class User extends Schema.Class<User>("User")({
  id: Schema.Number,
  name: Schema.String,
  email: Schema.String
}) {}

const CreateUserInput = Schema.Struct({
  name: Schema.String,
  email: Schema.String
})

// Handlers
const getUsers = Effect.succeed(
  HttpServerResponse.json([
    { id: 1, name: "Alice", email: "[email protected]" },
    { id: 2, name: "Bob", email: "[email protected]" }
  ])
)

const createUser = Effect.gen(function*() {
  const input = yield* HttpServerRequest.schemaBodyJson(CreateUserInput)
  const user = { id: Date.now(), ...input }
  return HttpServerResponse.json(user, { status: 201 })
})

const getUserById = Effect.gen(function*() {
  const { params } = yield* HttpRouter.RouteContext
  const id = parseInt(params.id)
  
  // Simulate database lookup
  if (id === 1) {
    return HttpServerResponse.json({
      id: 1,
      name: "Alice",
      email: "[email protected]"
    })
  }
  
  return HttpServerResponse.json(
    { error: "User not found" },
    { status: 404 }
  )
})

// Routes
const router = HttpRouter.empty.pipe(
  HttpRouter.get("/health", 
    Effect.succeed(HttpServerResponse.empty({ status: 200 }))
  ),
  HttpRouter.get("/api/users", getUsers),
  HttpRouter.post("/api/users", createUser),
  HttpRouter.get("/api/users/:id", getUserById)
)

// Server
const ServerLayer = HttpRouter.serve(router).pipe(
  HttpServer.withLogAddress,
  Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)

Layer.launch(ServerLayer).pipe(
  Effect.tap(() => Effect.log("Server started on http://localhost:3000")),
  NodeRuntime.runMain
)

See Also

Build docs developers (and LLMs) love