# Middleware

RPC middleware is a [`Context.Service`](https://effect.plants.sh/services-and-layers/) that **wraps
handler execution** on the server and can optionally install a **client-side
wrapper** for generated clients. A single middleware definition records four
things at the type level:

- **`provides`** — services the middleware injects into downstream handlers, so
  the handler can `yield* CurrentUser` without ever providing it itself.
- **`requires`** — services the middleware implementation itself needs.
- **`error`** — a `Schema` for failures the middleware can raise across the RPC
  boundary (e.g. `Unauthorized`). This is unioned into every guarded RPC's error
  channel.
- **`clientError`** / **`requiredForClient`** — client-only metadata for the
  matching `RpcMiddleware.layerClient` wrapper.

The mental model: a server middleware receives the handler effect plus the
request metadata (`client`, `requestId`, `rpc`, `payload`, `headers`) and
returns a new effect. `provides` **removes** a service from the handler's
requirements; `requires` **adds** the middleware's own dependencies.

## Defining a middleware

Here is a realistic authentication middleware. It declares an `Unauthorized`
error, provides a `CurrentUser` service to handlers, and reads the request
headers to authenticate.

```ts
import { Context, Effect, Schema } from "effect"
import { RpcMiddleware } from "effect/unstable/rpc"

// The service the middleware will inject into every guarded handler.
class CurrentUser extends Context.Service<CurrentUser, {
  readonly id: string
  readonly name: string
}>()("app/CurrentUser") {}

// A failure the middleware can raise. Because it crosses the RPC boundary it
// MUST be a Schema (here a tagged error). It is added to the error channel of
// every RPC the middleware guards.
class Unauthorized extends Schema.TaggedErrorClass<Unauthorized>()(
  "Unauthorized",
  { reason: Schema.String }
) {}

// `RpcMiddleware.Service<Self, Config>()(id, options?)`:
//   - the Config type param carries `provides` / `requires` / `clientError`
//   - the runtime `options` carries the `error` schema and `requiredForClient`
class AuthMiddleware extends RpcMiddleware.Service<AuthMiddleware, {
  provides: CurrentUser
}>()("app/AuthMiddleware", {
  error: Unauthorized,
  // Force typed clients to install a matching client middleware layer.
  requiredForClient: true
}) {}
```

The **server implementation** is the value of the service: a function matching
`RpcMiddleware<Provides, E, Requires>`. It receives the handler `effect` and the
request `options`, and returns the wrapped effect. Provide it like any other
`Context.Service` with `Layer.succeed` (or `Layer.effect` when the
implementation needs setup).

```ts
import { Effect, Layer } from "effect"

// `AuthMiddleware.of` types the function for you. The first argument is the
// handler effect; the second is the request metadata.
export const AuthLive = Layer.succeed(AuthMiddleware)(
  AuthMiddleware.of((effect, { headers }) =>
    Effect.gen(function*() {
      const userId = headers["x-user-id"]
      if (userId === undefined) {
        // The declared `error` schema — surfaces to the caller, typed.
        return yield* Effect.fail(
          new Unauthorized({ reason: "missing x-user-id header" })
        )
      }
      // `provides: CurrentUser` means we satisfy that requirement here. The
      // handler can `yield* CurrentUser` and never has to provide it.
      return yield* Effect.provideService(effect, CurrentUser, {
        id: userId,
        name: headers["x-user-name"] ?? "anonymous"
      })
    })
  )
)
```
**Note:** The handler effect's success value is hidden behind the opaque
`RpcMiddleware.SuccessValue` type, so you can `tap`, `catch`, `ensuring`, or
`provide` around it, but you cannot read or rewrite the concrete result. To
observe successes/defects (logging, metrics) use combinators that don't depend
on the value, like `Effect.tap`, `Effect.tapDefect`, and `Effect.ensuring`.

A middleware that does cross-cutting work without injecting anything is even
smaller. It takes no `provides`/`requires` and (here) declares no error:

```ts
import { Effect, Layer, Metric } from "effect"
import { RpcMiddleware } from "effect/unstable/rpc"

class TimingMiddleware extends RpcMiddleware.Service<TimingMiddleware>()(
  "app/TimingMiddleware"
) {}

const successes = Metric.counter("rpc_success")
const defects = Metric.counter("rpc_defect")

export const TimingLive = Layer.succeed(TimingMiddleware)(
  TimingMiddleware.of((effect) =>
    effect.pipe(
      Effect.tap(Metric.update(successes, 1)),
      Effect.tapDefect(() => Metric.update(defects, 1))
    )
  )
)
```

## Attaching middleware

Attach a middleware to a **single procedure** with `rpc.middleware(M)`, or to a
**whole group** with `group.middleware(M)`. Group-level middleware applies to
every RPC **added to the group so far** — so order matters: add the RPCs that
need it, then call `.middleware`.

```ts
import { Schema } from "effect"
import { Rpc, RpcGroup } from "effect/unstable/rpc"

const User = Schema.Struct({ id: Schema.String, name: Schema.String })

export const UserRpcs = RpcGroup.make(
  // Per-RPC: only this procedure is timed.
  Rpc.make("Ping", { success: Schema.String }).middleware(TimingMiddleware),

  Rpc.make("GetUser", {
    payload: { id: Schema.String },
    success: User
  }),
  Rpc.make("DeleteUser", {
    payload: { id: Schema.String }
  })
  // Group-level: every RPC above is now guarded by AuthMiddleware. The
  // `Unauthorized` error and the provided `CurrentUser` propagate to all of
  // them.
).middleware(AuthMiddleware)
```

Because `AuthMiddleware` declares `provides: CurrentUser`, the handler can read
`CurrentUser` from the environment and that requirement is **removed** from the
handler layer's requirements (`Rpc.ExcludeProvides` does this in the types). You
never have to provide `CurrentUser` to `toLayer`:

```ts
import { Effect } from "effect"

export const UserRpcsLayer = UserRpcs.toLayer(
  Effect.gen(function*() {
    return {
      Ping: () => Effect.succeed("pong"),
      // `CurrentUser` is injected by AuthMiddleware — just pull it.
      GetUser: ({ id }) =>
        Effect.gen(function*() {
          const me = yield* CurrentUser
          return { id, name: me.name }
        }),
      DeleteUser: ({ id }) => Effect.log(`deleting ${id}`)
    }
  })
)
```

Finally provide the middleware implementation alongside the handlers when you
build the server. `RpcServer.layer` reads the group's middleware set, so the
`AuthMiddleware` and `TimingMiddleware` service layers must be in scope:

```ts
import { Layer } from "effect"
import { RpcServer } from "effect/unstable/rpc"

export const RpcLive = RpcServer.layer(UserRpcs).pipe(
  Layer.provide([UserRpcsLayer, AuthLive, TimingLive])
)
```
**Caution:** A middleware that declares `requires` adds those services to the requirements of
every RPC it guards. For example a `DbMiddleware` with `requires: CurrentUser`
can only be attached after a middleware that `provides: CurrentUser`, or you
must provide `CurrentUser` to the server some other way. Plan your middleware
chain so each `requires` is satisfied by an earlier `provides`.

## Client middleware

`RpcMiddleware.layerClient(tag, service)` installs a client-side wrapper
(`RpcMiddlewareClient`) that runs for **outgoing** requests. It receives
`{ rpc, request, next }` and can inspect, rewrite, retry, or short-circuit the
request before calling `next`. The most common use is injecting an auth header
or token.

```ts
import { RpcMiddleware } from "effect/unstable/rpc"
import { Headers } from "effect/unstable/http"

// Match the SAME tag as the server middleware. The client wrapper rewrites the
// outgoing request's headers, then forwards it with `next`.
export const AuthClient = RpcMiddleware.layerClient(
  AuthMiddleware,
  ({ next, request }) =>
    next({
      ...request,
      headers: Headers.set(request.headers, "x-user-id", "u_123")
    })
)
```

The `service` argument can also be an `Effect`, which is run once in the layer's
scope — useful for reading a token service or config first:

```ts
import { Context, Effect } from "effect"
import { RpcMiddleware } from "effect/unstable/rpc"
import { Headers } from "effect/unstable/http"

class TokenStore extends Context.Service<TokenStore, {
  readonly current: Effect.Effect<string>
}>()("app/TokenStore") {}

// The Effect resolves to the client middleware function. Services it requires
// (here `TokenStore`) become requirements of the produced layer.
export const AuthClientFromStore = RpcMiddleware.layerClient(
  AuthMiddleware,
  Effect.gen(function*() {
    const store = yield* TokenStore
    return ({ next, request }) =>
      Effect.gen(function*() {
        const token = yield* store.current
        return yield* next({
          ...request,
          headers: Headers.set(request.headers, "authorization", `Bearer ${token}`)
        })
      })
  })
)
```

Because `AuthMiddleware` was defined with `requiredForClient: true`, the
generated client's type now **requires** the matching client layer (the
`RpcMiddleware.ForClient` marker). Provide `AuthClient` next to the client
protocol or you get a type error:

