# jg-nestjs

> NestJS for HTTP APIs: an ApiException, a filter and an interceptor for the jg-api-response envelope, schema validation, CORS, and app bootstrap.

- **Version:** `0.2.0`
- **Runtimes:** Node.js
- **Install:** `npm install jg-nestjs`
- **On npm:** [jg-nestjs](https://www.npmjs.com/package/jg-nestjs)
- **Source:** [packages/jg-nestjs](https://github.com/jesusgraterol/jg-packages/tree/main/packages/jg-nestjs)
- **Recipes:** [jg-nestjs recipes](https://jg-packages.jesusgraterol.dev/packages/jg-nestjs/recipes/index.md)

## Usage

### Answering in one envelope

Register the filter and the interceptor once, and every response uses the `{ success, data, error }` envelope from [jg-api-response](https://jg-packages.jesusgraterol.dev/packages/jg-api-response/):

```ts
import { Module } from '@nestjs/common';
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { ApiExceptionFilter, ApiResponseInterceptor, SchemaValidationPipe } from 'jg-nestjs';

@Module({
  providers: [
    { provide: APP_FILTER, useClass: ApiExceptionFilter },
    { provide: APP_INTERCEPTOR, useClass: ApiResponseInterceptor },
    { provide: APP_PIPE, useClass: SchemaValidationPipe },
  ],
})
export class AppModule {}
```

A handler's result becomes `{ success: true, data, error: null }`, and a 204 sends no body. An error becomes `{ success: false, error: { code, message, data } }`:

| Error                                                                                                 | Status                         | What the client sees                                                  |
| ----------------------------------------------------------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------- |
| `ApiException`                                                                                        | its `status`, 400 to 599       | its code, message, and data                                           |
| Another `Exception`, such as a jg package's                                                           | from `resolveStatus`, else 500 | its code, message, and data                                           |
| Nest's `HttpException`                                                                                | its status                     | `HTTP_EXCEPTION`, with its message                                    |
| A client error Express's middleware built for clients (`expose: true`), such as a body over the limit | its 4xx `statusCode`           | `HTTP_EXCEPTION`, with its message                                    |
| Anything else                                                                                         | from `resolveStatus`, else 500 | `INTERNAL_ERROR` and a generic message, never the error's own message |

Throw an `ApiException` for failures the client should understand:

```ts
import { ApiException } from 'jg-nestjs';

throw new ApiException('The project was not found.', {
  code: 'PROJECT_NOT_FOUND',
  status: 404,
  severity: 'expected',
});
```

### Logging and reporting

The filter logs, through Nest's `Logger`, an error whose throw site set `shouldLog` or `severity: 'critical'`, and any error that is none of an `Exception`, an `HttpException`, and a client error Express's middleware built for clients, with the method, the redacted URL, and the status. A `report` option sends errors to an error tracker, except those with `severity: 'expected'`, the statuses in `ignoredStatuses` (401 and 429 by default), and the codes in `ignoredCodes` (`CORS_ORIGIN_NOT_ALLOWED`, `MAINTENANCE_MODE`, and `IP_BLOCKED` by default). Provide the options as `API_EXCEPTION_FILTER_OPTIONS`; `redactUrl` removes secrets from URLs before either sees them.

### Validating input

`SchemaValidationPipe` validates each parameter that declares a Standard Schema, such as a Zod schema in `@Body({ schema })`. Invalid input gets a 400 with `VALIDATION_FAILED` and up to 32 `{ path, message }` issues as its data, and a valid parameter becomes the schema's output, without `__proto__` keys.

### CORS

```ts
app.enableCors(createCorsOptions({ origins: ['https://app.example.com'] }));
```

It allows the listed origins exactly, their `www.` variants, and requests without an `Origin` header, which servers and command-line clients send, unless `allowOriginless` is false. Any other origin gets a 403 with `CORS_ORIGIN_NOT_ALLOWED`, through the filter, and isn't reported.

### Maintenance mode and IP blocklists

`MaintenanceModeGuard` refuses requests with a 503, `MAINTENANCE_MODE`, while `getState` says maintenance is on, with the state's `message` as the error's data. `IpBlocklistGuard` refuses requests from blocked addresses with a 403, `IP_BLOCKED`, without echoing the address. Each reads its options from its own token, and the app fails to start without them:

```ts
import { Module } from '@nestjs/common';
import {
  IP_BLOCKLIST_GUARD_OPTIONS,
  IpBlocklistGuard,
  MAINTENANCE_MODE_GUARD_OPTIONS,
  MaintenanceModeGuard,
} from 'jg-nestjs';

@Module({
  providers: [
    {
      provide: MAINTENANCE_MODE_GUARD_OPTIONS,
      useValue: { getState: () => ({ enabled: process.env['MAINTENANCE_MODE'] === 'true' }) },
    },
    {
      provide: IP_BLOCKLIST_GUARD_OPTIONS,
      useValue: { isBlocked: (ip: string) => ipBlocklist.has(ip) },
    },
  ],
})
export class AppModule {}
```

`IpBlocklistGuard` checks Express's `request.ip`, which follows `configureHttpApp`'s `trustProxyHops`, or what a `getIp` option returns, after `normalizeIp`: trimmed, lowercased, and `::ffff:203.0.113.7` as `203.0.113.7`. Normalize addresses the same way when adding them to the list. A request without an address goes through. Both guards fail closed: an error from `getState` or `isBlocked` refuses the request, which the filter answers with a 500 and reports. Other contexts, such as microservices, pass.

Run the cheap refusals first, so a flood or a blocked client never reaches authentication: the rate limiter, then `IpBlocklistGuard`, then `MaintenanceModeGuard`, then authentication. Guards in one `@UseGuards(ThrottlerGuard, IpBlocklistGuard, MaintenanceModeGuard, AuthGuard)` run in that order, and so do `APP_GUARD` providers, in the order they're listed. A route that must stay up during maintenance, such as a health check, leaves `MaintenanceModeGuard` out.

### Bootstrapping an app

`configureHttpApp` sets up an Express-based app before it listens, always in this order: JSON and URL-encoded body parsers with `bodyLimit`, the security middleware, the trusted proxy hops, the request log, versioning, and shutdown hooks on SIGINT and SIGTERM:

```ts
import { NestFactory } from '@nestjs/core';
import type { NestExpressApplication } from '@nestjs/platform-express';
import { raw } from 'express';
import helmet from 'helmet';
import { configureHttpApp } from 'jg-nestjs';

const app = await NestFactory.create<NestExpressApplication>(AppModule);
// a webhook that checks a signature needs the exact bytes, so its parser comes first
app.use('/billing/webhooks', raw({ type: 'application/json', limit: '1mb' }));
configureHttpApp(app, { bodyLimit: '1mb', securityMiddleware: helmet(), trustProxyHops: 1 });
await app.listen(3000);
```

The request log writes `METHOD url status Nms` through Nest's `Logger` once each response finishes, at `error` for a 5xx, `warn` for a 4xx, and `log` otherwise, with the client's address, user agent, referer, and response size as structured fields. Its `redactUrl` option cleans the URL and the referer first, and `createRequestLoggingMiddleware` gives the same middleware on its own. The prefix and the levels Nest prints belong to Nest's own logger: `NestFactory.create(AppModule, { logger: new ConsoleLogger({ prefix: 'API', logLevels: ['fatal', 'error', 'warn', 'log'] }) })`.

### Errors

| Code                      | When                                                                                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `VALIDATION_FAILED`       | A parameter didn't match its schema                                                                                                                                      |
| `HTTP_EXCEPTION`          | A handler, guard, or Nest itself threw an `HttpException`, such as a 404, or Express's middleware refused the request with a client error, such as a body over the limit |
| `INTERNAL_ERROR`          | An error the filter doesn't recognize, such as a failed database call, reached it                                                                                        |
| `CORS_ORIGIN_NOT_ALLOWED` | A browser called from an origin the API doesn't allow                                                                                                                    |
| `MAINTENANCE_MODE`        | `MaintenanceModeGuard` refused a request while maintenance is on, with a 503                                                                                             |
| `IP_BLOCKED`              | `IpBlocklistGuard` refused a request from a blocked address, with a 403                                                                                                  |

`ERROR_MESSAGES` holds each code's message.

## <a id="api-reference"></a>jg-nestjs API reference

Every export of `jg-nestjs@0.2.0`, 25 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.

- Functions: [`configureHttpApp`](#api-configureHttpApp), [`createCorsOptions`](#api-createCorsOptions), [`createRequestLoggingMiddleware`](#api-createRequestLoggingMiddleware), [`normalizeIp`](#api-normalizeIp)
- Classes: [`ApiException`](#api-ApiException), [`ApiExceptionFilter`](#api-ApiExceptionFilter), [`ApiResponseInterceptor`](#api-ApiResponseInterceptor), [`IpBlocklistGuard`](#api-IpBlocklistGuard), [`MaintenanceModeGuard`](#api-MaintenanceModeGuard), [`SchemaValidationPipe`](#api-SchemaValidationPipe)
- Constants: [`API_EXCEPTION_FILTER_OPTIONS`](#api-API_EXCEPTION_FILTER_OPTIONS), [`ERROR_MESSAGES`](#api-ERROR_MESSAGES), [`ERRORS`](#api-ERRORS), [`IP_BLOCKLIST_GUARD_OPTIONS`](#api-IP_BLOCKLIST_GUARD_OPTIONS), [`MAINTENANCE_MODE_GUARD_OPTIONS`](#api-MAINTENANCE_MODE_GUARD_OPTIONS)
- Types: [`IApiExceptionFilterOptions`](#api-IApiExceptionFilterOptions), [`IApiExceptionOptions`](#api-IApiExceptionOptions), [`ICorsOptionsInput`](#api-ICorsOptionsInput), [`IErrorReport`](#api-IErrorReport), [`IHttpAppOptions`](#api-IHttpAppOptions), [`IIpBlocklistGuardOptions`](#api-IIpBlocklistGuardOptions), [`IIpBlocklistRequest`](#api-IIpBlocklistRequest), [`IMaintenanceModeGuardOptions`](#api-IMaintenanceModeGuardOptions), [`IMaintenanceModeState`](#api-IMaintenanceModeState), [`IRequestLoggingOptions`](#api-IRequestLoggingOptions)

### <a id="api-functions"></a>Functions

#### <a id="api-configureHttpApp"></a>`configureHttpApp`

*Function*

Configures an Express-based Nest app before it listens, in this order: JSON and URL-encoded body parsers with `bodyLimit`, the security middleware, the trusted proxy hops, the request log, versioning, and shutdown hooks. A route that needs the raw body, such as a webhook that checks a signature, registers its own parser with `app.use` before this call, and the JSON parser then leaves its body alone.

```ts
configureHttpApp(app: NestExpressApplication, options: IHttpAppOptions): void
```

**Parameters**

- `app` `NestExpressApplication`: The app, from `NestFactory.create<NestExpressApplication>`.
- `options` `IHttpAppOptions`: The body limit, and the optional security middleware, proxy hops, request log, versioning, and shutdown signals.

**Example**

```ts
import { VersioningType } from '@nestjs/common';
import type { NestExpressApplication } from '@nestjs/platform-express';
import { configureHttpApp } from 'jg-nestjs';

declare const app: NestExpressApplication;

configureHttpApp(app, {
  bodyLimit: '1mb',
  trustProxyHops: 1,
  versioning: { type: VersioningType.URI },
});
```

#### <a id="api-createCorsOptions"></a>`createCorsOptions`

*Function*

Creates the CORS options for `app.enableCors`, allowing only the listed origins, matched exactly, and by default their `www.` variants and requests without an Origin header. Any other origin is refused with a 403 ApiException, code CORS_ORIGIN_NOT_ALLOWED, which ApiExceptionFilter answers and doesn't report. An origin that isn't a URL throws when the options are created.

```ts
createCorsOptions(input: ICorsOptionsInput): CorsOptions
```

**Parameters**

- `input` `ICorsOptionsInput`: The allowed origins, and whether to allow requests without an origin, `www.` variants, and credentials.

**Returns** `CorsOptions`: The options.

**Example**

```ts
import type { INestApplication } from '@nestjs/common';
import { createCorsOptions } from 'jg-nestjs';

declare const app: INestApplication;

app.enableCors(createCorsOptions({ origins: ['https://app.example.com'] }));
```

#### <a id="api-createRequestLoggingMiddleware"></a>`createRequestLoggingMiddleware`

*Function*

Creates a middleware that logs each request once its response finishes, through Nest's Logger: `METHOD url status Nms`, at `error` for a 5xx, `warn` for a 4xx, and `log` otherwise, with the client's address, user agent, referer, and response size as structured fields when they're known. The URL and the referer go through `redactUrl` first. The prefix and the levels Nest prints are set on Nest's own ConsoleLogger.

```ts
createRequestLoggingMiddleware(
  options?: IRequestLoggingOptions,
): (request: IncomingMessage, response: ServerResponse, next: () => void) => void
```

**Parameters**

- `options?` `IRequestLoggingOptions`: The Logger context, and how to redact URLs. Defaults to `{}`.

**Returns** `(request: IncomingMessage, response: ServerResponse, next: () => void) => void`: The middleware, for `app.use`.

**Example**

```ts
import type { INestApplication } from '@nestjs/common';
import { createRequestLoggingMiddleware } from 'jg-nestjs';

declare const app: INestApplication;

app.use(
  createRequestLoggingMiddleware({
    redactUrl: (url) => url.replace(/token=[^&]+/, 'token=[redacted]'),
  }),
);
```

#### <a id="api-normalizeIp"></a>`normalizeIp`

*Function*

Normalizes an IP address the way IpBlocklistGuard checks it: trimmed, lowercased, and an IPv4 address mapped into IPv6, as `::ffff:203.0.113.7`, as plain IPv4. Normalize addresses the same way before adding them to the blocklist.

```ts
normalizeIp(ip: string): string
```

**Parameters**

- `ip` `string`: The address.

**Returns** `string`: The normalized address.

**Example**

```ts
import { normalizeIp } from 'jg-nestjs';

normalizeIp(' ::FFFF:203.0.113.7 '); // '203.0.113.7'
normalizeIp('2001:DB8::1'); // '2001:db8::1'
```

### <a id="api-classes"></a>Classes

#### <a id="api-ApiException"></a>`ApiException`

*Class*

An Exception that sets the HTTP status of the response ApiExceptionFilter sends: its code, message, and data go to the client in the jg-api-response envelope, so they must be safe to show. The status is an integer from 400 to 599, and 500 when it's missing or anything else.

**Members**

- `code: string` (property)
- `data: IJsonValue | undefined` (property)
- `severity: "expected" | "critical"` (property)
- `shouldLog: boolean` (property)
- `toJSON(): IExceptionJson` (method): Converts the exception to a plain object, which `JSON.stringify` calls.
- `toString(): string` (method): Describes the exception in one line, with its code.
- `status: number` (property): the response's status, from 400 to 599
- `new ApiException(message: string, options: IApiExceptionOptions)` (constructor): Creates an API exception.

**Example**

```ts
import { ApiException } from 'jg-nestjs';

throw new ApiException('The project was not found.', {
  code: 'PROJECT_NOT_FOUND',
  status: 404,
  data: { id: 42 },
  severity: 'expected',
});
```

#### <a id="api-ApiExceptionFilter"></a>`ApiExceptionFilter`

*Class*

Answers every error an HTTP request throws, from a handler, guard, pipe, or middleware, with the jg-api-response envelope. An ApiException sets the status; an Exception sends its code, message, and data; an HttpException sends HTTP_EXCEPTION with its own message, as does a client error Express's middleware built for clients, such as a body over the limit, with its own 4xx; and anything else gets a 500 with INTERNAL_ERROR and a generic message, while the error itself goes only to the logs and the reporter. It logs an error that its throw site marked shouldLog or critical, or that is none of those anticipated kinds, and reports every error except an expected one or one whose status or code is ignored. Errors outside HTTP are rethrown.

**Members**

- `new ApiExceptionFilter(__adapterHost: HttpAdapterHost, options?: IApiExceptionFilterOptions)` (constructor): Creates the filter. Nest injects its arguments.
- `catch(error: unknown, host: ArgumentsHost): void` (method): Answers an error with the envelope, after logging and reporting it.

**Example**

```ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { API_EXCEPTION_FILTER_OPTIONS, ApiExceptionFilter, type IApiExceptionFilterOptions } from 'jg-nestjs';

const filterOptions: IApiExceptionFilterOptions = {
  redactUrl: (url) => url.replace(/token=[^&]+/, 'token=[redacted]'),
};

@Module({
  providers: [
    { provide: API_EXCEPTION_FILTER_OPTIONS, useValue: filterOptions },
    { provide: APP_FILTER, useClass: ApiExceptionFilter },
  ],
})
export class AppModule {}
```

#### <a id="api-ApiResponseInterceptor"></a>`ApiResponseInterceptor`

*Class*

Wraps what each HTTP handler returns in the jg-api-response envelope, `{ success: true, data, error: null }`, which ApiExceptionFilter's error envelope matches. Other contexts, such as microservices, pass through untouched. A handler that answers 204 sends no body, envelope included.

**Members**

- `new ApiResponseInterceptor()` (constructor)
- `intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>` (method): Wraps an HTTP handler's result in the envelope.

**Example**

```ts
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ApiResponseInterceptor } from 'jg-nestjs';

@Module({
  providers: [{ provide: APP_INTERCEPTOR, useClass: ApiResponseInterceptor }],
})
export class AppModule {}
```

#### <a id="api-IpBlocklistGuard"></a>`IpBlocklistGuard`

*Class*

Refuses HTTP requests from blocked IP addresses with a 403, IP_BLOCKED, which carries no data and which the filter doesn't report. It reads the client's address with `getIp`, Express's `request.ip` by default, normalizes it with normalizeIp, and lets a request without one through. It fails closed: an error from `isBlocked` refuses the request. It never echoes the address or changes the request. Other contexts, such as microservices, pass. Its options come from IP_BLOCKLIST_GUARD_OPTIONS, and the app fails to start without them.

**Members**

- `new IpBlocklistGuard(__options: IIpBlocklistGuardOptions)` (constructor): Creates the guard. Nest injects its options.
- `canActivate(context: ExecutionContext): Promise<boolean>` (method): Lets an HTTP request through unless its client's address is blocked.

**Example**

```ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { IP_BLOCKLIST_GUARD_OPTIONS, IpBlocklistGuard } from 'jg-nestjs';

const BLOCKED_IPS = new Set(['203.0.113.7']);

@Module({
  providers: [
    { provide: IP_BLOCKLIST_GUARD_OPTIONS, useValue: { isBlocked: (ip: string) => BLOCKED_IPS.has(ip) } },
    { provide: APP_GUARD, useClass: IpBlocklistGuard },
  ],
})
export class AppModule {}
```

#### <a id="api-MaintenanceModeGuard"></a>`MaintenanceModeGuard`

*Class*

Refuses HTTP requests with a 503 while the API is down for maintenance, reading the state on every request, so turning it on or off needs no restart. The error, MAINTENANCE_MODE, carries the state's message as its data when there is one, and the filter doesn't report it. It fails closed: an error reading the state refuses the request. Other contexts, such as microservices, pass. Its options come from MAINTENANCE_MODE_GUARD_OPTIONS, and the app fails to start without them.

**Members**

- `new MaintenanceModeGuard(__options: IMaintenanceModeGuardOptions)` (constructor): Creates the guard. Nest injects its options.
- `canActivate(context: ExecutionContext): Promise<boolean>` (method): Lets an HTTP request through unless the API is down for maintenance.

**Example**

```ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { MAINTENANCE_MODE_GUARD_OPTIONS, MaintenanceModeGuard } from 'jg-nestjs';

@Module({
  providers: [
    {
      provide: MAINTENANCE_MODE_GUARD_OPTIONS,
      useValue: { getState: () => ({ enabled: process.env['MAINTENANCE_MODE'] === 'true' }) },
    },
    { provide: APP_GUARD, useClass: MaintenanceModeGuard },
  ],
})
export class AppModule {}
```

#### <a id="api-SchemaValidationPipe"></a>`SchemaValidationPipe`

*Class*

Validates each parameter that declares a Standard Schema, such as a Zod schema in `@Body({ schema })`, and answers invalid input with a 400 ApiException: code VALIDATION_FAILED, with up to 32 `{ path, message }` issues as its data. A valid parameter becomes the schema's output, and keys such as `__proto__` are stripped before validation.

**Members**

- `new SchemaValidationPipe()` (constructor)
- `transform(value: unknown, metadata: ArgumentMetadata): Promise<unknown>` (method): Validates a parameter against the schema it declares, if any.

**Example**

```ts
import { Body, Controller, Module, Post } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
import { SchemaValidationPipe } from 'jg-nestjs';
import { z } from 'zod';

const CreateProjectSchema = z.strictObject({ name: z.string().min(1).max(64) });

@Controller('projects')
class ProjectsController {
  @Post()
  create(@Body({ schema: CreateProjectSchema }) body: z.infer<typeof CreateProjectSchema>) {
    return { name: body.name };
  }
}

@Module({
  controllers: [ProjectsController],
  providers: [{ provide: APP_PIPE, useClass: SchemaValidationPipe }],
})
export class AppModule {}
```

### <a id="api-constants"></a>Constants

#### <a id="api-API_EXCEPTION_FILTER_OPTIONS"></a>`API_EXCEPTION_FILTER_OPTIONS`

*Constant*

the token ApiExceptionFilter reads its options from, provided with `useValue`

```ts
const API_EXCEPTION_FILTER_OPTIONS: typeof API_EXCEPTION_FILTER_OPTIONS
```

#### <a id="api-ERROR_MESSAGES"></a>`ERROR_MESSAGES`

*Constant*

The message each error answers with. An HTTP_EXCEPTION answers with the HttpException's or the exposed client error's own message instead, and its entry describes it. INTERNAL_ERROR's is all a client sees of an unexpected error.

```ts
const ERROR_MESSAGES: Record<keyof typeof ERRORS, string>
```

#### <a id="api-ERRORS"></a>`ERRORS`

*Constant*

the codes of every error the package answers with

```ts
const ERRORS: { VALIDATION_FAILED: "VALIDATION_FAILED"; HTTP_EXCEPTION: "HTTP_EXCEPTION"; INTERNAL_ERROR: "INTERNAL_ERROR"; CORS_ORIGIN_NOT_ALLOWED: "CORS_ORIGIN_NOT_ALLOWED"; MAINTENANCE_MODE: "MAINTENANCE_MODE"; IP_BLOCKED: "IP_BLOCKED" }
```

#### <a id="api-IP_BLOCKLIST_GUARD_OPTIONS"></a>`IP_BLOCKLIST_GUARD_OPTIONS`

*Constant*

the token IpBlocklistGuard reads its options from, provided with `useValue` or `useFactory`

```ts
const IP_BLOCKLIST_GUARD_OPTIONS: typeof IP_BLOCKLIST_GUARD_OPTIONS
```

#### <a id="api-MAINTENANCE_MODE_GUARD_OPTIONS"></a>`MAINTENANCE_MODE_GUARD_OPTIONS`

*Constant*

the token MaintenanceModeGuard reads its options from, provided with `useValue` or `useFactory`

```ts
const MAINTENANCE_MODE_GUARD_OPTIONS: typeof MAINTENANCE_MODE_GUARD_OPTIONS
```

### <a id="api-types"></a>Types

#### <a id="api-IApiExceptionFilterOptions"></a>`IApiExceptionFilterOptions`

*Type*

how ApiExceptionFilter reports and logs errors, provided as API_EXCEPTION_FILTER_OPTIONS

```ts
type IApiExceptionFilterOptions = {
    report?: ((report: IErrorReport) => void | Promise<void>) | undefined;
    resolveStatus?: ((error: unknown) => number | undefined) | undefined;
    ignoredStatuses?: readonly number[] | undefined;
    ignoredCodes?: readonly string[] | undefined;
    redactUrl?: ((url: string) => string) | undefined;
};
```

**Members**

- `report: (report: IErrorReport) => void | Promise<void>` (property): sends an error to an error tracker. It runs without delaying the response, and a throw or a rejection is logged
- `resolveStatus: (error: unknown) => number | undefined` (property): gives the status of an error that is none of an ApiException, an HttpException, and an exposed client error, such as one from a library. A value that isn't an integer from 400 to 599 is ignored
- `ignoredStatuses: readonly number[]` (property): the statuses that are never reported. Defaults to 401 and 429
- `ignoredCodes: readonly string[]` (property): the error codes that are never reported. Defaults to CORS_ORIGIN_NOT_ALLOWED, MAINTENANCE_MODE, and IP_BLOCKED
- `redactUrl: (url: string) => string` (property): removes secrets from a URL before it's logged or reported, such as a token in the query. Defaults to leaving it as it is

#### <a id="api-IApiExceptionOptions"></a>`IApiExceptionOptions`

*Type*

the options an ApiException is created with: an Exception's, and the HTTP status to answer with

```ts
type IApiExceptionOptions = IExceptionOptions & {
    status?: number | undefined;
};
```

#### <a id="api-ICorsOptionsInput"></a>`ICorsOptionsInput`

*Type*

what createCorsOptions takes

```ts
type ICorsOptionsInput = {
    origins: readonly string[];
    allowOriginless?: boolean | undefined;
    includeWwwVariants?: boolean | undefined;
    credentials?: boolean | undefined;
};
```

**Members**

- `origins: readonly string[]` (property): the browser origins the API serves, such as `https://app.example.com`
- `allowOriginless: boolean` (property): whether a request without an Origin header, such as one from a server or a command-line client, is allowed. Defaults to true
- `includeWwwVariants: boolean` (property): whether each origin also allows its `www.` variant, or the one without it, with the same scheme and port. Defaults to true
- `credentials: boolean` (property): whether browsers may send cookies and other credentials. Defaults to false

#### <a id="api-IErrorReport"></a>`IErrorReport`

*Type*

what ApiExceptionFilter hands its reporter about one error

```ts
type IErrorReport = {
    error: unknown;
    status: number;
    severity: IExceptionSeverity | undefined;
    request: IncomingMessage;
    url: string;
};
```

**Members**

- `error: unknown` (property): the error a handler, guard, pipe, or middleware threw
- `status: number` (property): the status the response got
- `severity: IExceptionSeverity | undefined` (property): the severity the throw site gave an Exception, if any
- `request: IncomingMessage` (property): the request that failed
- `url: string` (property): the request's URL, already through redactUrl

#### <a id="api-IHttpAppOptions"></a>`IHttpAppOptions`

*Type*

what configureHttpApp takes

```ts
type IHttpAppOptions = {
    bodyLimit: string | number;
    securityMiddleware?: ((request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void) | undefined;
    trustProxyHops?: number | undefined;
    requestLogging?: IRequestLoggingOptions | false | undefined;
    versioning?: VersioningOptions | undefined;
    shutdownSignals?: readonly string[] | undefined;
};
```

**Members**

- `bodyLimit: string | number` (property): the most a JSON or URL-encoded body may hold, such as `'1mb'`, or a number of bytes. A larger body is refused with a 413, which ApiExceptionFilter answers as HTTP_EXCEPTION
- `securityMiddleware: (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void` (property): middleware that runs before the request log, such as `helmet()`
- `trustProxyHops: number` (property): how many proxies in front of the app to trust for the client's address, such as 1 behind one load balancer. Only a positive integer is applied
- `requestLogging: IRequestLoggingOptions | false` (property): the request log's options, or false for none. Defaults to logging every request under the `HTTP` context
- `versioning: VersioningOptions` (property): how the API's routes are versioned, such as `{ type: VersioningType.URI }`
- `shutdownSignals: readonly string[]` (property): the signals that close the app gracefully, running its shutdown hooks. Defaults to SIGINT and SIGTERM

#### <a id="api-IIpBlocklistGuardOptions"></a>`IIpBlocklistGuardOptions`

*Type*

what IpBlocklistGuard takes, through IP_BLOCKLIST_GUARD_OPTIONS

```ts
type IIpBlocklistGuardOptions = {
    isBlocked: (ip: string) => boolean | Promise<boolean>;
    getIp?: ((request: IIpBlocklistRequest) => string | undefined) | undefined;
};
```

**Members**

- `isBlocked: (ip: string) => boolean | Promise<boolean>` (property): resolves to whether the normalized address is blocked, such as with jg-redis's blocklist. An error it throws refuses the request, which the filter answers with a 500 and reports
- `getIp: (request: IIpBlocklistRequest) => string | undefined` (property): reads the client's address from the request. Defaults to Express's `request.ip`, which follows configureHttpApp's trustProxyHops

#### <a id="api-IIpBlocklistRequest"></a>`IIpBlocklistRequest`

*Type*

the request IpBlocklistGuard reads the client's address from, with Express's `ip`

```ts
type IIpBlocklistRequest = IncomingMessage & {
    ip?: string | undefined;
};
```

#### <a id="api-IMaintenanceModeGuardOptions"></a>`IMaintenanceModeGuardOptions`

*Type*

what MaintenanceModeGuard takes, through MAINTENANCE_MODE_GUARD_OPTIONS

```ts
type IMaintenanceModeGuardOptions = {
    getState: () => IMaintenanceModeState | Promise<IMaintenanceModeState>;
};
```

**Members**

- `getState: () => IMaintenanceModeState | Promise<IMaintenanceModeState>` (property): reads the current state on every request, such as from an environment flag or a stored record. An error it throws refuses the request, which the filter answers with a 500 and reports

#### <a id="api-IMaintenanceModeState"></a>`IMaintenanceModeState`

*Type*

whether the API is down for maintenance, and what to tell clients meanwhile

```ts
type IMaintenanceModeState = {
    enabled: boolean;
    message?: string | undefined;
};
```

**Members**

- `enabled: boolean` (property): whether requests are refused
- `message: string` (property): a note for clients, such as when the service is expected back, sent as the error's data

#### <a id="api-IRequestLoggingOptions"></a>`IRequestLoggingOptions`

*Type*

what createRequestLoggingMiddleware takes

```ts
type IRequestLoggingOptions = {
    context?: string | undefined;
    redactUrl?: ((url: string) => string) | undefined;
};
```

**Members**

- `context: string` (property): the Logger context the request log is written under. Defaults to `HTTP`
- `redactUrl: (url: string) => string` (property): removes secrets from a URL or a referer before it's logged, such as a token in the query. Defaults to leaving it as it is
