Effect by Example: Next.js API Handler (App Router)

Tags:

nextjs
import {
import HttpApp
HttpApp
,
import HttpServerRequest
HttpServerRequest
,
import HttpServerResponse
HttpServerResponse
,
import UrlParams
UrlParams
,
} from "@effect/platform";
import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Layer
Layer
,
import ManagedRuntime
ManagedRuntime
} from "effect";
// your main layer representing all of the services your handler needs (db, auth, etc.)
const
const mainLive: Layer.Layer<never, never, never>
mainLive
=
import Layer
Layer
.
const empty: Layer.Layer<never, never, never>

A Layer that constructs an empty Context.

@since2.0.0

empty
;
const
const managedRuntime: ManagedRuntime.ManagedRuntime<never, never>
managedRuntime
=
import ManagedRuntime
ManagedRuntime
.
const make: <never, never>(layer: Layer.Layer<never, never, never>, memoMap?: Layer.MemoMap | undefined) => ManagedRuntime.ManagedRuntime<never, never>

Convert a Layer into an ManagedRuntime, that can be used to run Effect's using your services.

@since2.0.0

@example

import { Console, Effect, Layer, ManagedRuntime } from "effect"
class Notifications extends Effect.Tag("Notifications")<
Notifications,
{ readonly notify: (message: string) => Effect.Effect<void> }
>() {
static Live = Layer.succeed(this, { notify: (message) => Console.log(message) })
}
async function main() {
const runtime = ManagedRuntime.make(Notifications.Live)
await runtime.runPromise(Notifications.notify("Hello, world!"))
await runtime.dispose()
}
main()

make
(
const mainLive: Layer.Layer<never, never, never>
mainLive
);
const
const runtime: Runtime<never>
runtime
= await
const managedRuntime: ManagedRuntime.ManagedRuntime<never, never>
managedRuntime
.
ManagedRuntime<never, never>.runtime: () => Promise<Runtime<never>>
runtime
();
// everything interesting happens in this effect
// Which is of type Effect<HttpServerResponse, _, HttpServerRequest>
// it consumes the request from context anywhere
// and ultimately produces some http response
const
const exampleEffectHandler: Effect.Effect<HttpServerResponse.HttpServerResponse, RequestError | Error | HttpBodyError, HttpServerRequest.HttpServerRequest>
exampleEffectHandler
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<HttpServerRequest.HttpServerRequest, HttpServerRequest.HttpServerRequest>> | YieldWrap<Effect.Effect<UrlParams.UrlParams, RequestError, never>> | YieldWrap<...> | YieldWrap<...>, HttpServerResponse.HttpServerResponse>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
const
const request: HttpServerRequest.HttpServerRequest
request
= yield*
import HttpServerRequest
HttpServerRequest
.
const HttpServerRequest: Tag<HttpServerRequest.HttpServerRequest, HttpServerRequest.HttpServerRequest>

@since1.0.0

@since1.0.0

HttpServerRequest
;
const
const params: UrlParams.UrlParams
params
= yield*
const request: HttpServerRequest.HttpServerRequest
request
.
HttpIncomingMessage<RequestError>.urlParamsBody: Effect.Effect<UrlParams.UrlParams, RequestError, never>
urlParamsBody
;
const
const id: string
id
= yield*
import UrlParams
UrlParams
.
const getFirst: (self: UrlParams.UrlParams, key: string) => Option<string> (+1 overload)

@since1.0.0

getFirst
(
const params: UrlParams.UrlParams
params
, "id").
Pipeable.pipe<Option<string>, Effect.Effect<string, Error, never>>(this: Option<string>, ab: (_: Option<string>) => Effect.Effect<string, Error, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const mapError: <NoSuchElementException, Error>(f: (e: NoSuchElementException) => Error) => <A, R>(self: Effect.Effect<A, NoSuchElementException, R>) => Effect.Effect<...> (+1 overload)

Transforms or modifies the error produced by an effect without affecting its success value.

When to Use

This function is helpful when you want to enhance the error with additional information, change the error type, or apply custom error handling while keeping the original behavior of the effect's success values intact. It only operates on the error channel and leaves the success channel unchanged.

Example

import { Effect } from "effect"
// ┌─── Effect<number, string, never>
// ▼
const simulatedTask = Effect.fail("Oh no!").pipe(Effect.as(1))
// ┌─── Effect<number, Error, never>
// ▼
const mapped = Effect.mapError(
simulatedTask,
(message) => new Error(message)
)

@seemap for a version that operates on the success channel.

@seemapBoth for a version that operates on both channels.

@seeorElseFail if you want to replace the error with a new one.

@since2.0.0

mapError
(() => new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("no id param found")),
);
const
const data: User
data
= yield*
const doThing: (id: string) => Effect.Effect<User, Error>
doThing
(
const id: string
id
);
return yield*
import HttpServerResponse
HttpServerResponse
.
const json: (body: unknown, options?: HttpServerResponse.Options.WithContentType | undefined) => Effect.Effect<HttpServerResponse.HttpServerResponse, HttpBodyError>