```ts
import { Layer } from "effect"
import { RpcClient, RpcSerialization } from "effect/unstable/rpc"
import { FetchHttpClient } from "effect/unstable/http"

// The client layer must include the ForClient marker layer because
// `requiredForClient: true`.
const ClientLayer = Layer.empty.pipe(
  Layer.provide(AuthClient), // satisfies ForClient<AuthMiddleware>
  Layer.provide(RpcClient.layerProtocolHttp({ url: "http://localhost:3000/rpc" })),
  Layer.provide(RpcSerialization.layerNdjson),
  Layer.provide(FetchHttpClient.layer)
)
```
**Tip:** `clientError` (set via the `Config` type param) contributes a failure type
**only** to the client-side call's error channel — it is not seen by the server
handler. The middleware's `error` schema, by contrast, is shared with server
failures and flows to both ends. Leave `requiredForClient` unset (default
`false`) when a middleware has no client-side counterpart or the client wrapper
is optional.

## Exhaustive reference

Everything below is exported from `effect/unstable/rpc/RpcMiddleware`.

### RpcMiddleware.Service

Creates a typed middleware service class. The `Config` type parameter declares
`provides` / `requires` / `clientError`; the call options declare the `error`
schema (default `Schema.Never`) and `requiredForClient` (default `false`).

