|  | 
|  | 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