@web-ts-toolkit/express-response-handler 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +4 -323
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -10,50 +10,12 @@ Instead of calling `res.json(...)` in every route, return a value. This package
10
10
  npm install @web-ts-toolkit/express-response-handler
11
11
  ```
12
12
 
13
- ## Philosophy
13
+ ## Documentation
14
14
 
15
- Express is usually imperative:
15
+ - Full package documentation lives in `website/docs/packages/express-response-handler.md`.
16
+ - Use the Docusaurus site in `website` for examples, hooks, and structured error format details.
16
17
 
17
- ```ts
18
- app.get('/users/:id', async (req, res, next) => {
19
- try {
20
- const user = await getUser(req.params.id);
21
-
22
- if (!user) {
23
- return res.status(404).json({ message: 'user not found' });
24
- }
25
-
26
- return res.json(user);
27
- } catch (err) {
28
- next(err);
29
- }
30
- });
31
- ```
32
-
33
- This package lets you write the same route in a more declarative style:
34
-
35
- ```ts
36
- app.get(
37
- '/users/:id',
38
- handleResponse(async (req) => {
39
- const user = await getUser(req.params.id);
40
-
41
- if (!user) {
42
- throw new NotFoundError('user not found');
43
- }
44
-
45
- return user;
46
- }),
47
- );
48
- ```
49
-
50
- The main rule is simple:
51
-
52
- - return a plain value for a `200 OK` JSON response
53
- - return an explicit response wrapper for a custom status or format
54
- - throw an error for failures
55
-
56
- ## Quick Start
18
+ ## Minimal Example
57
19
 
58
20
  ```ts
59
21
  import express from 'express';
@@ -93,284 +55,3 @@ app.post(
93
55
  }),
94
56
  );