```ts
import { Context, Schema } from "effect"
import { RpcMiddleware } from "effect/unstable/rpc"

class CurrentUser extends Context.Service<CurrentUser, { id: string }>()(
  "CurrentUser"
) {}

class Auth extends RpcMiddleware.Service<Auth, {
  provides: CurrentUser
}>()("Auth", { error: Schema.Never }) {}
// => a Context.Service class usable in `Layer.succeed(Auth)(...)`
```

### RpcMiddleware.layerClient

Provides the client-side implementation for a middleware. Takes the middleware
tag and either a `RpcMiddlewareClient` function or an `Effect` that produces one;
returns a `Layer<ForClient<Id>, ...>`.

```ts
import { RpcMiddleware } from "effect/unstable/rpc"
import { Headers } from "effect/unstable/http"

const AuthClient = RpcMiddleware.layerClient(Auth, ({ next, request }) =>
  next({ ...request, headers: Headers.set(request.headers, "x-user-id", "1") })
)
// => Layer<RpcMiddleware.ForClient<Auth>>
```

### RpcMiddleware.RpcMiddleware

The server middleware function type: `RpcMiddleware<Provides, E, Requires>`. It
takes `(effect, { client, requestId, rpc, payload, headers })` and returns
`Effect<SuccessValue, E | unhandled, Requires | Scope>`.

```ts
import { Effect } from "effect"
import type { RpcMiddleware } from "effect/unstable/rpc"

// A no-op middleware function value.
const passthrough: RpcMiddleware.RpcMiddleware<never, never, never> =
  (effect) => effect
// => returns the handler effect unchanged
```

### RpcMiddleware.RpcMiddlewareClient

The client middleware function type: `RpcMiddlewareClient<E, CE, R>`. It takes
`{ rpc, request, next }` and must eventually call `next(request)`. Use it to
rewrite headers, retry, or short-circuit.

```ts
import { Effect } from "effect"
import type { RpcMiddleware } from "effect/unstable/rpc"

const retryOnce: RpcMiddleware.RpcMiddlewareClient<never, never, never> =
  ({ next, request }) => next(request).pipe(Effect.retry({ times: 1 }))
// => retries the outgoing request once before failing
```

### RpcMiddleware.SuccessValue

