Launching Applications
Many real applications are not “compute a value and exit” programs — they are
long-running: an HTTP server, a queue consumer, a set of background workers
that should stay up until the process is stopped. Layer.launch is built for
exactly this shape. It takes a layer describing your
whole application, builds it (running all the layer’s startup logic and
acquiring its resources), and then never returns — it keeps the process alive
until interrupted, tearing every resource down cleanly on shutdown.
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"import { Effect, Layer } from "effect"import { HttpRouter, HttpServerResponse } from "effect/unstable/http"import { createServer } from "node:http"
// Describe the app as routes...const HealthRoutes = HttpRouter.use( Effect.fn(function*(router) { yield* router.add("GET", "/health", Effect.succeed(HttpServerResponse.text("ok"))) yield* router.add("GET", "/healthz", Effect.succeed(HttpServerResponse.text("ok"))) }))
// ...then as a server *layer*, with the Node HTTP backend provided.const HttpServerLive = HttpRouter.serve(HealthRoutes).pipe( Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })))
// Layer.launch converts the layer into a long-running Effect<never, E, RIn>:// it builds the layer, then blocks forever until interrupted.const main = Layer.launch(HttpServerLive)
// runMain makes it the process root, with signal handling + error reporting.NodeRuntime.runMain(main)How it works
Section titled “How it works”Layer.launch(layer) does two things:
- Builds the layer inside a scope — every service’s acquisition effect runs, side effects fire, the server starts listening.
- Waits forever (
Effect.never) so the process stays alive. The resulting effect has typeEffect<never, E, RIn>: it never succeeds (there is no value to produce), it may fail with the layer’s error typeE, andRInis whatever the layer still needs you to provide.
Because the build happens inside a scope, when the launched effect is
interrupted — say by SIGTERM under runMain — every
layer finalizer runs in reverse order. Your server stops accepting connections,
your database pool drains, your workers shut down. This is the payoff of
modelling the app as layers: startup and graceful shutdown come for free.
Composing the whole app
Section titled “Composing the whole app”The pattern scales: merge every part of your application — server, background workers, schedulers — into a single layer, then launch it. Each piece is an ordinary layer, so it can declare its own dependencies and finalizers.
import { NodeRuntime } from "@effect/platform-node"import { Effect, Layer } from "effect"
// A background worker expressed as a layer. forkScoped keeps the loop tied to// the layer's scope, so it is interrupted on shutdown.const Worker = Layer.effectDiscard( Effect.gen(function*() { yield* Effect.logInfo("worker online") yield* Effect.forkScoped( Effect.gen(function*() { while (true) { yield* Effect.logInfo("processing batch") yield* Effect.sleep("5 seconds") } }) ) }))
// Imagine HttpServerLive from the example above. Merge everything the app runs.declare const HttpServerLive: Layer.Layer<never>const AppLive = Layer.mergeAll(HttpServerLive, Worker)
// One launch, one runMain. The app stays up until interrupted, then every// layer's finalizers run in reverse for a clean shutdown.NodeRuntime.runMain(Layer.launch(AppLive))Handling failure at the boundary
Section titled “Handling failure at the boundary”Layer.launch returns a normal effect, so you can attach error handling before
running it — useful for logging why an app shut down:
import { Console, Effect, Layer } from "effect"
declare const AppLive: Layer.Layer<never, Error>
const application = Layer.launch(AppLive).pipe( Effect.tapError((error) => Console.error(`Application failed: ${error}`)))When to use it
Section titled “When to use it”Use Layer.launch whenever your application is naturally expressed as layers
and should run until stopped — servers, workers, daemons. Combine it with
NodeRuntime.runMain / BunRuntime.runMain for the full
production entrypoint: graceful signal-driven shutdown, error reporting, and
correct exit codes. For one-shot programs that compute a result and exit, you do
not need launch — just provide the layers and run the effect directly.