# API reference: Middleware & helpers

Built-in middleware, the `every`/`some`/`except` composition primitives, typed dependencies, config validation, structured logging, the startup banner, and connection-info helpers. Everything on this page is exported from the root `@daloyjs/core` barrel. See the [API reference overview](/docs/api-reference) for the full module map.

**Diagram: A typical middleware stack**

- **Platform hygiene** - applied by secureDefaults - [requestId(), secureHeaders()]
- **Traffic shaping** - [rateLimit(), loadShedding(), compression()]
- **Authentication & access** - [bearerAuth(), jwk(), requireScopes(), csrf()]
- **Route handler** - typed ctx, typed response

Hooks compose top to bottom. every(), some(), and except() combine layers; defineDependency() injects per-request values into ctx.state.

## Built-in middleware

OAuth2 role Resource Server

Your API's job. DaloyJS is built for this.

Accepts a token that somebody else issued, verifies its signature and claims, and decides whether this caller may touch this resource. Verifying is not the same as issuing: you always own verification, even when a provider owns the login.

The authentication middleware on this page (`bearerAuth()`, `jwk()`, `requireScopes()`, `basicAuth()`) all sit on the receiving end of a token. They read a credential that something else issued and decide whether this request continues. None of them issue credentials, and `basicAuth()` in particular is for machine-to-machine and internal endpoints, not for logging in end users.

[Auth architecture: where DaloyJS fits in OAuth2 & OpenID Connect](/docs/auth/architecture) explains all three roles and the two deployment shapes we recommend.

```ts
requestId(opts?: RequestIdOptions): Hooks
secureHeaders(opts?: SecureHeadersOptions): Hooks
cors(opts: CorsOptions): Hooks
rateLimit(opts: RateLimitOptions): Hooks
loginThrottle(opts?: LoginThrottleOptions): Hooks
timing(headerName?: string): Hooks
compression(opts?: CompressionOptions): Hooks
bearerAuth(opts: BearerAuthOptions): Hooks
basicAuth(opts: BasicAuthOptions): Hooks
markAuthHook(hooks: Hooks): Hooks
const AUTH_HOOK_MARKER: unique symbol  // stamped by built-ins and markAuthHook()
csrf(opts?: CsrfOptions): Hooks
fetchMetadata(opts?: FetchMetadataOptions): Hooks   // Sec-Fetch-Site/Mode/Dest enforcement
requireScopes(scopes: string | string[]
            | { all?: string[]; any?: string[] }): Hooks
ipRestriction(opts: IpRestrictionOptions): Hooks    // CIDR allow/deny
loadShedding(opts?: LoadSheddingOptions): Hooks
etag(opts?: ETagOptions): Hooks                      // 304 + Set-Cookie / Cache-Control skip

interface RateLimitOptions {
  windowMs: number;
  max: number;
  keyGenerator?: (ctx: RateLimitContext) => string; // may run on an early auth rejection
  store?: RateLimitStore;          // default in-memory; use redisRateLimitStore for clusters
  trustProxyHeaders?: boolean;     // rightmost XFF; spoofable if origin is reachable
  trustedHops?: number;            // multi-hop chain length (implies trust)
  trustedProxies?: readonly string[]; // CIDR allowlist of proxy peers (peer-verified trust)
  retryAfter?: boolean;
  groupId?: string;
}

interface BearerAuthOptions {
  validate: (token: string) => boolean | Promise<boolean>;  // static check; token only
  verify?: BearerAuthVerifyHook;    // (token, ctx) => boolean | void; per-request revalidation
  realm?: string;
}
```

## Composition primitives

```ts
every(...layers: Hooks[]): Hooks      // run every lifecycle phase in order
some (...layers: Hooks[]): Hooks      // pass the first successful preBody/beforeHandle auth gate
except(when: ExceptPredicate, hooks: Hooks): Hooks  // exempt paths from preBody + beforeHandle gates

type ExceptPredicate =
  | string                            // path glob: "*" = one segment, "**" = any suffix
  | string[]                          // any-of globs
  | ((ctx) => boolean | Promise<boolean>);
```

## Dependencies (typed DI chain)

