xpref 1.0.1 → 1.0.3
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.
- package/README.md +282 -228
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,297 +1,394 @@
|
|
|
1
|
-
#
|
|
1
|
+
# xpref
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Express.js application bootstrap for APIs — nested routing, request validation, OpenAPI docs, idempotency, proxying, and i18n.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
7
|
+
### Core server
|
|
8
|
+
- Express 5 app bootstrap with a single `xpref()` call
|
|
9
|
+
- Automatic port fallback when the configured port is in use
|
|
10
|
+
- Manual start control via `manuallyStart`
|
|
11
|
+
- `onInit` hook (runs before built-in middleware)
|
|
12
|
+
- `interceptor` hook (runs after built-in middleware, before routes)
|
|
13
|
+
- Startup banner with app name, environment, and port
|
|
14
|
+
- TypeScript-first types and exports
|
|
15
|
+
|
|
16
|
+
### Security & request pipeline
|
|
17
|
+
- Helmet security headers
|
|
18
|
+
- CORS enabled
|
|
19
|
+
- Trust proxy enabled
|
|
20
|
+
- JSON body parsing (8MB limit)
|
|
21
|
+
- URL-encoded body parsing
|
|
22
|
+
- Unique request ID (`Request-Id` / `request-id`) on every request and response
|
|
23
|
+
- `getRequestId()` helper to read the current request ID
|
|
24
|
+
|
|
25
|
+
### Routing
|
|
26
|
+
- Declarative nested route trees (parent path + children)
|
|
27
|
+
- Per-route middleware arrays
|
|
28
|
+
- HTTP methods: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`
|
|
29
|
+
- Handler as a function, array of functions, or `{ action, params, description }`
|
|
30
|
+
- Static file serving via `staticRoutes`
|
|
31
|
+
|
|
32
|
+
### Request validation (AJV)
|
|
33
|
+
- Shared schemas via the top-level `schemas` option
|
|
34
|
+
- Per-method validation for `query`, `path` (`params`), and `body`
|
|
35
|
+
- Type coercion, `$data`, and `ajv-errors` support
|
|
36
|
+
- Human-readable field error messages (400 responses)
|
|
37
|
+
|
|
38
|
+
### Request logging
|
|
39
|
+
- Morgan console access logs (app name, env, request ID, method, status, URL, timing, user-agent)
|
|
40
|
+
- Optional external logger integration (e.g. `@core/log-client`)
|
|
41
|
+
- Structured log payload: request ID, IP, country, language, device ID, origin, referer, and base64 body for non-GET
|
|
42
|
+
|
|
43
|
+
### OpenAPI / Swagger (`xpref/api-docs`)
|
|
44
|
+
- OpenAPI 3.0 document generation from routes and schemas
|
|
45
|
+
- Swagger UI middleware (`swagger-ui-express`)
|
|
46
|
+
- Tags and external docs metadata
|
|
47
|
+
- Bearer (JWT) and API key security schemes
|
|
48
|
+
|
|
49
|
+
### Idempotency (`xpref/idempotency`)
|
|
50
|
+
- POST-only idempotency via `idempotency-key` header (UUID v4)
|
|
51
|
+
- Optional enforcement per route
|
|
52
|
+
- Configurable TTL (default 5 minutes)
|
|
53
|
+
- In-progress (`202`), cached success replay, and expired (`410`) responses
|
|
54
|
+
- Optional `validateResponse` callback for custom success rules
|
|
55
|
+
- JSON and `x-www-form-urlencoded` bodies
|
|
56
|
+
|
|
57
|
+
### Request forwarder / proxy (`xpref/request-forwarder`)
|
|
58
|
+
- `forwarder` — proxy HTTP/HTTPS upstream with body forwarding
|
|
59
|
+
- `proxy` — stream pipe to upstream
|
|
60
|
+
- Custom host, `proxyPrefix`, `withPrefix`, extra headers
|
|
61
|
+
- `onUrlConstructed` URL rewrite hook
|
|
62
|
+
- `passToNext` — collect upstream result and continue the middleware chain
|
|
63
|
+
|
|
64
|
+
### Internationalization (`xpref/i18n`)
|
|
65
|
+
- Locale JSON files loaded at init
|
|
66
|
+
- `translate(key, replace?, lang?)` with `{placeholder}` substitution
|
|
67
|
+
- Fallback language support (`fallbackLang`, `fallbackLangOnly`)
|
|
16
68
|
|
|
17
69
|
## Usage
|
|
18
70
|
|
|
19
|
-
### Basic
|
|
71
|
+
### Basic server setup
|
|
20
72
|
|
|
21
73
|
```typescript
|
|
22
74
|
import xpref from 'xpref';
|
|
23
75
|
|
|
24
|
-
// Basic server setup
|
|
25
76
|
xpref({
|
|
26
77
|
appName: 'my-app',
|
|
27
78
|
appEnv: 'development',
|
|
28
79
|
port: 3000,
|
|
29
80
|
routes: {
|
|
30
81
|
'/api/users': [
|
|
31
|
-
'users',
|
|
32
|
-
[],
|
|
82
|
+
'users',
|
|
83
|
+
[],
|
|
33
84
|
{
|
|
34
85
|
get: (req, res) => {
|
|
35
86
|
res.json({ users: [] });
|
|
36
|
-
}
|
|
87
|
+
},
|
|
37
88
|
},
|
|
38
|
-
{}
|
|
39
|
-
]
|
|
40
|
-
}
|
|
89
|
+
{},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
41
92
|
}).then(({ port, app }) => {
|
|
42
93
|
console.log(`Server running on port ${port}`);
|
|
43
94
|
});
|
|
44
95
|
```
|
|
45
96
|
|
|
46
|
-
###
|
|
97
|
+
### Nested routes and middleware
|
|
47
98
|
|
|
48
99
|
```typescript
|
|
49
|
-
import xpref from 'xpref';
|
|
50
|
-
import { createLogger } from '@core/log-client';
|
|
51
|
-
|
|
52
|
-
// Create a logger instance
|
|
53
|
-
const logger = createLogger({
|
|
54
|
-
appName: 'my-app',
|
|
55
|
-
appEnv: 'development'
|
|
56
|
-
});
|
|
57
|
-
|
|
58
100
|
xpref({
|
|
59
101
|
appName: 'my-app',
|
|
60
102
|
appEnv: 'development',
|
|
61
103
|
port: 3000,
|
|
62
|
-
|
|
104
|
+
interceptor: (app) => {
|
|
105
|
+
app.use((req, res, next) => {
|
|
106
|
+
console.log('Custom middleware');
|
|
107
|
+
next();
|
|
108
|
+
});
|
|
109
|
+
},
|
|
63
110
|
routes: {
|
|
64
|
-
'/api/
|
|
65
|
-
'
|
|
111
|
+
'/api/users': [
|
|
112
|
+
'users',
|
|
66
113
|
[],
|
|
67
114
|
{
|
|
68
|
-
get: (req, res) => {
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
get: (req, res) => res.json({ users: [] }),
|
|
116
|
+
post: (req, res) => res.json({ message: 'User created' }),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
'/:id': [
|
|
120
|
+
'user-by-id',
|
|
121
|
+
[authMiddleware],
|
|
122
|
+
{
|
|
123
|
+
get: (req, res) => res.json({ id: req.params.id }),
|
|
124
|
+
put: (req, res) => res.json({ message: 'Updated' }),
|
|
125
|
+
delete: (req, res) => res.json({ message: 'Deleted' }),
|
|
126
|
+
},
|
|
127
|
+
],
|
|
71
128
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
129
|
+
],
|
|
130
|
+
},
|
|
75
131
|
});
|
|
76
132
|
```
|
|
77
133
|
|
|
78
|
-
###
|
|
134
|
+
### Request validation
|
|
79
135
|
|
|
80
136
|
```typescript
|
|
81
137
|
xpref({
|
|
82
138
|
appName: 'my-app',
|
|
83
139
|
appEnv: 'development',
|
|
84
140
|
port: 3000,
|
|
141
|
+
schemas: {
|
|
142
|
+
email: { type: 'string', format: 'email' },
|
|
143
|
+
name: { type: 'string', minLength: 1 },
|
|
144
|
+
},
|
|
85
145
|
routes: {
|
|
86
146
|
'/api/users': [
|
|
87
147
|
'users',
|
|
88
|
-
[],
|
|
148
|
+
[],
|
|
89
149
|
{
|
|
90
|
-
|
|
91
|
-
|
|
150
|
+
post: {
|
|
151
|
+
description: 'Create a user',
|
|
152
|
+
params: {
|
|
153
|
+
body: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: ['email', 'name'],
|
|
156
|
+
required: ['email', 'name'],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
action: (req, res) => {
|
|
160
|
+
res.json({ message: 'User created', ...req.body });
|
|
161
|
+
},
|
|
92
162
|
},
|
|
93
|
-
post: (req, res) => {
|
|
94
|
-
res.json({ message: 'User created' });
|
|
95
|
-
}
|
|
96
163
|
},
|
|
97
|
-
{
|
|
98
|
-
'/create': [
|
|
99
|
-
'create-user',
|
|
100
|
-
[], // middleware array
|
|
101
|
-
{
|
|
102
|
-
post: (req, res) => {
|
|
103
|
-
res.json({ message: 'User created' });
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
{}
|
|
107
|
-
]
|
|
108
|
-
}
|
|
109
164
|
],
|
|
110
|
-
|
|
111
|
-
'auth',
|
|
112
|
-
[], // middleware array
|
|
113
|
-
{
|
|
114
|
-
post: (req, res) => {
|
|
115
|
-
res.json({ token: 'jwt-token' });
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
{}
|
|
119
|
-
]
|
|
120
|
-
}
|
|
165
|
+
},
|
|
121
166
|
});
|
|
122
167
|
```
|
|
123
168
|
|
|
124
|
-
### With
|
|
169
|
+
### With request logging
|
|
125
170
|
|
|
126
171
|
```typescript
|
|
172
|
+
import xpref from 'xpref';
|
|
173
|
+
import { createLogger } from '@core/log-client';
|
|
174
|
+
|
|
175
|
+
const logger = createLogger({
|
|
176
|
+
appName: 'my-app',
|
|
177
|
+
appEnv: 'development',
|
|
178
|
+
});
|
|
179
|
+
|
|
127
180
|
xpref({
|
|
128
181
|
appName: 'my-app',
|
|
129
182
|
appEnv: 'development',
|
|
130
183
|
port: 3000,
|
|
131
|
-
|
|
132
|
-
'/public': './public',
|
|
133
|
-
'/uploads': './uploads'
|
|
134
|
-
},
|
|
184
|
+
logger,
|
|
135
185
|
routes: {
|
|
136
|
-
'/api/
|
|
137
|
-
'
|
|
186
|
+
'/api/logs': [
|
|
187
|
+
'logs',
|
|
138
188
|
[],
|
|
139
189
|
{
|
|
140
|
-
get: (req, res) => {
|
|
141
|
-
res.json({ files: [] });
|
|
142
|
-
},
|
|
143
|
-
post: (req, res) => {
|
|
144
|
-
res.json({ message: 'File uploaded' });
|
|
145
|
-
},
|
|
146
|
-
put: (req, res) => {
|
|
147
|
-
res.json({ message: 'File updated' });
|
|
148
|
-
},
|
|
149
|
-
delete: (req, res) => {
|
|
150
|
-
res.json({ message: 'File deleted' });
|
|
151
|
-
}
|
|
190
|
+
get: (req, res) => res.json({ logs: [] }),
|
|
152
191
|
},
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
192
|
+
],
|
|
193
|
+
},
|
|
156
194
|
});
|
|
157
195
|
```
|
|
158
196
|
|
|
159
|
-
###
|
|
197
|
+
### Static files
|
|
160
198
|
|
|
161
199
|
```typescript
|
|
162
200
|
xpref({
|
|
163
201
|
appName: 'my-app',
|
|
164
202
|
appEnv: 'development',
|
|
165
203
|
port: 3000,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
console.log('Custom middleware');
|
|
170
|
-
next();
|
|
171
|
-
});
|
|
204
|
+
staticRoutes: {
|
|
205
|
+
'/public': './public',
|
|
206
|
+
'/uploads': './uploads',
|
|
172
207
|
},
|
|
173
208
|
routes: {
|
|
174
|
-
'/api/
|
|
175
|
-
'
|
|
176
|
-
[
|
|
209
|
+
'/api/files': [
|
|
210
|
+
'files',
|
|
211
|
+
[],
|
|
177
212
|
{
|
|
178
|
-
get: (req, res) => {
|
|
179
|
-
res.json({ data: 'Protected route' });
|
|
180
|
-
},
|
|
181
|
-
post: (req, res) => {
|
|
182
|
-
res.json({ data: 'Protected route created' });
|
|
183
|
-
},
|
|
184
|
-
put: (req, res) => {
|
|
185
|
-
res.json({ data: 'Protected route updated' });
|
|
186
|
-
},
|
|
187
|
-
delete: (req, res) => {
|
|
188
|
-
res.json({ data: 'Protected route deleted' });
|
|
189
|
-
}
|
|
213
|
+
get: (req, res) => res.json({ files: [] }),
|
|
190
214
|
},
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
215
|
+
],
|
|
216
|
+
},
|
|
194
217
|
});
|
|
195
218
|
```
|
|
196
219
|
|
|
197
|
-
###
|
|
220
|
+
### Manual start
|
|
198
221
|
|
|
199
222
|
```typescript
|
|
200
223
|
xpref({
|
|
201
224
|
appName: 'my-app',
|
|
202
225
|
appEnv: 'development',
|
|
203
226
|
port: 3000,
|
|
204
|
-
manuallyStart: ({ app, port }) =>
|
|
205
|
-
|
|
206
|
-
return new Promise((resolve) => {
|
|
227
|
+
manuallyStart: ({ app, port }) =>
|
|
228
|
+
new Promise((resolve) => {
|
|
207
229
|
const server = app.listen(port, () => {
|
|
208
230
|
resolve({ port, app, server });
|
|
209
231
|
});
|
|
210
|
-
})
|
|
211
|
-
},
|
|
232
|
+
}),
|
|
212
233
|
routes: {
|
|
213
234
|
'/api/health': [
|
|
214
235
|
'health',
|
|
215
236
|
[],
|
|
216
237
|
{
|
|
217
|
-
get: (req, res) => {
|
|
218
|
-
res.json({ status: 'ok' });
|
|
219
|
-
}
|
|
238
|
+
get: (req, res) => res.json({ status: 'ok' }),
|
|
220
239
|
},
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
240
|
+
],
|
|
241
|
+
},
|
|
224
242
|
});
|
|
225
243
|
```
|
|
226
244
|
|
|
227
|
-
|
|
245
|
+
### Idempotency
|
|
228
246
|
|
|
229
|
-
|
|
247
|
+
```typescript
|
|
248
|
+
import type { Route } from 'xpref';
|
|
249
|
+
import idempotency from 'xpref/idempotency';
|
|
230
250
|
|
|
231
|
-
|
|
251
|
+
const routes = {
|
|
252
|
+
'/wallets': [
|
|
253
|
+
'wallets',
|
|
254
|
+
[],
|
|
255
|
+
{},
|
|
256
|
+
{
|
|
257
|
+
'/transfer': [
|
|
258
|
+
'transfer',
|
|
259
|
+
[idempotency({ enforced: true, ttl: 300 })],
|
|
260
|
+
{
|
|
261
|
+
post: walletTransferAction,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
} as Route;
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Clients send `idempotency-key: <uuid-v4>` on POST. Retry on `5xx`, `422`, `429`, and similar failures with exponential backoff.
|
|
232
270
|
|
|
233
|
-
|
|
271
|
+
### Request forwarder / proxy
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { forwarder, proxy } from 'xpref/request-forwarder';
|
|
275
|
+
|
|
276
|
+
// As middleware: forward and respond from upstream
|
|
277
|
+
app.use('/upstream', forwarder({
|
|
278
|
+
host: 'https://api.example.com',
|
|
279
|
+
proxyPrefix: '/v1',
|
|
280
|
+
headers: { 'x-api-key': 'secret' },
|
|
281
|
+
}));
|
|
282
|
+
|
|
283
|
+
// Collect upstream result and continue the chain
|
|
284
|
+
app.use('/gateway', forwarder({
|
|
285
|
+
host: 'https://api.example.com',
|
|
286
|
+
passToNext: true,
|
|
287
|
+
}), (req, res) => {
|
|
288
|
+
// Upstream status/headers/body available via applyProxyResultToRequest
|
|
289
|
+
res.json({ ok: true });
|
|
290
|
+
});
|
|
234
291
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
- `interceptor`: Function to add custom middleware
|
|
243
|
-
- `logger`: LogClient instance from @core/log-client package
|
|
292
|
+
// Stream pipe proxy
|
|
293
|
+
app.use('/proxy', proxy({
|
|
294
|
+
host: 'https://api.example.com',
|
|
295
|
+
withPrefix: true,
|
|
296
|
+
onUrlConstructed: (url) => url.replace(/\/+$/, ''),
|
|
297
|
+
}));
|
|
298
|
+
```
|
|
244
299
|
|
|
245
|
-
###
|
|
300
|
+
### i18n
|
|
246
301
|
|
|
247
302
|
```typescript
|
|
248
|
-
|
|
249
|
-
get?: (req: Request, res: Response, next: NextFunction) => void;
|
|
250
|
-
post?: (req: Request, res: Response, next: NextFunction) => void;
|
|
251
|
-
put?: (req: Request, res: Response, next: NextFunction) => void;
|
|
252
|
-
delete?: (req: Request, res: Response, next: NextFunction) => void;
|
|
253
|
-
patch?: (req: Request, res: Response, next: NextFunction) => void;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
type RouteConfig = [
|
|
257
|
-
string, // route name
|
|
258
|
-
Array<(req: Request, res: Response, next: NextFunction) => void>, // middleware array
|
|
259
|
-
Handler, // handler object with HTTP methods
|
|
260
|
-
Record<string, RouteConfig> // children routes
|
|
261
|
-
];
|
|
303
|
+
import { i18n } from 'xpref';
|
|
262
304
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
305
|
+
const t = i18n({
|
|
306
|
+
locale: {
|
|
307
|
+
en: './locales/en.json',
|
|
308
|
+
km: './locales/km.json',
|
|
309
|
+
},
|
|
310
|
+
fallbackLang: 'en',
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
t('welcome.message', { name: 'Ada' }, 'en');
|
|
266
314
|
```
|
|
267
315
|
|
|
268
|
-
###
|
|
316
|
+
### OpenAPI / Swagger UI
|
|
269
317
|
|
|
270
318
|
```typescript
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
319
|
+
import setupApiDocs from 'xpref/api-docs';
|
|
320
|
+
|
|
321
|
+
const [serve, setup] = setupApiDocs(
|
|
322
|
+
{
|
|
323
|
+
info: {
|
|
324
|
+
title: 'My API',
|
|
325
|
+
version: '1.0.0',
|
|
326
|
+
description: 'API documentation',
|
|
327
|
+
},
|
|
328
|
+
bearerAuth: true,
|
|
329
|
+
apiKeys: ['x-api-key'],
|
|
330
|
+
tags: {
|
|
331
|
+
users: { description: 'User endpoints' },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{ routes, schemas },
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
app.use('/docs', serve, setup);
|
|
274
338
|
```
|
|
275
339
|
|
|
276
|
-
##
|
|
340
|
+
## API reference
|
|
277
341
|
|
|
278
|
-
###
|
|
279
|
-
- Helmet.js for security headers
|
|
280
|
-
- CORS enabled
|
|
281
|
-
- Trust proxy enabled
|
|
282
|
-
- JSON body parsing (8MB limit)
|
|
283
|
-
- URL-encoded body parsing
|
|
342
|
+
### `xpref(props: Xpref): Promise<{ port: number; app: Application }>`
|
|
284
343
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
344
|
+
| Option | Type | Description |
|
|
345
|
+
| --- | --- | --- |
|
|
346
|
+
| `appName` | `string` | Application name |
|
|
347
|
+
| `appEnv` | `string` | Environment (e.g. `development`, `production`) |
|
|
348
|
+
| `port` | `number` | Listen port (default `3000`; auto-increments if in use) |
|
|
349
|
+
| `routes` | `Route` | Nested route configuration |
|
|
350
|
+
| `schemas` | `Record<string, any>` | Shared AJV schemas for method validation |
|
|
351
|
+
| `staticRoutes` | `Record<string, string>` | URL path → filesystem path |
|
|
352
|
+
| `logger` | `any` | Optional logger factory used by request logging |
|
|
353
|
+
| `onInit` | `(app) => void` | Hook before built-in middleware |
|
|
354
|
+
| `interceptor` | `(app) => void` | Hook after built-in middleware |
|
|
355
|
+
| `manuallyStart` | `({ app, port }) => Promise` | Skip auto-listen; start the server yourself |
|
|
289
356
|
|
|
290
|
-
###
|
|
291
|
-
- Automatic port fallback if port is in use
|
|
292
|
-
- Error handling for server startup
|
|
357
|
+
### Route configuration
|
|
293
358
|
|
|
294
|
-
|
|
359
|
+
```typescript
|
|
360
|
+
type MethodOptions =
|
|
361
|
+
| CallableFunction
|
|
362
|
+
| CallableFunction[]
|
|
363
|
+
| {
|
|
364
|
+
action: CallableFunction | CallableFunction[];
|
|
365
|
+
params?: { query?: any; path?: any; body?: any };
|
|
366
|
+
description?: string | [string, string];
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
type PathDetail = [
|
|
370
|
+
string, // route name
|
|
371
|
+
any[], // middleware
|
|
372
|
+
MethodHandler, // get/post/put/delete/patch
|
|
373
|
+
Record<string, PathDetail>?, // children
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
type Route = Record<string, PathDetail>;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Exports
|
|
380
|
+
|
|
381
|
+
| Export | From | Description |
|
|
382
|
+
| --- | --- | --- |
|
|
383
|
+
| default `xpref` | `xpref` | Create and start the app |
|
|
384
|
+
| `getRequestId` | `xpref` | Read request ID from a request |
|
|
385
|
+
| `i18n` | `xpref` | Initialize translations |
|
|
386
|
+
| Express types / `urlencoded` | `xpref` | Re-exported for convenience |
|
|
387
|
+
| `idempotency` | `xpref/idempotency` | Idempotency middleware |
|
|
388
|
+
| `setupApiDocs` | `xpref/api-docs` | OpenAPI + Swagger UI |
|
|
389
|
+
| `forwarder`, `proxy` | `xpref/request-forwarder` | Upstream proxy helpers |
|
|
390
|
+
|
|
391
|
+
## Example project structure
|
|
295
392
|
|
|
296
393
|
```
|
|
297
394
|
src/
|
|
@@ -300,54 +397,14 @@ src/
|
|
|
300
397
|
│ └── auth.ts
|
|
301
398
|
├── middleware/
|
|
302
399
|
│ └── auth.ts
|
|
400
|
+
├── locales/
|
|
401
|
+
│ └── en.json
|
|
303
402
|
├── static/
|
|
304
403
|
│ └── public/
|
|
305
404
|
└── index.ts
|
|
306
405
|
```
|
|
307
406
|
|
|
308
407
|
```typescript
|
|
309
|
-
// src/routes/users.ts
|
|
310
|
-
export default {
|
|
311
|
-
'/api/users': [
|
|
312
|
-
'users',
|
|
313
|
-
[],
|
|
314
|
-
{
|
|
315
|
-
get: (req, res) => {
|
|
316
|
-
res.json({ users: [] });
|
|
317
|
-
},
|
|
318
|
-
post: (req, res) => {
|
|
319
|
-
res.json({ message: 'User created' });
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
'/create': [
|
|
324
|
-
'create-user',
|
|
325
|
-
[],
|
|
326
|
-
{
|
|
327
|
-
post: (req, res) => {
|
|
328
|
-
res.json({ message: 'User created' });
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
{}
|
|
332
|
-
]
|
|
333
|
-
}
|
|
334
|
-
]
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
// src/routes/auth.ts
|
|
338
|
-
export default {
|
|
339
|
-
'/api/auth': [
|
|
340
|
-
'auth',
|
|
341
|
-
[],
|
|
342
|
-
{
|
|
343
|
-
post: (req, res) => {
|
|
344
|
-
res.json({ token: 'jwt-token' });
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
{}
|
|
348
|
-
]
|
|
349
|
-
};
|
|
350
|
-
|
|
351
408
|
// src/index.ts
|
|
352
409
|
import xpref from 'xpref';
|
|
353
410
|
import { createLogger } from '@core/log-client';
|
|
@@ -355,10 +412,9 @@ import userRoutes from './routes/users';
|
|
|
355
412
|
import authRoutes from './routes/auth';
|
|
356
413
|
import authMiddleware from './middleware/auth';
|
|
357
414
|
|
|
358
|
-
// Create logger instance
|
|
359
415
|
const logger = createLogger({
|
|
360
416
|
appName: 'my-api',
|
|
361
|
-
appEnv: process.env.NODE_ENV || 'development'
|
|
417
|
+
appEnv: process.env.NODE_ENV || 'development',
|
|
362
418
|
});
|
|
363
419
|
|
|
364
420
|
xpref({
|
|
@@ -366,17 +422,16 @@ xpref({
|
|
|
366
422
|
appEnv: process.env.NODE_ENV || 'development',
|
|
367
423
|
port: 3000,
|
|
368
424
|
staticRoutes: {
|
|
369
|
-
'/public': './static/public'
|
|
425
|
+
'/public': './static/public',
|
|
370
426
|
},
|
|
371
427
|
interceptor: (app) => {
|
|
372
|
-
// Add authentication middleware
|
|
373
428
|
app.use('/api/protected', authMiddleware);
|
|
374
429
|
},
|
|
375
|
-
logger,
|
|
430
|
+
logger,
|
|
376
431
|
routes: {
|
|
377
432
|
...userRoutes,
|
|
378
|
-
...authRoutes
|
|
379
|
-
}
|
|
433
|
+
...authRoutes,
|
|
434
|
+
},
|
|
380
435
|
}).then(({ port }) => {
|
|
381
436
|
console.log(`Server running on port ${port}`);
|
|
382
437
|
});
|
|
@@ -384,5 +439,4 @@ xpref({
|
|
|
384
439
|
|
|
385
440
|
## License
|
|
386
441
|
|
|
387
|
-
|
|
388
|
-
|
|
442
|
+
ISC
|