|
| 1 | +# Configuration Options |
| 2 | + |
| 3 | +You can provide configuration options, primarily hooks for handling errors and |
| 4 | +post-response actions, globally when creating/wrapping a router or on a per-route basis. |
| 5 | +Per-route options override global ones. |
| 6 | + |
| 7 | +## Global Options (`TypedRouterOptions`) |
| 8 | + |
| 9 | +Passed as the optional second argument to [`createRouter`](./create-router) or the |
| 10 | +optional third argument to [`wrapRouter`](./wrap-router). |
| 11 | + |
| 12 | +```typescript |
| 13 | +import express from 'express'; |
| 14 | +import * as t from 'io-ts'; // For Errors type |
| 15 | +import { ApiSpec } from '@api-ts/io-ts-http'; // Conceptual |
| 16 | + |
| 17 | +// Simplified representation of hook signatures |
| 18 | +type DecodeErrorHandler<Req = any, Res = any> = ( |
| 19 | + errors: t.Errors, |
| 20 | + req: express.Request & { decoded?: any }, // May not have decoded fully |
| 21 | + res: express.Response & { sendEncoded?: any }, |
| 22 | + next: express.NextFunction, |
| 23 | +) => void; |
| 24 | + |
| 25 | +type EncodeErrorHandler<Req = any, Res = any> = ( |
| 26 | + error: unknown, // The error during encoding/validation |
| 27 | + req: express.Request & { decoded?: any }, |
| 28 | + res: express.Response & { sendEncoded?: any }, |
| 29 | + next: express.NextFunction, |
| 30 | +) => void; |
| 31 | + |
| 32 | +type AfterResponseHandler<Req = any, Res = any> = ( |
| 33 | + status: number, |
| 34 | + payload: any, // The successfully encoded payload |
| 35 | + req: express.Request & { decoded?: any }, |
| 36 | + res: express.Response & { sendEncoded?: any }, |
| 37 | +) => void; |
| 38 | + |
| 39 | +export type TypedRouterOptions<T extends ApiSpec = any> = { |
| 40 | + onDecodeError?: DecodeErrorHandler; |
| 41 | + onEncodeError?: EncodeErrorHandler; |
| 42 | + afterEncodedResponseSent?: AfterResponseHandler; |
| 43 | +}; |
| 44 | +``` |
| 45 | + |
| 46 | +- `onDecodeError(errors, req, res, next)`: |
| 47 | + - **Triggered**: When using a "checked" route method (such as `.get`) and the incoming |
| 48 | + request fails decoding or validation against the `httpRoute`'s `request` codec. |
| 49 | + - **Purpose**: Allows custom formatting and sending of error responses (such as 400 |
| 50 | + Bad Request). If not provided, a default basic error handler might be used or the |
| 51 | + error might propagate. |
| 52 | + - `errors`: The `t.Errors` array from `io-ts` detailing the validation failures. |
| 53 | + - **Note**: You typically end the response (`res.status(...).json(...).end()`) within |
| 54 | + this handler. Calling `next()` might lead to unexpected behavior. |
| 55 | +- `onEncodeError(error, req, res, next)`: |
| 56 | + - **Triggered**: When `res.sendEncoded(status, payload)` is called, but the provided |
| 57 | + `payload` fails validation against the `httpRoute`'s `response` codec for the given |
| 58 | + `status`. |
| 59 | + - **Purpose**: Handles server-side errors where the application tries to send data |
| 60 | + inconsistent with the API specification. This usually indicates a bug. |
| 61 | + - `error`: The validation error encountered. |
| 62 | + - **Note**: You typically send a 500 Internal Server Error response here and should |
| 63 | + end the response. |
| 64 | +- `afterEncodedResponseSent(status, payload, req, res)`: |
| 65 | + - **Triggered**: After `res.sendEncoded(status, payload)` has successfully validated, |
| 66 | + encoded, and finished sending the response. |
| 67 | + - **Purpose**: Lets you perform side-effects after a successful response, such as |
| 68 | + logging, metrics collection, cleanup, etc. |
| 69 | + - `status`: The status code that was sent. |
| 70 | + - `payload`: The original (pre-encoding) payload object that was sent. |
| 71 | + - **Note**: The response stream (`res`) is likely ended at this point. Don't attempt |
| 72 | + to send further data. |
| 73 | + |
| 74 | +## Per-Route Options (`RouteOptions`) |
| 75 | + |
| 76 | +Pass these as the optional third argument to the route definition methods (such as |
| 77 | +`typedRouter.get(..., ..., routeOptions)`). |
| 78 | + |
| 79 | +```typescript |
| 80 | +// RouteOptions includes the global hooks plus routeAliases |
| 81 | +export type RouteOptions<RouteDef = any> = TypedRouterOptions & { |
| 82 | + routeAliases?: string[]; |
| 83 | +}; |
| 84 | +``` |
| 85 | + |
| 86 | +- `onDecodeError` / `onEncodeError` / `afterEncodedResponseSent`: Same hooks as the |
| 87 | + global options, but these versions apply only to the specific route they're defined on |
| 88 | + and take precedence over any global hooks defined via `createRouter` or `wrapRouter`. |
| 89 | +- `routeAliases` (`string[]`): |
| 90 | + - An array of additional path strings that should also map to this route handler. |
| 91 | + - Uses Express path syntax (such as `/path/:param`). |
| 92 | + - See [`TypedRouter` Object](./typed-router) for more details and caveats regarding |
| 93 | + path parameters. |
| 94 | + |
| 95 | +## Example (Global and Per-Route): |
| 96 | + |
| 97 | +```typescript |
| 98 | +import { createRouter } from '@api-ts/typed-express-router'; |
| 99 | +import { MyApi } from 'my-api-package'; |
| 100 | + |
| 101 | +// Global options |
| 102 | +const typedRouter = createRouter(MyApi, { |
| 103 | + onDecodeError: globalDecodeErrorHandler, |
| 104 | + afterEncodedResponseSent: globalMetricsHandler, |
| 105 | +}); |
| 106 | + |
| 107 | +// Per-route options overriding global and adding alias |
| 108 | +typedRouter.get('some.operation', [myHandler], { |
| 109 | + routeAliases: ['/legacy/path'], |
| 110 | + onDecodeError: specificDecodeErrorHandler, // Overrides globalDecodeErrorHandler for this route |
| 111 | + // afterEncodedResponseSent is inherited from global options |
| 112 | +}); |
| 113 | + |
| 114 | +typedRouter.post('another.operation', [otherHandler], { |
| 115 | + // Inherits onDecodeError from global options |
| 116 | + // No afterEncodedResponseSent hook will run for this route |
| 117 | + afterEncodedResponseSent: undefined, // Explicitly disable global hook for this route |
| 118 | +}); |
| 119 | +``` |
0 commit comments