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

Tags:

import {
  import HttpAppHttpApp,
  import HttpServerRequestHttpServerRequest,
  import HttpServerResponseHttpServerResponse,
  import UrlParamsUrlParams,
} from "@effect/platform";
import { import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
Effect
, import LayerLayer, import ManagedRuntimeManagedRuntime } from "effect";
// your main layer representing all of the services your handler needs (db, auth, etc.) declare const const mainLive: Layer.Layer<void, never, never>mainLive: import LayerLayer.interface Layer<in ROut, out E = never, out RIn = never>
@since2.0.0@categorymodels@since2.0.0
Layer
<void>;
const const managedRuntime: ManagedRuntime.ManagedRuntime<void, never>managedRuntime = import ManagedRuntimeManagedRuntime.const make: <void, never>(layer: Layer.Layer<void, never, never>, memoMap?: Layer.MemoMap | undefined) => ManagedRuntime.ManagedRuntime<void, never>
Convert a Layer into an ManagedRuntime, that can be used to run Effect's using your services.
@since2.0.0@categoryruntime class@example```ts 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<void, never, never>mainLive);
const const runtime: Runtime<void>runtime = await const managedRuntime: ManagedRuntime.ManagedRuntime<void, never>managedRuntime.ManagedRuntime<void, never>.runtime: () => Promise<Runtime<void>>runtime(); // everything interesting happens in this effect- it consumes the request from context anywhere and ultimately produces some http response declare const const effectHandler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest>effectHandler: import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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@categoryModels@since2.0.0
Effect
<
import HttpServerResponseHttpServerResponse.HttpServerResponse, never, import HttpServerRequestHttpServerRequest.HttpServerRequest >; // example: declare const
const doThing: (id: string) => Effect.Effect<{
    readonly _tag: "user";
}, Error>
doThing
: (
id: stringid: string, ) => import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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@categoryModels@since2.0.0
Effect
<{ readonly _tag: "user"_tag: "user" }, Error>;
const const exampleEffectHandler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest>exampleEffectHandler = import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
Effect
.const gen: <YieldWrap<Tag<HttpServerRequest.HttpServerRequest, HttpServerRequest.HttpServerRequest>> | YieldWrap<Effect.Effect<UrlParams.UrlParams, RequestError, never>> | YieldWrap<...> | 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```ts 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@categoryCreating Effects
gen
(function* () {
const const request: HttpServerRequest.HttpServerRequestrequest = yield* import HttpServerRequestHttpServerRequest.const HttpServerRequest: Tag<HttpServerRequest.HttpServerRequest, HttpServerRequest.HttpServerRequest>
@since1.0.0@categorymodels@since1.0.0@categorycontext
HttpServerRequest
;
const const params: UrlParams.UrlParamsparams = yield* const request: HttpServerRequest.HttpServerRequestrequest.HttpIncomingMessage<RequestError>.urlParamsBody: Effect.Effect<UrlParams.UrlParams, RequestError, never>urlParamsBody; const const id: stringid = yield* import UrlParamsUrlParams.const getFirst: (self: UrlParams.UrlParams, key: string) => Option<string> (+1 overload)
@since1.0.0@categorycombinators
getFirst
(const params: UrlParams.UrlParamsparams, "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@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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.
@see{@link map} for a version that operates on the success channel.@see{@link mapBoth} for a version that operates on both channels.@see{@link orElseFail} if you want to replace the error with a new one.@example```ts 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) ) ```@since2.0.0@categoryMapping
mapError
(() => new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("no id param found")),
); const
const data: {
    readonly _tag: "user";
}
data
= yield*
const doThing: (id: string) => Effect.Effect<{
    readonly _tag: "user";
}, Error>
doThing
(const id: stringid);
return yield* import HttpServerResponseHttpServerResponse.const json: (body: unknown, options?: HttpServerResponse.Options.WithContentType | undefined) => Effect.Effect<HttpServerResponse.HttpServerResponse, HttpBodyError>
@since1.0.0@categoryconstructors
json
(
const data: {
    readonly _tag: "user";
}
data
);
}).Pipeable.pipe<Effect.Effect<HttpServerResponse.HttpServerResponse, Error | RequestError | HttpBodyError, HttpServerRequest.HttpServerRequest>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)pipe( // probably want some kind of catch all (defects too) // although the `toWebHandlerRuntime` already does quite a bit for you: https://github.com/Effect-TS/effect/blob/main/packages/platform/src/Http/App.ts#L134 import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
Effect
.const catchAllCause: <Error | RequestError | HttpBodyError, HttpServerResponse.HttpServerResponse, never, never>(f: (cause: Cause<Error | RequestError | HttpBodyError>) => Effect.Effect<...>) => <A, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)
Handles both recoverable and unrecoverable errors by providing a recovery effect. **When to Use** The `catchAllCause` function allows you to handle all errors, including unrecoverable defects, by providing a recovery effect. The recovery logic is based on the `Cause` of the error, which provides detailed information about the failure. **When to Recover from Defects** Defects are unexpected errors that typically shouldn't be recovered from, as they often indicate serious issues. However, in some cases, such as dynamically loaded plugins, controlled recovery might be needed.
@example```ts // Title: Recovering from All Errors import { Cause, Effect } from "effect" // Define an effect that may fail with a recoverable or unrecoverable error const program = Effect.fail("Something went wrong!") // Recover from all errors by examining the cause const recovered = program.pipe( Effect.catchAllCause((cause) => Cause.isFailType(cause) ? Effect.succeed("Recovered from a regular error") : Effect.succeed("Recovered from a defect") ) ) // Effect.runPromise(recovered).then(console.log) // Output: "Recovered from a regular error" ```@since2.0.0@categoryError handling
catchAllCause
((e: Cause<Error | RequestError | HttpBodyError>e) =>
import HttpServerResponseHttpServerResponse.const empty: (options?: HttpServerResponse.Options.WithContent | undefined) => HttpServerResponse.HttpServerResponse
@since1.0.0@categoryconstructors
empty
().Pipeable.pipe<HttpServerResponse.HttpServerResponse, HttpServerResponse.HttpServerResponse>(this: HttpServerResponse.HttpServerResponse, ab: (_: HttpServerResponse.HttpServerResponse) => HttpServerResponse.HttpServerResponse): HttpServerResponse.HttpServerResponse (+21 overloads)pipe(import HttpServerResponseHttpServerResponse.const setStatus: (status: number, statusText?: string | undefined) => (self: HttpServerResponse.HttpServerResponse) => HttpServerResponse.HttpServerResponse (+1 overload)
@since1.0.0@categorycombinators
setStatus
(500)),
), ); const const webHandler: (request: Request, context?: Context<never> | undefined) => Promise<Response>webHandler = import HttpAppHttpApp.const toWebHandlerRuntime: <void>(runtime: Runtime<void>) => <E>(self: HttpApp.Default<E, void | Scope>, middleware?: HttpMiddleware | undefined) => (request: Request, context?: Context<never> | undefined) => Promise<Response>
@since1.0.0@categoryconversions
toWebHandlerRuntime
(const runtime: Runtime<void>runtime)(const effectHandler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest>effectHandler);
export const const GET: (req: Request) => Promise<Response>GET: (req: Requestreq: Request) => interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<Response> = const webHandler: (request: Request, context?: Context<never> | undefined) => Promise<Response>webHandler;