Skip to content

Commit ae5f7e4

Browse files
docs(typed-express-router): Refactor documentation into Diátaxis Reference section
This commit significantly restructures and enhances the documentation for the `@api-ts/typed-express-router` package by adopting the Diátaxis framework and creating a dedicated, multi-file Reference section. **Motivation:** The previous documentation, primarily located in the README, mixed introductory guides with technical details. This made it difficult for users to quickly find precise information about specific functions, types, or configuration options without reading through narrative content. A formal Reference section, following Diátaxis principles, provides a much clearer, more accessible, and maintainable structure for technical documentation. **Changes Implemented:** 1. **Diátaxis Reference Structure:** Established a dedicated directory (`docs/reference/typed-express-router/`) for technical reference material, separating it from other documentation types (like Tutorials or How-To Guides, which might exist elsewhere). 2. **Component-Focused File Organization:** Divided the reference documentation into distinct MDX files, each dedicated to a specific component or concept of the library: * `index.mdx`: Serves as the entry point and overview for the typed-express-router reference section. * `create-router.mdx`: Details the `createRouter` function. * `wrap-router.mdx`: Details the `wrapRouter` function. * `typed-router.mdx`: Describes the `TypedRouter` object itself, including its typed route methods (e.g., `.get`, `.getUnchecked`) and middleware handling (`.use`). * `request-response.mdx`: Explains the augmented `req` (with `req.decoded`) and `res` (with `res.sendEncoded`) objects provided to route handlers. * `configuration.mdx`: Documents the global (`TypedRouterOptions`) and per-route (`RouteOptions`) configuration, including error hooks (`onDecodeError`, `onEncodeError`), the post-response hook (`afterEncodedResponseSent`), and `routeAliases`. * `typed-request-handler.mdx`: Describes the `TypedRequestHandler` helper type for improved type safety in handler definitions. 3. **Content Migration & Refinement:** Technical information previously embedded in the README's "Usage" section has been extracted, expanded, and reformatted into precise reference documentation within the new structure. This includes: * Clear function/type signatures. * Detailed descriptions of parameters and return values. * Explicit explanations of component behavior (e.g., checked vs. unchecked routes, hook triggering conditions). * Code examples focused on illustrating the specific component being documented. **Benefits (Impact on Users & Maintainers):** * **Improved Discoverability:** Users needing technical details about `createRouter`, `req.decoded`, configuration hooks, or other specific features can now find dedicated pages quickly. * **Enhanced Clarity & Precision:** Technical specifications are presented directly and unambiguously, separate from introductory narratives. * **Better Maintainability:** Isolating documentation for each component makes future updates easier and less error-prone. * **Standardized Structure:** Adopts a recognized documentation pattern (Diátaxis), improving consistency potentially across multiple related packages. * **Increased Comprehensiveness:** Provides more explicit detail on the library's features and types than previously available in the consolidated README. This refactoring represents a significant improvement in the quality, usability, and maintainability of the `@api-ts/typed-express-router` technical documentation.
1 parent 1c2b000 commit ae5f7e4

File tree

8 files changed

+595
-0
lines changed

8 files changed