@since1.0.0

json
(
const data: User
data
);
});
const
const webHandler: (request: Request, context?: Context<never> | undefined) => Promise<Response>
webHandler
=
import HttpApp
HttpApp
.
const toWebHandlerRuntime: <never>(runtime: Runtime<never>) => <E>(self: HttpApp.Default<E, Scope>, middleware?: HttpMiddleware | undefined) => (request: Request, context?: Context<never> | undefined) => Promise<Response>

@since1.0.0

toWebHandlerRuntime
(
const runtime: Runtime<never>
runtime
)(
const exampleEffectHandler: Effect.Effect<HttpServerResponse.HttpServerResponse, RequestError | Error | HttpBodyError, HttpServerRequest.HttpServerRequest>
exampleEffectHandler
);
type
type Handler = (req: Request) => Promise<Response>
Handler
= (
req: Request
req
:
interface Request
Request
) =>
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Response
Response
>;
export const
const GET: Handler
GET
:
type Handler = (req: Request) => Promise<Response>
Handler
=
const webHandler: (request: Request, context?: Context<never> | undefined) => Promise<Response>
webHandler
;

waitUntil

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Runtime
Runtime
,
import Context

@since2.0.0

@since2.0.0

Context
,
import Layer
Layer
} from "effect";
class
class WaitUntil
WaitUntil
extends
import Context

@since2.0.0

@since2.0.0

Context
.
const Tag: <"WaitUntil">(id: "WaitUntil") => <Self, Shape>() => Context.TagClass<Self, "WaitUntil", Shape>

@example

import * as assert from "node:assert"
import { Context, Layer } from "effect"
class MyTag extends Context.Tag("MyTag")<
MyTag,
{ readonly myNum: number }
>() {
static Live = Layer.succeed(this, { myNum: 108 })
}

@since2.0.0

Tag
("WaitUntil")<
class WaitUntil
WaitUntil
,
(
promise: Promise<unknown>
promise
:
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<unknown>) => void
>() {}
const
const effectWaitUntil: <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal) => Effect.Effect<void, never, WaitUntil | R>
effectWaitUntil
= <
function (type parameter) A in <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal): Effect.Effect<void, never, WaitUntil | R>
A
,
function (type parameter) E in <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal): Effect.Effect<void, never, WaitUntil | R>
E
,
function (type parameter) R in <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal): Effect.Effect<void, never, WaitUntil | R>
R
>(
effect: Effect.Effect<A, E, R>
effect
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
function (type parameter) A in <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal): Effect.Effect<void, never, WaitUntil | R>
A
,
function (type parameter) E in <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal): Effect.Effect<void, never, WaitUntil | R>
E
,
function (type parameter) R in <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal): Effect.Effect<void, never, WaitUntil | R>
R
>,
abortSignal: AbortSignal | undefined
abortSignal
?:
interface AbortSignal

A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object.

AbortSignal
,
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runtime: <R>() => Effect.Effect<Runtime.Runtime<R>, never, R>

Returns an effect that accesses the runtime, which can be used to (unsafely) execute tasks.

When to Use

This is useful for integration with legacy code that must call back into Effect code.

@since2.0.0