95
57
  ```
96
-
97
- ## How It Works
98
-
99
- `handleResponse(...)` wraps one or more Express handlers.
100
-
101
- When a handler runs:
102
-
103
- - a plain returned value becomes `res.json(value)`
104
- - a returned `HttpResponse.*(...)` wrapper controls the status code
105
- - a returned `HttpResponse.csv(...)` streams CSV
106
- - a thrown error becomes an error response
107
- - a returned promise is awaited automatically
108
-
109
- Supported forms:
110
-
111
- - `handleResponse(fn)`
112
- - `handleResponse(fn1, fn2)`
113
- - `handleResponse([fn1, fn2])`
114
-
115
- ## Examples
116
-
117
- ### Return JSON with `200 OK`
118
-
119
- ```ts
120
- app.get(
121
- '/profile',
122
- handleResponse(async (req) => {
123
- return {
124
- id: req.user.id,
125
- email: req.user.email,
126
- };
127
- }),
128
- );
129
- ```
130
-
131
- ### Return a custom success status
132
-
133
- ```ts
134
- app.post(
135
- '/sessions',
136
- handleResponse(async (req) => {
137
- const session = await createSession(req.body);
138
- return HttpResponse.created(session);
139
- }),
140
- );
141
- ```
142
-
143
- ### Throw HTTP errors
144
-
145
- ```ts
146
- import { BadRequestError, NotFoundError } from '@web-ts-toolkit/http-errors';
147
-
148
- app.get(
149
- '/projects/:id',
150
- handleResponse(async (req) => {
151
- if (!req.params.id) {
152
- throw new BadRequestError('project id is required');
153
- }
154
-
155
- const project = await getProject(req.params.id);
156
-
157
- if (!project) {
158
- throw new NotFoundError('project not found');
159
- }
160
-
161
- return project;
162
- }),
163
- );
164
- ```
165
-
166
- ### Return CSV
167
-
168
- ```ts
169
- app.get(
170
- '/reports/users.csv',
171
- handleResponse(async () => {
172
- const rows = await getUserReportRows();
173
-
174
- return HttpResponse.csv(rows, {
175
- filename: 'users.csv',
176
- });
177
- }),
178
- );
179
- ```
180
-
181
- ### Use more than one Express handler
182
-
183
- ```ts
184
- app.get(
185
- '/me',
186
- handleResponse(requireAuth, async (req) => {
187
- return req.user;
188
- }),
189
- );
190
- ```
191
-
192
- If you call `next()` with no arguments, Express middleware flow continues normally.
193
-
194
- Do not use `next(value)` for successful responses. Return the value instead.
195
-
196
- ## Hooks
197
-
198
- Hooks let you observe or modify response flow without repeating code in every route.
199
-
200
- Available setters:
201
-
202
- - `apiHandler.preJson = fn`
203
- - `apiHandler.postJson = fn`
204
- - `apiHandler.preError = fn`
205
- - `apiHandler.postError = fn`
206
-
207
- Example:
208
-
209
- ```ts
210
- apiHandler.preJson = async function (data) {
211
- console.log('about to send json response', data);
212
- };
213
-
214
- apiHandler.preError = async function (err) {
215
- console.error('request failed', err);
216
- };
217
- ```
218
-
219
- ## Custom Error Messages
220
-
221
- Non-HTTP errors default to status `422` with a message resolved from the thrown value.
222
-
223
- You can customize that behavior:
224
-
225
- ```ts
226
- apiHandler.errorMessageProvider = function (err) {
227
- return {
228
- message: 'request failed',
229
- detail: err instanceof Error ? err.message : String(err),
230
- };
231
- };
232
- ```
233
-
234
- ## Structured Error Format
235
-
236
- The default error payload is intentionally small:
237
-
238
- ```json
239
- { "message": "project not found" }
240
- ```
241
-
242
- If you want an AIP-193-inspired error envelope, create a handler instance with `errorFormat: 'aip193'`:
243
-
244
- ```ts
245
- import apiHandler from '@web-ts-toolkit/express-response-handler';
246
- import { ErrorFormats } from '@web-ts-toolkit/express-response-handler';
247
-
248
- const structuredHandler = apiHandler.createHandler({
249
- errorFormat: ErrorFormats.aip193,
250
- errorDomain: 'api.example.com',
251
- });
252
- ```
253
-
254
- That mode returns errors in this shape:
255
-
256
- ```json
257
- {
258
- "error": {
259
- "code": 404,
260
- "status": "NOT_FOUND",
261
- "message": "project not found",
262
- "details": [
263
- {
264
- "type": "error_info",
265
- "reason": "NOT_FOUND",
266
- "domain": "api.example.com"
267
- }
268
- ]
269
- }
270
- }
271
- ```
272
-
273
- You can enrich HTTP errors with machine-readable fields:
274
-
275
- ```ts
276
- import { BadRequestError } from '@web-ts-toolkit/http-errors';
277
-
278
- app.get(
279
- '/projects/:id',
280
- structuredHandler.handleResponse(async () => {
281
- throw new BadRequestError('invalid project id', {
282
- reason: 'INVALID_PROJECT_ID',
283
- metadata: { field: 'id' },
284
- details: [
285
- {
286
- type: 'help',
287
- links: [
288
- {
289
- description: 'Project ID format guide',
290
- url: 'https://api.example.com/docs/errors/invalid-project-id',
291
- },
292
- ],
293
- },
294
- ],
295
- });
296
- }),
297
- );
298
- ```
299
-
300
- If you want RFC 9457 problem details instead, create a handler instance with `errorFormat: 'rfc9457'`:
301
-
302
- ```ts
303
- import apiHandler from '@web-ts-toolkit/express-response-handler';
304
- import { ErrorFormats } from '@web-ts-toolkit/express-response-handler';
305
-
306
- const problemHandler = apiHandler.createHandler({
307
- errorFormat: ErrorFormats.rfc9457,
308
- errorDomain: 'api.example.com',
309
- });
310
- ```
311
-
312
- That mode returns `application/problem+json` payloads in this shape:
313
-
314
- ```json
315
- {
316
- "type": "https://api.example.com/problems/invalid-project-id",
317
- "title": "Invalid project id",
318
- "status": 400,
319
- "detail": "invalid project id",
320
- "instance": "/problems/invalid-project-id/123",
321
- "errors": [
322
- {
323
- "detail": "must be a valid project id",
324
- "pointer": "#/id"
325
- }
326
- ]
327
- }
328
- ```
329
-
330
- You can enrich HTTP errors with problem detail fields:
331
-
332
- ```ts
333
- import { BadRequestError } from '@web-ts-toolkit/http-errors';
334
-
335
- app.get(
336
- '/projects/:id',
337
- problemHandler.handleResponse(async () => {
338
- throw new BadRequestError('invalid project id', {
339
- type: 'https://api.example.com/problems/invalid-project-id',
340
- title: 'Invalid project id',
341
- instance: '/problems/invalid-project-id/123',
342
- errors: [
343
- {
344
- detail: 'must be a valid project id',
345
- pointer: '#/id',
346
- },
347
- ],
348
- });
349
- }),
350
- );
351
- ```
352
-
353
- ## Isolated Instances
354
-
355
- The default export is a ready-to-use singleton. If you want separate hook configuration per router or module, create an isolated instance:
356
-
357
- ```ts
358
- import apiHandler from '@web-ts-toolkit/express-response-handler';
359
-
360
- const adminHandler = apiHandler.createHandler();
361
- const publicHandler = apiHandler.createHandler();
362
-
363
- adminHandler.preError = async function (err) {
364
- console.error('admin route failed', err);
365
- };
366
- ```
367
-
368
- ## When To Use It
369
-
370
- This package is a good fit when you want:
371
-
372
- - Express routes that return values instead of calling `res.json(...)`
373
- - a small abstraction rather than a full framework
374
- - consistent JSON, error, and CSV response behavior
375
-
376
- It is less useful if you want fully explicit low-level Express response control in every route.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@web-ts-toolkit/express-response-handler",
3
3
  "description": "Express response handler",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "keywords": [
6
6
  "express",
7
7
  "api",
@@ -45,8 +45,8 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@fast-csv/format": "^5.0.5",
48
- "@web-ts-toolkit/http-errors": "0.2.0",
49
- "@web-ts-toolkit/utils": "0.2.0"
48
+ "@web-ts-toolkit/http-errors": "0.3.0",
49
+ "@web-ts-toolkit/utils": "0.3.0"
50
50
  },
51
51
  "author": "Junmin Ahn",
52
52
  "bugs": {