+595
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# `createRouter`
2+
3+
Creates a new Express Router instance that's typed according to a provided
4+
`@api-ts/io-ts-http` API specification.
5+
6+
**Signature:**
7+
8+
```typescript
9+
import express from 'express';
10+
import { ApiSpec } from '@api-ts/io-ts-http'; // Conceptual import
11+
import { TypedRouter } from './typed-router'; // Conceptual import of the return type
12+
import { TypedRouterOptions } from './configuration'; // Conceptual import
13+
14+
declare function createRouter<T extends ApiSpec>(
15+
apiSpec: T,
16+
options?: TypedRouterOptions<T>, // Global options/hooks
17+
): TypedRouter<T>; // Returns the specialized router object
18+
```
19+
20+
**Parameters:**
21+
22+
- `apiSpec` (`ApiSpec`): An API specification object created using
23+
`@api-ts/io-ts-http`'s `apiSpec` function. This defines the routes that you can attach
24+
to this router.
25+
- `options` (Optional `TypedRouterOptions<T>`): An optional object containing global
26+
configuration hooks for error handling and post-response actions. See
27+
[Configuration Options](./configuration) for details.
28+
29+
**Return Value:**
30+
31+
- `TypedRouter<T>`: A specialized Express Router instance. This object has methods (like
32+
`.get`, `.post`) that accept operation names from the `apiSpec` and provide augmented
33+
`req` and `res` objects to the handlers. See [`TypedRouter` Object](./typed-router)
34+
for details.
35+
36+
**Usage Example:**
37+
38+
```typescript
39+
import express from 'express';
40+
import { createRouter } from '@api-ts/typed-express-router';
41+
import { MyApi } from 'my-api-package'; // Your apiSpec import
42+
43+
const app = express();
44+
const typedRouter = createRouter(MyApi, {
45+
// Optional global configuration
46+
onDecodeError: (errs, req, res, next) => {
47+
res.status(400).json({ error: 'Invalid request format', details: errs });
48+
},
49+
});
50+
51+
app.use('/api', typedRouter); // Mount the typed router
52+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Reference: @api-ts/typed-express-router
2+
3+
This section provides detailed technical descriptions of the functions, objects, types,
4+
and configuration options available in the `@api-ts/typed-express-router` package. Use
5+
this reference to understand the specific parameters, return values, and behavior of its
6+
components when integrating `@api-ts/io-ts-http` specifications with Express.
7+
8+
## Components
9+
10+
- [**`createRouter`**](./create-router): Creates a new, typed Express Router instance
11+
linked to an API specification.
12+
- [**`wrapRouter`**](./wrap-router): Wraps an existing Express Router instance, linking
13+
it to an API specification.
14+
- [**`TypedRouter` Object**](./typed-router): Describes the router object returned by
15+
`createRouter` and `wrapRouter`, detailing its route definition methods (`.get`,
16+
`.post`, `.getUnchecked`, etc.) and middleware usage (`.use`).
17+
- [**Augmented Request & Response**](./request-response): Explains the properties and
18+
methods added to the standard Express `req` (`req.decoded`) and `res`
19+
(`res.sendEncoded`) objects within typed route handlers.
20+
- [**Configuration Options**](./configuration): Details the configurable options for
21+
error handling (`onDecodeError`, `onEncodeError`), post-response actions
22+
(`afterEncodedResponseSent`), and route aliasing (`routeAliases`).
23+
- [**`TypedRequestHandler` Type**](./typed-request-handler): Describes the TypeScript
24+
helper type for defining route handlers with correctly inferred augmented request and
25+
response types.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Augmented Request & Response
2+
3+
When you use route handlers registered via a [`TypedRouter`](./typed-router) object
4+
(using methods like `.get`, `.post`, `.getUnchecked`, etc.), the standard Express
5+
`request` and `response` objects are augmented with additional properties and methods
6+
related to the API specification.
7+
8+
## Augmented Request (`req`)
9+
10+
The Express `request` object (`req`) passed to typed route handlers includes an
11+
additional property:
12+
13+
### `req.decoded`
14+
15+
- **Type (Checked Routes):** `DecodedRequest`
16+
- In handlers attached using the "checked" methods (such as `typedRouter.get(...)`),
17+
`req.decoded` holds the successfully decoded and validated request data. Its
18+
TypeScript type is inferred directly from the `request` codec defined in the
19+
corresponding `httpRoute` of the `ApiSpec`. This object contains the flattened
20+
combination of path parameters, query parameters, headers, and body properties as
21+
defined by the `httpRequest` codec used in the spec.
22+
- **Type (Unchecked Routes & Middleware):** `Either<t.Errors, DecodedRequest>`
23+
- In handlers attached using the "unchecked" methods (such as
24+
`typedRouter.getUnchecked(...)`) or in middleware added via `typedRouter.use(...)`,
25+
`req.decoded` holds the raw result of the decoding attempt from `io-ts`. This is an
26+
`Either` type from the `fp-ts` library.
27+
- Use `E.isRight(req.decoded)` to check if decoding was successful. If true,
28+
`req.decoded.right` contains the `DecodedRequest`.
29+
- Use `E.isLeft(req.decoded)` to check if decoding failed. If true, `req.decoded.left`
30+
contains the `t.Errors` object detailing the validation failures.
31+
32+
## Augmented Response (`res`)
33+
34+
The Express `response` object (`res`) passed to typed route handlers includes an
35+
additional method:
36+
37+
### `res.sendEncoded(status, payload)`
38+
39+
Use this method instead of `res.json()` or `res.send()` when sending responses that
40+
should conform to the API specification.
41+
42+
**Parameters:**
43+
44+
- `status` (`number`): The HTTP status code for the response. This status code **must**
45+
be a key defined in the `response` object of the `httpRoute` associated with the
46+
current route in the `ApiSpec`.
47+
- `payload` (`any`): The data to be sent as the response body.
48+
49+
**Behavior:**
50+
51+
1. **Type Checking:** Validates that the provided `payload` conforms to the `io-ts`
52+
codec associated with the given `status` in the `httpRoute`'s `response` definition.
53+
2. **Encoding:** Encodes the `payload` using the same `io-ts` codec. This handles
54+
necessary transformations (such as converting a `Date` object to an ISO string if
55+
using `DateFromISOString`, or a `bigint` to a string if using `BigIntFromString`).
56+
3. **Sending Response:** Sets the response status code to `status`, sets the
57+
`Content-Type` header to `application/json`, and sends the JSON-stringified encoded
58+
payload as the response body.
59+
4. **Error Handling:** If the `payload` fails validation against the codec for the
60+
specified `status`, calls the `onEncodeError` hook (route-specific or global).
61+
5. **Post-Response Hook:** After the response has been successfully sent, calls the
62+
`afterEncodedResponseSent` hook (route-specific or global).
63+
64+
**Example:**
65+
66+
```typescript
67+
import { TypedRequestHandler } from '@api-ts/typed-express-router';
68+
import { MyApi } from 'my-api-package';
69+
70+
// Assuming 'api.v1.getUser' route expects a { user: UserType } payload for status 200
71+
const getUserHandler: TypedRequestHandler<MyApi['api.v1.getUser']['get']> = (
72+
req,
73+
res,
74+
) => {
75+
const userId = req.decoded.userId; // Access decoded request data
76+
const user = findUserById(userId);
77+
78+
if (!user) {
79+
// Assuming 404 is defined in the spec with an error object payload
80+
res.sendEncoded(404, { error: 'User not found' });
81+
return;
82+
}
83+
84+
// Send status 200 with the UserType payload
85+
// 'sendEncoded' ensures 'user' matches the spec for status 200
86+
res.sendEncoded(200, { user: user });
87+
};
88+
```
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# `TypedRequestHandler` Type
2+
3+
A TypeScript helper type provided by `@api-ts/typed-express-router` to help you define
4+
Express route handlers with correctly inferred types for the augmented `request` and
5+
`response` objects.
6+
7+
**Purpose:**
8+
9+
When defining handlers for "checked" routes (such as using `typedRouter.get(...)`), this
10+
type automatically infers:
11+
12+
- The type of `req.decoded` based on the `request` codec of the specific `httpRoute`
13+
linked via the `operationName`.
14+
- The type signature of `res.sendEncoded`, ensuring the `payload` type is checked
15+
against the appropriate `response` codec for the given `status` code from the
16+
`httpRoute`.
17+
18+
**Definition (Conceptual):**
19+
20+
```typescript
21+
import express from 'express';
22+
import { HttpRoute } from '@api-ts/io-ts-http'; // Conceptual import
23+
import * as t from 'io-ts'; // For TypeOf and OutputOf
24+
25+
// RouteDefinition represents the specific httpRoute object from the ApiSpec
26+
// e.g., MyApi['my.operation']['get']
27+
type RouteDefinition = HttpRoute<any, any>;
28+
29+
// Extracts the decoded request type from the route's request codec
30+
type DecodedRequest<R extends RouteDefinition> = t.TypeOf<R['request']>;
31+
32+
// Represents the augmented response object
33+
type TypedResponse<R extends RouteDefinition> = express.Response & {
34+
sendEncoded<Status extends keyof R['response'] & number>( // Status must be a key in response obj
35+
status: Status,
36+
// Payload type must match the codec for the given status
37+
payload: t.TypeOf<R['response'][Status]>,
38+
): TypedResponse<R>; // Allows chaining like standard Express res
39+
};
40+
41+
export type TypedRequestHandler<RouteDef extends RouteDefinition = any> = (
42+
req: express.Request & { decoded: DecodedRequest<RouteDef> },
43+
res: TypedResponse<RouteDef>,
44+
next: express.NextFunction,
45+
) => void | Promise<void>; // Allow async handlers
46+
```
47+
48+
(Note: The actual implementation may involve more complex generic constraints)
49+
50+
**Usage:** Import the type and use it when defining your handler functions. Provide the
51+
specific `httpRoute` definition type from your imported `ApiSpec` as the generic
52+
argument.
53+
54+
```typescript
55+
import express from 'express';
56+
import { TypedRequestHandler } from '@api-ts/typed-express-router';
57+
import { MyApi } from 'my-api-package'; // Your generated ApiSpec object
58+
59+
// Define the type for the specific route handler
60+
type HelloWorldRouteHandler = TypedRequestHandler<MyApi['hello.world']['get']>;
61+
// ^------------------------------^
62+
// Generic argument points to the specific httpRoute definition in the spec
63+
64+
const handler: HelloWorldRouteHandler = (req, res, next) => {
65+
// req.decoded is strongly typed based on MyApi['hello.world']['get'].request
66+
const name = req.decoded.name || 'World';
67+
68+
// Payload for status 200 is type-checked against MyApi['hello.world']['get'].response[200]
69+
res.sendEncoded(200, { message: `Hello, ${name}!` });
70+
71+
// If status 400 was defined in the spec with a different payload type:
72+
// const errorPayload = { error: 'Missing name' };
73+
// res.sendEncoded(400, errorPayload); // This would also be type-checked
74+
};
75+
76+
// Use the handler
77+
// typedRouter.get('hello.world', [handler]);
78+
```
79+
80+
Using `TypedRequestHandler` significantly improves your developer experience by
81+
providing type safety and autocompletion for the decoded request properties and the
82+
`sendEncoded` payload within route handlers.

0 commit comments

Comments
 (0)