runtime
<
function (type parameter) R in <A, E, R>(effect: Effect.Effect<A, E, R>, abortSignal?: AbortSignal): Effect.Effect<void, never, WaitUntil | R>
R
>().
Pipeable.pipe<Effect.Effect<Runtime.Runtime<R>, never, R>, Effect.Effect<[Runtime.Runtime<R>, (promise: Promise<unknown>) => void], never, WaitUntil | R>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const zip: <(promise: Promise<unknown>) => void, never, WaitUntil>(that: Effect.Effect<(promise: Promise<unknown>) => void, never, WaitUntil>, options?: {
readonly concurrent?: boolean | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly concurrentFinalizers?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

Combines two effects into a single effect, producing a tuple of their results.

Details

This function combines two effects, self and that, into one. It executes the first effect (self) and then the second effect (that), collecting their results into a tuple. Both effects must succeed for the resulting effect to succeed. If either effect fails, the entire operation fails.

By default, the effects are executed sequentially. If the concurrent option is set to true, the effects will run concurrently, potentially improving performance for independent operations.

Example (Combining Two Effects Sequentially)

import { Effect } from "effect"
const task1 = Effect.succeed(1).pipe(
Effect.delay("200 millis"),
Effect.tap(Effect.log("task1 done"))
)
const task2 = Effect.succeed("hello").pipe(
Effect.delay("100 millis"),
Effect.tap(Effect.log("task2 done"))
)
// Combine the two effects together
//
// ┌─── Effect<[number, string], never, never>
// ▼
const program = Effect.zip(task1, task2)
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="task1 done"
// timestamp=... level=INFO fiber=#0 message="task2 done"
// [ 1, 'hello' ]

Example (Combining Two Effects Concurrently)

import { Effect } from "effect"
const task1 = Effect.succeed(1).pipe(
Effect.delay("200 millis"),
Effect.tap(Effect.log("task1 done"))
)
const task2 = Effect.succeed("hello").pipe(
Effect.delay("100 millis"),
Effect.tap(Effect.log("task2 done"))
)
// Run both effects concurrently using the concurrent option
const program = Effect.zip(task1, task2, { concurrent: true })
Effect.runPromise(program).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="task2 done"
// timestamp=... level=INFO fiber=#0 message="task1 done"
// [ 1, 'hello' ]

@seezipWith for a version that combines the results with a custom function.

@seevalidate for a version that accumulates errors.

@since2.0.0

zip
(
class WaitUntil
WaitUntil
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <[Runtime.Runtime<R>, (promise: Promise<unknown>) => void], void, never, never>(f: (a: [Runtime.Runtime<R>, (promise: Promise<unknown>) => void]) => Effect.Effect<...>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(([
runtime: Runtime.Runtime<R>
runtime
,
waitUntil: (promise: Promise<unknown>) => void
waitUntil
]) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const sync: <void>(thunk: LazyArg<void>) => Effect.Effect<void, never, never>

Creates an Effect that represents a synchronous side-effectful computation.

Details

The provided function (thunk) must not throw errors; if it does, the error will be treated as a "defect".

This defect is not a standard error but indicates a flaw in the logic that was expected to be error-free. You can think of it similar to an unexpected crash in the program, which can be further managed or logged using tools like

catchAllDefect

.

When to Use

Use this function when you are sure the operation will not fail.

Example (Logging a Message)

import { Effect } from "effect"
const log = (message: string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
const program = log("Hello, World!")

@seetry_try for a version that can handle failures.

@since2.0.0

sync
(() =>
waitUntil: (promise: Promise<unknown>) => void
waitUntil
(
import Runtime
Runtime
.
const runPromise: <R, A, E>(runtime: Runtime.Runtime<R>, effect: Effect.Effect<A, E, R>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<...> (+1 overload)

Runs the Effect, returning a JavaScript Promise that will be resolved with the value of the effect once the effect has been executed, or will be rejected with the first error or exception throw by the effect.

This method is effectful and should only be used at the edges of your program.

@since2.0.0

runPromise
(
runtime: Runtime.Runtime<R>
runtime
,
effect: Effect.Effect<A, E, R>
effect
, {
signal?: AbortSignal | undefined
signal
:
abortSignal: AbortSignal | undefined
abortSignal
})),
),
),
);
import {
const waitUntil: (promise: Promise<unknown>) => void | undefined

Extends the lifetime of the request handler for the lifetime of the given

Promise

@seehttps://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil

@parampromise The promise to wait for.

@example

import { waitUntil } from '@vercel/functions';
export function GET(request) {
waitUntil(fetch('https://vercel.com'));
return new Response('OK');
}

waitUntil
} from "@vercel/functions";
const
const VercelWaitUntil: Layer.Layer<WaitUntil, never, never>
VercelWaitUntil
=
import Layer
Layer
.
const succeed: <WaitUntil, (promise: Promise<unknown>) => void>(tag: Context.Tag<WaitUntil, (promise: Promise<unknown>) => void>, resource: (promise: Promise<unknown>) => void) => Layer.Layer<...> (+1 overload)

Constructs a layer from the specified value.

@since2.0.0

succeed
(
class WaitUntil
WaitUntil
,
const waitUntil: (promise: Promise<unknown>) => void | undefined

Extends the lifetime of the request handler for the lifetime of the given

Promise

@seehttps://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil

@parampromise The promise to wait for.

@example

import { waitUntil } from '@vercel/functions';
export function GET(request) {
waitUntil(fetch('https://vercel.com'));
return new Response('OK');
}

waitUntil
);
const
const CloudflareWaitUntil: (ctx: ExecutionContext) => Layer.Layer<WaitUntil, never, never>
CloudflareWaitUntil
= (
ctx: ExecutionContext
ctx
:
interface ExecutionContext
ExecutionContext
) =>
import Layer
Layer
.
const succeed: <WaitUntil, (promise: Promise<unknown>) => void>(tag: Context.Tag<WaitUntil, (promise: Promise<unknown>) => void>, resource: (promise: Promise<unknown>) => void) => Layer.Layer<...> (+1 overload)

Constructs a layer from the specified value.

@since2.0.0

succeed
(
class WaitUntil
WaitUntil
,
ctx: ExecutionContext
ctx
.
ExecutionContext.waitUntil(promise: Promise<any>): void
waitUntil
);