The opaque marker type standing in for a handler's concrete success value inside
a middleware. You can wrap the effect but not read the value.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _S = RpcMiddleware.SuccessValue
// => { readonly _: unique symbol } — never inspected directly
```

### RpcMiddleware.ForClient

A marker service requirement indicating a middleware has a client-side
implementation installed. Produced by `layerClient` and required by clients when
`requiredForClient: true`.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _Marker = RpcMiddleware.ForClient<typeof Auth>
// => { readonly _: unique symbol; readonly id: typeof Auth }
```

### RpcMiddleware.ServiceClass

The full class shape returned by `RpcMiddleware.Service`. It is a
`Context.Service` whose value is the server middleware function, carrying the
`error` schema, `requiredForClient` flag, and `~ClientError` marker.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

// `Auth` IS a ServiceClass instance; its static members include:
Auth.error // => the Unauthorized schema (or Schema.Never)
Auth.requiredForClient // => false unless set
```

### RpcMiddleware.Any

An erased server middleware **function** type, accepting any provides/error/
requires — useful for storing heterogeneous middleware functions.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

const store = new Map<string, RpcMiddleware.Any>()
// => holds middleware functions regardless of their concrete types
```

### RpcMiddleware.AnyService

An erased middleware **context key** (`Context.Key`) carrying the metadata
fields (`error`, `requiredForClient`, `~ClientError`). This is what
`rpc.middleware(...)` and `group.middleware(...)` accept.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

const fn = (m: RpcMiddleware.AnyService) => m.requiredForClient
// => reads the flag off any middleware service key
```

### RpcMiddleware.AnyServiceWithProps

Like `AnyService`, but its service value is known to be an
`RpcMiddleware<any, any, any>` function. Used internally by `Rpc.AnyWithProps`'s
`middlewares` set.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _M = RpcMiddleware.AnyServiceWithProps
// => Context.Key<any, RpcMiddleware<any, any, any>> + metadata
```

### RpcMiddleware.Provides

Type-level extractor for the services a middleware provides.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _P = RpcMiddleware.Provides<typeof Auth>
// => CurrentUser
```

### RpcMiddleware.Requires

Type-level extractor for the services a middleware requires.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _R = RpcMiddleware.Requires<typeof Auth>
// => never (Auth requires nothing)
```

### RpcMiddleware.ApplyServices

Applies a middleware's transformation to an environment type `R`: removes what
it `provides`, adds what it `requires`. This is `Exclude<R, Provides> | Requires`.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _Env = RpcMiddleware.ApplyServices<typeof Auth, CurrentUser>
// => never — CurrentUser is removed because Auth provides it
```

### RpcMiddleware.ErrorSchema

Extracts the `Schema` declared as a middleware's `error`.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _E = RpcMiddleware.ErrorSchema<typeof Auth>
// => the Unauthorized schema
```

### RpcMiddleware.Error

Extracts the **decoded** error value type from a middleware's error schema.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _E = RpcMiddleware.Error<typeof Auth>
// => Unauthorized
```

### RpcMiddleware.ErrorServicesEncode / ErrorServicesDecode

Extract the schema services needed to **encode** (server side) or **decode**
(client side) the middleware's error. These services stay in the generated RPC
environments — see the gotcha below.

```ts
import type { RpcMiddleware } from "effect/unstable/rpc"

type _Enc = RpcMiddleware.ErrorServicesEncode<typeof Auth>
type _Dec = RpcMiddleware.ErrorServicesDecode<typeof Auth>
// => never for a plain tagged error with no service-backed fields
```

### RpcMiddleware.TypeId

The literal type id `"~effect/rpc/RpcMiddleware"` used to identify and inspect
middleware service classes at runtime.

```ts
import { RpcMiddleware } from "effect/unstable/rpc"

RpcMiddleware.TypeId
// => "~effect/rpc/RpcMiddleware"
```
**Caution:** Middleware errors that cross the RPC boundary **must** be declared with a
`Schema`. Because the error is serialized like any other RPC value, the schema's
encoding services (server) and decoding services (client) become part of the
generated RPC environments — exactly like payload and success schemas. Keep
middleware error schemas service-free (plain tagged errors) unless you are
prepared to provide those schema services to the server and client.

## Related

- [Defining RPCs](https://effect.plants.sh/rpc/defining-rpcs/) — the `Rpc.make` / `RpcGroup.make`
  building blocks middleware attaches to.
- [Client and server](https://effect.plants.sh/rpc/client-and-server/) — implementing handlers,
  serving, and the typed client that client middleware wraps.