@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.
Files changed (2) hide show
  1. package/README.md +217 -0
  2. 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.2",
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.1",
19
+ "@lara-node/validator": "0.1.2",
20
20
  "@lara-node/db": "0.1.1"
21
21
  },
22
22
  "devDependencies": {