@lara-node/middlewares 0.1.2 → 0.1.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 +217 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# @lara-node/middlewares
|
|
2
|
+
|
|
3
|
+
Predefined class-based middleware for Lara-Node applications. Every middleware is a plain class with a `handle()` method, keeping them testable and dependency-injectable.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @lara-node/middlewares
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Setup
|
|
12
|
+
|
|
13
|
+
Register the core middleware stack in your Express app:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import {
|
|
18
|
+
asyncContext,
|
|
19
|
+
requestLogger,
|
|
20
|
+
validatorAttach,
|
|
21
|
+
responseExtender,
|
|
22
|
+
errorHandler,
|
|
23
|
+
} from '@lara-node/middlewares';
|
|
24
|
+
|
|
25
|
+
const app = express();
|
|
26
|
+
app.use(express.json());
|
|
27
|
+
app.use(asyncContext); // async context per request
|
|
28
|
+
app.use(requestLogger); // colored request log
|
|
29
|
+
app.use(validatorAttach); // attaches req.validate()
|
|
30
|
+
app.use(responseExtender); // adds res.jsonAsync(), auto-serializes Models
|
|
31
|
+
|
|
32
|
+
// ... routes ...
|
|
33
|
+
|
|
34
|
+
app.use(errorHandler); // 4-arg error handler (must be last)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Middleware Reference
|
|
38
|
+
|
|
39
|
+
### AsyncContextMiddleware
|
|
40
|
+
|
|
41
|
+
Stores the request in `AsyncLocalStorage` so it's accessible anywhere in the call stack without prop-drilling:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { asyncLocalStorage } from '@lara-node/middlewares';
|
|
45
|
+
|
|
46
|
+
// Anywhere in the request lifecycle:
|
|
47
|
+
const store = asyncLocalStorage.getStore(); // { req, user? }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### RequestLoggerMiddleware
|
|
51
|
+
|
|
52
|
+
Logs `METHOD PATH STATUS TIME IP [user:id]` to stdout on response finish. Color-coded: green (2xx), yellow (4xx), red (5xx).
|
|
53
|
+
|
|
54
|
+
### ValidatorMiddleware
|
|
55
|
+
|
|
56
|
+
Attaches `req.validate()` to every request:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// In any controller:
|
|
60
|
+
const data = await req.validate({
|
|
61
|
+
name: 'required|string|min:2|max:100',
|
|
62
|
+
email: 'required|email',
|
|
63
|
+
age: 'required|integer|min:18',
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Throws `ValidationError` (caught by `ErrorHandlerMiddleware` → HTTP 422) on failure.
|
|
68
|
+
|
|
69
|
+
### ResponseExtenderMiddleware
|
|
70
|
+
|
|
71
|
+
Adds `res.jsonAsync()` and overrides `res.json()` to automatically call `model.toJSONAsync()` on `@lara-node/db` Model instances (handles hidden fields, casts, relations):
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// All equivalent — Model is auto-serialized:
|
|
75
|
+
res.json(user);
|
|
76
|
+
res.json([user1, user2]);
|
|
77
|
+
res.json(await User.paginate(15, 1)); // QueryResult
|
|
78
|
+
await res.jsonAsync(user);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### AuthMiddleware
|
|
82
|
+
|
|
83
|
+
JWT authentication. Verifies `Authorization: Bearer <token>` and optionally loads the user from the database:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { AuthMiddleware } from '@lara-node/middlewares';
|
|
87
|
+
|
|
88
|
+
const auth = new AuthMiddleware({
|
|
89
|
+
// Optional: load full user from DB (adds req.user with roles/permissions)
|
|
90
|
+
userLoader: async (uid) => {
|
|
91
|
+
const user = await User.with(['roles', 'roles.permissions']).find(uid);
|
|
92
|
+
if (!user) return null;
|
|
93
|
+
return {
|
|
94
|
+
id: user.id,
|
|
95
|
+
roles: user.roles.map(r => r.slug),
|
|
96
|
+
permissions: user.roles.flatMap(r => r.permissions.map(p => p.slug)),
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
// Optional: decrypt token before verification
|
|
100
|
+
decryptToken: (token) => myDecrypt(token),
|
|
101
|
+
}).toHandler();
|
|
102
|
+
|
|
103
|
+
app.use('/api/protected', auth);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If no `userLoader` is provided, `req.user` is populated from the JWT payload (`{ id: sub, roles, permissions }`).
|
|
107
|
+
|
|
108
|
+
### AuthorizeByStatusMiddleware
|
|
109
|
+
|
|
110
|
+
Rejects requests where `req.user.status !== 'active'` or `req.user.isActive()` returns false:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { authorizeByStatus } from '@lara-node/middlewares';
|
|
114
|
+
|
|
115
|
+
app.use('/api/protected', auth, authorizeByStatus);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### ErrorHandlerMiddleware
|
|
119
|
+
|
|
120
|
+
Express 4-argument error handler. Handles:
|
|
121
|
+
- `ValidationError` → 422 with `{ success: false, errors, messages }`
|
|
122
|
+
- Any `err.status` (400–599) → that status code
|
|
123
|
+
- Everything else → 500
|
|
124
|
+
- `err.stack` included in non-production responses
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { errorHandler } from '@lara-node/middlewares';
|
|
128
|
+
app.use(errorHandler); // must be registered last
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Authorization Helpers
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { authorizeRoles, authorizePermissions } from '@lara-node/middlewares';
|
|
135
|
+
|
|
136
|
+
// Allow only admin or moderator:
|
|
137
|
+
router.get('/admin', auth, authorizeRoles('admin', 'moderator'), handler);
|
|
138
|
+
|
|
139
|
+
// Require a specific permission:
|
|
140
|
+
router.delete('/users/:id', auth, authorizePermissions('delete_users'), handler);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Class-Based Usage
|
|
144
|
+
|
|
145
|
+
All middleware can also be used as classes (useful for testing or custom wiring):
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { AuthMiddleware } from '@lara-node/middlewares';
|
|
149
|
+
|
|
150
|
+
// Class form:
|
|
151
|
+
const authMiddleware = new AuthMiddleware({ userLoader });
|
|
152
|
+
app.use(authMiddleware.handle.bind(authMiddleware));
|
|
153
|
+
// or:
|
|
154
|
+
app.use(authMiddleware.toHandler());
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Express Type Augmentation
|
|
158
|
+
|
|
159
|
+
This package augments the Express `Request` and `Response` interfaces globally:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// req.user is typed:
|
|
163
|
+
req.user?.id // number | string
|
|
164
|
+
req.user?.roles // string[]
|
|
165
|
+
req.user?.permissions // string[]
|
|
166
|
+
|
|
167
|
+
// req.validate is typed:
|
|
168
|
+
const data = await req.validate<{ email: string }>(rules);
|
|
169
|
+
|
|
170
|
+
// res.jsonAsync is typed:
|
|
171
|
+
await res.jsonAsync(model);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You can also declare these locally in your project via `src/types/express.d.ts` — the `create-lara-node` scaffold generates this file automatically.
|
|
175
|
+
|
|
176
|
+
## Writing Custom Middleware
|
|
177
|
+
|
|
178
|
+
Follow the same class pattern:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { Request, Response, NextFunction } from 'express';
|
|
182
|
+
|
|
183
|
+
export class MaintenanceModeMiddleware {
|
|
184
|
+
handle(req: Request, res: Response, next: NextFunction): void {
|
|
185
|
+
if (process.env.MAINTENANCE === 'true') {
|
|
186
|
+
res.status(503).json({ message: 'Service temporarily unavailable.' });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
next();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
toHandler() {
|
|
193
|
+
return (req: Request, res: Response, next: NextFunction) =>
|
|
194
|
+
this.handle(req, res, next);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Async Context Store
|
|
200
|
+
|
|
201
|
+
The store is populated by `AsyncContextMiddleware` and augmented by `AuthMiddleware`:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { asyncLocalStorage } from '@lara-node/middlewares';
|
|
205
|
+
|
|
206
|
+
function getCurrentUser() {
|
|
207
|
+
return asyncLocalStorage.getStore()?.user ?? null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getCurrentRequest() {
|
|
211
|
+
return asyncLocalStorage.getStore()?.req;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lara-node/middlewares",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Predefined class-based middleware for Lara-Node applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"jsonwebtoken": "^9.0.2",
|
|
19
|
-
"@lara-node/validator": "0.1.
|
|
19
|
+
"@lara-node/validator": "0.1.2",
|
|
20
20
|
"@lara-node/db": "0.1.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|