```ts
defineDependency<TName, TValue, TStateKey>(opts: {
  name: TName;
  dependsOn?: readonly string[];      // refuses cycles at registration
  stateKey?: TStateKey;
  resolve: (ctx) => TValue | Promise<TValue>;
}): DependencyHooks   // per-request cached; runs once per dependency per request
```

## Configuration

```ts
defineConfig<S extends StandardSchemaV1>(opts: {
  schema: S;
  source?: ConfigSource;               // default: "env" (process.env)
  stderr?: { write(chunk: string): void } | false;
}): Promise<StandardSchemaV1.InferOutput<S>>;
  // Async. Validates once at startup; throws ConfigValidationError on missing/invalid values.

type ConfigSource =
  | "env"
  | { kind: "env";    env: Record<string, string | undefined> }
  | { kind: "file";   path: string; parse?: (text: string) => unknown }
  | { kind: "object"; data: Record<string, unknown> }
  | { kind: "custom"; resolve: () => Promise<Record<string, unknown>> };

class ConfigValidationError extends Error {
  readonly issues: ReadonlyArray<{ key: string; message: string }>;
}
```

## Logging

```ts
type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";

createLogger(opts?: ConsoleLoggerOptions): Logger;
const noopLogger: Logger;
const DEFAULT_REDACT_KEYS: ReadonlyArray<string>;  // password, token, secret, authorization, ...

interface ConsoleLoggerOptions {
  level?: LogLevel;
  bindings?: Record<string, unknown>;
  write?: (line: string) => void;
  redact?: LoggerRedactionOptions;     // { keys?, replacer? }
}

interface Logger {
  trace(obj?, msg?): void;
  debug(obj?, msg?): void;
  info (obj?, msg?): void;
  warn (obj?, msg?): void;
  error(obj?, msg?): void;
  fatal(obj?, msg?): void;
  child(bindings: Record<string, unknown>): Logger;
}
```

## Startup banner

```ts
interface StartupBannerLink { label: string; url: string }
interface StartupBannerOptions {
  name?: string;        // default: "DaloyJS"
  version?: string;
  url: string;
  runtime?: string;     // e.g. "Node.js", "Bun"
  links?: StartupBannerLink[];
  color?: boolean;
  ascii?: boolean;
}

formatStartupBanner(opts: StartupBannerOptions): string;
printStartupBanner(opts: StartupBannerOptions): void;
```

## Connection info & proxy posture

```ts
type BehindProxyConfig = "none" | "loopback" | { hops: number } | { cidrs: readonly string[] };
interface ConnInfo { remoteAddress?: string; remotePort?: number; tls?: boolean }

getConnInfo(req: Request): ConnInfo | undefined;
setConnInfo(req: Request, info: ConnInfo): void;   // adapter helper
assertBehindProxy(cfg: BehindProxyConfig | undefined): void;
resolveClientIp(ctx, cfg?: BehindProxyConfig): string | undefined;
readRemoteAddress(ctx): string | undefined;
readRemotePort(ctx): number | undefined;
pickForwardedForByHops(header: string, hops: number): string | undefined;

// Spoof-resistant client IP: reads `hops` entries from the RIGHT of
// X-Forwarded-For (the slots your own proxy chain wrote). Falls back to
// X-Real-IP only when hops === 1; past that a chain shorter than the
// declaration yields undefined rather than a caller-settable header.
resolveForwardedClientIp(req: Request, hops?: number): string | undefined;
```

## Subdomains (Public-Suffix-aware)

```ts
subdomains(hostname: string, opts?: SubdomainsOptions): SubdomainsResult;

interface SubdomainsResult {
  subdomain: string | undefined;       // e.g. "api" for "api.example.co.uk"
  registrableDomain: string | undefined;
  publicSuffix: string | undefined;
}

const PSL_SNAPSHOT_DATE: string;       // ISO date of the bundled PSL snapshot
const MAX_SNAPSHOT_AGE_DAYS: number;   // refuses to use a stale snapshot
const PSL_PUBLIC_SUFFIXES: ReadonlySet<string>;
```

Next up: [security & auth helpers](/docs/api-reference/security).

---

Source: https://daloyjs.dev/docs/api-reference/middleware