exisjs 0.5.10 → 0.5.12
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/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oauth/core.d.ts +96 -0
- package/dist/auth/oauth/core.d.ts.map +1 -0
- package/dist/auth/oauth/core.js +203 -0
- package/dist/auth/oauth/core.js.map +1 -0
- package/dist/auth/oauth/index.d.ts +45 -0
- package/dist/auth/oauth/index.d.ts.map +1 -0
- package/dist/auth/oauth/index.js +64 -0
- package/dist/auth/oauth/index.js.map +1 -0
- package/dist/auth/oauth/middleware.d.ts +63 -0
- package/dist/auth/oauth/middleware.d.ts.map +1 -0
- package/dist/auth/oauth/middleware.js +164 -0
- package/dist/auth/oauth/middleware.js.map +1 -0
- package/dist/auth/oauth/providers/custom.d.ts +41 -0
- package/dist/auth/oauth/providers/custom.d.ts.map +1 -0
- package/dist/auth/oauth/providers/custom.js +204 -0
- package/dist/auth/oauth/providers/custom.js.map +1 -0
- package/dist/auth/oauth/providers/discord.d.ts +23 -0
- package/dist/auth/oauth/providers/discord.d.ts.map +1 -0
- package/dist/auth/oauth/providers/discord.js +130 -0
- package/dist/auth/oauth/providers/discord.js.map +1 -0
- package/dist/auth/oauth/providers/facebook.d.ts +28 -0
- package/dist/auth/oauth/providers/facebook.d.ts.map +1 -0
- package/dist/auth/oauth/providers/facebook.js +124 -0
- package/dist/auth/oauth/providers/facebook.js.map +1 -0
- package/dist/auth/oauth/providers/github.d.ts +25 -0
- package/dist/auth/oauth/providers/github.d.ts.map +1 -0
- package/dist/auth/oauth/providers/github.js +132 -0
- package/dist/auth/oauth/providers/github.js.map +1 -0
- package/dist/auth/oauth/providers/google.d.ts +23 -0
- package/dist/auth/oauth/providers/google.d.ts.map +1 -0
- package/dist/auth/oauth/providers/google.js +134 -0
- package/dist/auth/oauth/providers/google.js.map +1 -0
- package/dist/auth/oauth/providers/microsoft.d.ts +28 -0
- package/dist/auth/oauth/providers/microsoft.d.ts.map +1 -0
- package/dist/auth/oauth/providers/microsoft.js +136 -0
- package/dist/auth/oauth/providers/microsoft.js.map +1 -0
- package/dist/auth/oauth/types.d.ts +158 -0
- package/dist/auth/oauth/types.d.ts.map +1 -0
- package/dist/auth/oauth/types.js +4 -0
- package/dist/auth/oauth/types.js.map +1 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +0 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/database/adapters/mongodb.d.ts +58 -0
- package/dist/database/adapters/mongodb.d.ts.map +1 -0
- package/dist/database/adapters/mongodb.js +264 -0
- package/dist/database/adapters/mongodb.js.map +1 -0
- package/dist/database/adapters/mysql.d.ts +27 -0
- package/dist/database/adapters/mysql.d.ts.map +1 -0
- package/dist/database/adapters/mysql.js +188 -0
- package/dist/database/adapters/mysql.js.map +1 -0
- package/dist/database/adapters/postgres.d.ts +28 -0
- package/dist/database/adapters/postgres.d.ts.map +1 -0
- package/dist/database/adapters/postgres.js +174 -0
- package/dist/database/adapters/postgres.js.map +1 -0
- package/dist/database/adapters/sqlite.d.ts +27 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +149 -0
- package/dist/database/adapters/sqlite.js.map +1 -0
- package/dist/database/index.d.ts +42 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +58 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/manager.d.ts +114 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/manager.js +269 -0
- package/dist/database/manager.js.map +1 -0
- package/dist/database/migration.d.ts +57 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +329 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/query-builder.d.ts +89 -0
- package/dist/database/query-builder.d.ts.map +1 -0
- package/dist/database/query-builder.js +353 -0
- package/dist/database/query-builder.js.map +1 -0
- package/dist/database/types.d.ts +184 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +4 -0
- package/dist/database/types.js.map +1 -0
- package/dist/docs/ARCHITECTURE.md +349 -0
- package/dist/docs/BENCHMARKS.md +36 -0
- package/dist/docs/README.md +34 -0
- package/dist/docs/content/README.md +44 -0
- package/dist/docs/content/adapters.mdx +199 -0
- package/dist/docs/content/cache.mdx +239 -0
- package/dist/docs/content/circuit-breaker.mdx +130 -0
- package/dist/docs/content/cli/commands.mdx +214 -0
- package/dist/docs/content/cli/overview.mdx +92 -0
- package/dist/docs/content/cli/usage.mdx +61 -0
- package/dist/docs/content/config.mdx +214 -0
- package/dist/docs/content/controllers.mdx +241 -0
- package/dist/docs/content/cron.mdx +109 -0
- package/dist/docs/content/database.mdx +209 -0
- package/dist/docs/content/dataloader.mdx +170 -0
- package/dist/docs/content/decorators.mdx +204 -0
- package/dist/docs/content/errors.mdx +344 -0
- package/dist/docs/content/exisjs-mcp.mdx +97 -0
- package/dist/docs/content/gateways.mdx +376 -0
- package/dist/docs/content/installation.mdx +210 -0
- package/dist/docs/content/introduction.mdx +96 -0
- package/dist/docs/content/middleware.mdx +441 -0
- package/dist/docs/content/modules.mdx +144 -0
- package/dist/docs/content/oauth.mdx +179 -0
- package/dist/docs/content/providers.mdx +147 -0
- package/dist/docs/content/queues.mdx +177 -0
- package/dist/docs/content/requests.mdx +293 -0
- package/dist/docs/content/responses.mdx +271 -0
- package/dist/docs/content/routing.mdx +541 -0
- package/dist/docs/content/rpc.mdx +146 -0
- package/dist/docs/content/security.mdx +294 -0
- package/dist/docs/content/sse.mdx +159 -0
- package/dist/docs/content/structure.mdx +116 -0
- package/dist/docs/content/testing.mdx +139 -0
- package/dist/docs/content/validation.mdx +249 -0
- package/dist/docs/content/websockets.mdx +289 -0
- package/dist/docs/integrations/README.md +23 -0
- package/dist/docs/integrations/jwt.mdx +151 -0
- package/dist/docs/integrations/mongo.mdx +183 -0
- package/dist/docs/integrations/observability.mdx +125 -0
- package/dist/docs/integrations/openai.mdx +108 -0
- package/dist/docs/integrations/postgres.mdx +190 -0
- package/dist/docs/integrations/posthog.mdx +122 -0
- package/dist/docs/integrations/redis.mdx +141 -0
- package/dist/docs/integrations/resend.mdx +117 -0
- package/dist/docs/integrations/s3.mdx +122 -0
- package/dist/docs/integrations/swagger.mdx +113 -0
- package/dist/docs/reference/README.md +3 -0
- package/dist/docs/reference/adapters.mdx +130 -0
- package/dist/docs/reference/auth.mdx +183 -0
- package/dist/docs/reference/cache.mdx +132 -0
- package/dist/docs/reference/circuit-breaker.mdx +71 -0
- package/dist/docs/reference/config.mdx +118 -0
- package/dist/docs/reference/context.mdx +168 -0
- package/dist/docs/reference/dataloader.mdx +91 -0
- package/dist/docs/reference/decorators.mdx +293 -0
- package/dist/docs/reference/di.mdx +123 -0
- package/dist/docs/reference/error.mdx +170 -0
- package/dist/docs/reference/exis.mdx +124 -0
- package/dist/docs/reference/middleware.mdx +304 -0
- package/dist/docs/reference/plugin.mdx +113 -0
- package/dist/docs/reference/queue.mdx +155 -0
- package/dist/docs/reference/router.mdx +248 -0
- package/dist/docs/reference/sanitize.mdx +130 -0
- package/dist/docs/reference/storage.mdx +116 -0
- package/dist/docs/reference/swagger.mdx +132 -0
- package/dist/docs/reference/testing.mdx +145 -0
- package/dist/docs/reference/validator.mdx +186 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/compression.d.ts +12 -0
- package/dist/middleware/compression.d.ts.map +1 -1
- package/dist/middleware/compression.js +61 -99
- package/dist/middleware/compression.js.map +1 -1
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +25 -8
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/security.d.ts.map +1 -1
- package/dist/middleware/security.js +6 -3
- package/dist/middleware/security.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +11 -7
- package/dist/router/router.js.map +1 -1
- package/dist/server/bootstrapper.js +1 -1
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/bun-adapter.d.ts.map +1 -1
- package/dist/server/bun-adapter.js +2 -2
- package/dist/server/bun-adapter.js.map +1 -1
- package/dist/server/request-handler.d.ts +7 -0
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +37 -2
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +4 -6
- package/dist/server/request.js.map +1 -1
- package/dist/server/route-scanner.d.ts.map +1 -1
- package/dist/server/route-scanner.js +4 -1
- package/dist/server/route-scanner.js.map +1 -1
- package/dist/server/uws-adapter.d.ts +5 -3
- package/dist/server/uws-adapter.d.ts.map +1 -1
- package/dist/server/uws-adapter.js +39 -4
- package/dist/server/uws-adapter.js.map +1 -1
- package/dist/server/ws-orchestrator.d.ts.map +1 -1
- package/dist/server/ws-orchestrator.js +4 -13
- package/dist/server/ws-orchestrator.js.map +1 -1
- package/package.json +17 -10
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/modules"
|
|
3
|
+
nextTitle: "Modules & Plugins"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Middleware
|
|
7
|
+
|
|
8
|
+
ExisJS comes with a collection of built-in middleware out of the box.
|
|
9
|
+
|
|
10
|
+
ExisJS provides first-party implementations for features like CORS, Request Logging, Backpressure, and Deduplication.
|
|
11
|
+
|
|
12
|
+
All middleware can be imported directly from the `exisjs/middleware` package.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Where to Apply Middleware
|
|
17
|
+
|
|
18
|
+
ExisJS gives you granular control over where your middleware executes:
|
|
19
|
+
|
|
20
|
+
1. **Globally (`server.ts`)**: Applies to every request hitting your server.
|
|
21
|
+
2. **Folder-Scoped (`gateway.ts`)**: Applies only to routes inside that specific folder.
|
|
22
|
+
3. **Route-Scoped (`route.ts`)**: Applies only to a specific endpoint.
|
|
23
|
+
|
|
24
|
+
```typescript filename="src/http/admin/gateway.ts"
|
|
25
|
+
import { defineGateway } from 'exisjs/router'
|
|
26
|
+
import { requestLogger } from 'exisjs/middleware'
|
|
27
|
+
|
|
28
|
+
// This logger will ONLY run for requests to /admin/*
|
|
29
|
+
export default defineGateway({
|
|
30
|
+
middlewares: [
|
|
31
|
+
requestLogger({ level: 'debug' })
|
|
32
|
+
]
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Middleware vs Inline Execution
|
|
37
|
+
|
|
38
|
+
It is critical to understand that middleware executes **before** your route handler. If you are using [Custom Typed Contexts](/docs/requests#strongly-typed-context--native-destructuring) to destructure variables (e.g., `async handle({ user })`), you **must** use middleware to populate `req.user`.
|
|
39
|
+
|
|
40
|
+
If you try to call an authentication function *inline* inside the handler, the `user` variable will be `undefined` because destructuring occurs at the moment the handler is invoked—before the inline function has a chance to execute!
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// ❌ BAD: Destructuring happens before requireAuth mutates the request
|
|
44
|
+
create: route.post('/', {
|
|
45
|
+
async handle({ user, req }) {
|
|
46
|
+
requireAuth(req);
|
|
47
|
+
console.log(user); // undefined
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// ✅ GOOD: Middleware runs first, so `user` is fully populated and strongly typed
|
|
52
|
+
create: route.post('/', {
|
|
53
|
+
middleware: [requireAuth],
|
|
54
|
+
async handle({ user }) {
|
|
55
|
+
console.log(user); // Populated and typed
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Functional Paradigm
|
|
61
|
+
|
|
62
|
+
```typescript filename="src/http/route.ts"
|
|
63
|
+
import { controller, route } from 'exisjs/router'
|
|
64
|
+
import { validate } from 'exisjs/middleware'
|
|
65
|
+
|
|
66
|
+
export default controller({
|
|
67
|
+
// This middleware ONLY runs for this specific endpoint
|
|
68
|
+
create: route.post('/', {
|
|
69
|
+
middlewares: [validate(mySchema)],
|
|
70
|
+
handle(req, res) {
|
|
71
|
+
return { ok: true }
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Class-Based (OOP) Paradigm
|
|
78
|
+
|
|
79
|
+
```typescript filename="src/http/route.ts"
|
|
80
|
+
import { Controller, Post, Use } from 'exisjs/decorators'
|
|
81
|
+
import { validate } from 'exisjs/middleware'
|
|
82
|
+
|
|
83
|
+
@Controller()
|
|
84
|
+
export default class MyController {
|
|
85
|
+
// This middleware ONLY runs for this specific endpoint
|
|
86
|
+
@Use(validate(mySchema))
|
|
87
|
+
@Post('/')
|
|
88
|
+
async create() {
|
|
89
|
+
return { ok: true }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 1. CORS
|
|
97
|
+
|
|
98
|
+
The `cors` middleware handles Cross-Origin Resource Sharing. It is configurable and optimized for low overhead on preflight requests.
|
|
99
|
+
|
|
100
|
+
The recommended practice in ExisJS is to configure CORS globally in your **`exis.config.ts`** file (the Single Source of Truth). The framework will automatically merge and apply it globally for you.
|
|
101
|
+
|
|
102
|
+
```typescript filename="exis.config.ts"
|
|
103
|
+
import { defineConfig } from 'exisjs/config'
|
|
104
|
+
|
|
105
|
+
export default async () => defineConfig({
|
|
106
|
+
cors: {
|
|
107
|
+
origin: ['https://myapp.com', /localhost:\d+/],
|
|
108
|
+
methods: ['GET', 'POST', 'OPTIONS'],
|
|
109
|
+
credentials: true,
|
|
110
|
+
maxAge: 86400,
|
|
111
|
+
preflightContinue: true // Set this to true if you want OPTIONS requests to hit your custom route.options() handlers!
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
By default, ExisJS intercepts `OPTIONS` requests at the middleware level and responds with a `204 No Content` (and the appropriate CORS headers) to save server resources. However, if you need to run custom logic on preflight requests—such as hitting a specific `route.options('/', ...)` handler or a `@Options()` decorated controller method—you can set `preflightContinue: true`.
|
|
117
|
+
|
|
118
|
+
You can also configure CORS locally per-route by passing it to your controller config or gateway.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 2. Request Logger
|
|
123
|
+
|
|
124
|
+
ExisJS uses **Pino** under the hood. The `requestLogger` injects a request-scoped logger instance (`req.log`) and times response cycles automatically.
|
|
125
|
+
|
|
126
|
+
```typescript filename="src/http/server.ts"
|
|
127
|
+
import { requestLogger } from 'exisjs/middleware'
|
|
128
|
+
|
|
129
|
+
app.use(requestLogger({
|
|
130
|
+
level: 'debug',
|
|
131
|
+
pretty: true // Enable beautiful CLI formatting
|
|
132
|
+
}))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### Functional Paradigm
|
|
136
|
+
|
|
137
|
+
```typescript filename="src/http/route.ts"
|
|
138
|
+
import { controller, route } from 'exisjs/router'
|
|
139
|
+
|
|
140
|
+
export default controller({
|
|
141
|
+
index: route.get('/', {
|
|
142
|
+
handle(req, res) {
|
|
143
|
+
req.log.info({ userId: 123 }, "User hit the index route")
|
|
144
|
+
return { ok: true }
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Class-Based (OOP) Paradigm
|
|
151
|
+
|
|
152
|
+
```typescript filename="src/http/route.ts"
|
|
153
|
+
import { Controller, Get, Req } from 'exisjs/decorators'
|
|
154
|
+
|
|
155
|
+
@Controller()
|
|
156
|
+
export default class MyController {
|
|
157
|
+
@Get('/')
|
|
158
|
+
async index(@Req() req: any) {
|
|
159
|
+
req.log.info({ userId: 123 }, "User hit the index route")
|
|
160
|
+
return { ok: true }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 3. Request ID
|
|
168
|
+
|
|
169
|
+
Injects a unique `X-Request-Id` into every incoming request and outgoing response header. It natively supports distributed tracing headers (`traceparent`, `x-b3-traceid`).
|
|
170
|
+
|
|
171
|
+
```typescript filename="src/http/server.ts"
|
|
172
|
+
import { requestId } from 'exisjs/middleware'
|
|
173
|
+
|
|
174
|
+
app.use(requestId())
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Functional Paradigm
|
|
178
|
+
|
|
179
|
+
```typescript filename="src/http/route.ts"
|
|
180
|
+
import { controller, route } from 'exisjs/router'
|
|
181
|
+
|
|
182
|
+
export default controller({
|
|
183
|
+
index: route.get('/', {
|
|
184
|
+
handle(req, res) {
|
|
185
|
+
console.log("Trace ID:", req.requestId)
|
|
186
|
+
return { ok: true }
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Class-Based (OOP) Paradigm
|
|
193
|
+
|
|
194
|
+
```typescript filename="src/http/route.ts"
|
|
195
|
+
import { Controller, Get, Req } from 'exisjs/decorators'
|
|
196
|
+
|
|
197
|
+
@Controller()
|
|
198
|
+
export default class MyController {
|
|
199
|
+
@Get('/')
|
|
200
|
+
async index(@Req() req: any) {
|
|
201
|
+
console.log("Trace ID:", req.requestId)
|
|
202
|
+
return { ok: true }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 4. Static File Serving
|
|
210
|
+
|
|
211
|
+
A static file server that resolves MIME types, streams files, and provides Cache-Control configuration out of the box.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { serveStatic } from 'exisjs/middleware'
|
|
215
|
+
import path from 'node:path'
|
|
216
|
+
|
|
217
|
+
app.use('/public', serveStatic(path.join(process.cwd(), 'public'), {
|
|
218
|
+
maxAge: 31536000 // Cache for 1 year
|
|
219
|
+
}))
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 5. Security Middlewares
|
|
225
|
+
|
|
226
|
+
ExisJS bundles several security middlewares. (We will cover these in the Security documentation, but here is a preview):
|
|
227
|
+
|
|
228
|
+
- **`helmet()`**: Automatically sets 14+ secure HTTP headers.
|
|
229
|
+
- **`hpp()`**: Prevents HTTP Parameter Pollution attacks.
|
|
230
|
+
- **`dbSanitize()`**: A universal scraper that protects against NoSQL injection patterns (`$where` and other malicious operators).
|
|
231
|
+
- **`timeout(ms)`**: Kills requests that hang longer than a specified duration.
|
|
232
|
+
- **`ipFilter()`**: Restricts access based on IP Whitelists/Blacklists.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { helmet, timeout, dbSanitize } from 'exisjs/middleware'
|
|
236
|
+
|
|
237
|
+
app.use(helmet())
|
|
238
|
+
app.use(dbSanitize({ mongo: true }))
|
|
239
|
+
app.use(timeout(5000)) // 5 second timeout
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 6. Advanced Performance Middlewares
|
|
245
|
+
|
|
246
|
+
These middlewares provide additional scaling and performance capabilities.
|
|
247
|
+
|
|
248
|
+
- **`cache()`**: An in-memory route caching engine. Requires a `keyGenerator` to prevent data leaks.
|
|
249
|
+
- **`dedupe()`**: Request deduplication (In-flight request collapsing). If 1,000 users hit a heavy `/stats` route at the exact same millisecond, ExisJS will only compute it **once**, and send the same response to all 1,000 users simultaneously. Requires a `keyGenerator`.
|
|
250
|
+
- **`backpressure()`**: Protects your server from traffic spikes by shedding load or queuing requests when the Node.js Event Loop lags.
|
|
251
|
+
|
|
252
|
+
> [!WARNING]
|
|
253
|
+
> **Authentication Leak Hazard**: When using `cache()` or `dedupe()` on authenticated routes, you **must** include the user's ID or session token in your `keyGenerator`. Failing to do so will result in User B receiving a cached/deduped response generated for User A.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 7. Interceptors & Error Filters
|
|
258
|
+
|
|
259
|
+
As covered in our [Error Handling](/docs/errors) guide, you can centrally mutate responses or catch specific Exception classes using `intercept()` and `catchError()`.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { intercept, catchError } from 'exisjs/middleware'
|
|
263
|
+
|
|
264
|
+
// Wrap all responses in { data: ... }
|
|
265
|
+
app.use(intercept((data) => ({ data })))
|
|
266
|
+
|
|
267
|
+
// Catch specific errors
|
|
268
|
+
app.use(catchError(DatabaseError, (err, req, res) => {
|
|
269
|
+
res.status(503).json({ error: 'Database Offline' })
|
|
270
|
+
}))
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 8. Schema Validation
|
|
276
|
+
|
|
277
|
+
ExisJS includes a validation engine. You can validate `body`, `query`, or `params` directly at the middleware layer. If validation fails, it automatically rejects the request with a structured HTTP 400 JSON response.
|
|
278
|
+
|
|
279
|
+
#### Functional Paradigm
|
|
280
|
+
|
|
281
|
+
```typescript filename="src/http/users/route.ts"
|
|
282
|
+
import { controller, route } from 'exisjs/router'
|
|
283
|
+
import { validate } from 'exisjs/middleware'
|
|
284
|
+
import { tex } from 'exisjs/validator'
|
|
285
|
+
|
|
286
|
+
const createUserSchema = {
|
|
287
|
+
body: tex.object({
|
|
288
|
+
email: tex.email(),
|
|
289
|
+
age: tex.number({ min: 18 })
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export default controller({
|
|
294
|
+
// Automatically rejects invalid payloads with 400 Bad Request
|
|
295
|
+
create: route.post('/', {
|
|
296
|
+
middlewares: [validate(createUserSchema)],
|
|
297
|
+
handle(req, res) {
|
|
298
|
+
// req.body is fully validated, but we also type-cast it safely
|
|
299
|
+
const body = req.body as { email: string, age: number }
|
|
300
|
+
|
|
301
|
+
return { success: true, email: body.email }
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Class-Based (OOP) Paradigm
|
|
308
|
+
|
|
309
|
+
```typescript filename="src/http/users/route.ts"
|
|
310
|
+
import { Controller, Post, Use, Body } from 'exisjs/decorators'
|
|
311
|
+
import { validate } from 'exisjs/middleware'
|
|
312
|
+
import { tex } from 'exisjs/validator'
|
|
313
|
+
|
|
314
|
+
const createUserSchema = {
|
|
315
|
+
body: tex.object({
|
|
316
|
+
email: tex.email(),
|
|
317
|
+
age: tex.number({ min: 18 })
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
@Controller()
|
|
322
|
+
export default class UsersController {
|
|
323
|
+
// Automatically rejects invalid payloads with 400 Bad Request
|
|
324
|
+
@Use(validate(createUserSchema))
|
|
325
|
+
@Post('/')
|
|
326
|
+
async create(@Body() body: { email: string, age: number }) {
|
|
327
|
+
return { success: true, email: body.email }
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 9. Idempotency
|
|
335
|
+
|
|
336
|
+
Idempotency ensures that retrying an identical request multiple times (e.g., due to network timeouts) will not result in duplicate operations on the server. ExisJS provides a Redis-backed (or in-memory) idempotency engine.
|
|
337
|
+
|
|
338
|
+
#### Functional Paradigm
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { controller, route } from 'exisjs/router'
|
|
342
|
+
import { IdempotentMiddleware } from 'exisjs/middleware'
|
|
343
|
+
|
|
344
|
+
export default controller({
|
|
345
|
+
checkout: route.post('/checkout', {
|
|
346
|
+
middlewares: [IdempotentMiddleware({ ttl: 86400, headerName: 'Idempotency-Key' })],
|
|
347
|
+
handle(req, res) {
|
|
348
|
+
return { success: true }
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### Class-Based (OOP) Paradigm
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { Controller, Post, Idempotent } from 'exisjs/decorators'
|
|
358
|
+
|
|
359
|
+
@Controller()
|
|
360
|
+
export default class CheckoutController {
|
|
361
|
+
@Idempotent({ ttl: 86400 }) // Caches the response for 24 hours based on the Idempotency-Key header
|
|
362
|
+
@Post('/checkout')
|
|
363
|
+
async checkout() {
|
|
364
|
+
return { success: true }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 10. Cache Engine
|
|
372
|
+
|
|
373
|
+
ExisJS provides a built-in Cache Engine for `GET` requests. It allows you to reduce database load and response times for frequently requested data.
|
|
374
|
+
|
|
375
|
+
#### Functional Paradigm
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { controller, route } from 'exisjs/router'
|
|
379
|
+
import { cache } from 'exisjs/middleware'
|
|
380
|
+
|
|
381
|
+
export default controller({
|
|
382
|
+
popular: route.get('/popular', {
|
|
383
|
+
middlewares: [
|
|
384
|
+
cache({
|
|
385
|
+
ttlMs: 60000,
|
|
386
|
+
keyGenerator: (req) => req.url // Critical: Define how cache keys are built
|
|
387
|
+
})
|
|
388
|
+
],
|
|
389
|
+
handle(req, res) {
|
|
390
|
+
return { success: true, books: [] }
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### Class-Based (OOP) Paradigm
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
import { Controller, Get, Cache } from 'exisjs/decorators'
|
|
400
|
+
|
|
401
|
+
@Controller()
|
|
402
|
+
export default class BooksController {
|
|
403
|
+
@Cache({ ttlMs: 60000, keyGenerator: (req) => req.url }) // Caches the response for 60 seconds
|
|
404
|
+
@Get('/popular')
|
|
405
|
+
async getPopular() {
|
|
406
|
+
return { success: true, books: [] }
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 11. Compression (Gzip / Brotli / Deflate)
|
|
414
|
+
|
|
415
|
+
ExisJS includes a stream-based Compression middleware. It natively hooks into the `uWebSockets` core and compresses JSON, streams, and text on-the-fly, reducing your outbound network bandwidth.
|
|
416
|
+
|
|
417
|
+
The best practice is to configure Compression globally in your **`exis.config.ts`**. By default, if `compression: true` is passed, the framework will add the `compress` middleware for you automatically.
|
|
418
|
+
|
|
419
|
+
```typescript filename="exis.config.ts"
|
|
420
|
+
import type { ExisConfig } from 'exisjs/config'
|
|
421
|
+
|
|
422
|
+
const config: ExisConfig = {
|
|
423
|
+
// Enables Gzip, Brotli, and Deflate compression globally for all routes
|
|
424
|
+
compression: true,
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export default config
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
If you prefer to manually apply it to specific routes or gateways instead of globally, you can import it:
|
|
431
|
+
|
|
432
|
+
```typescript filename="src/http/api/gateway.ts"
|
|
433
|
+
import { defineGateway } from 'exisjs/router'
|
|
434
|
+
import { compress } from 'exisjs/middleware'
|
|
435
|
+
|
|
436
|
+
export default defineGateway({
|
|
437
|
+
middlewares: [
|
|
438
|
+
compress() // Only compress responses within this Gateway
|
|
439
|
+
]
|
|
440
|
+
})
|
|
441
|
+
```
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/cache"
|
|
3
|
+
nextTitle: "Caching"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Modules & Plugins
|
|
7
|
+
|
|
8
|
+
ExisJS provides a modular architecture. The framework is built on a Plugin Engine that allows you to encapsulate functionality, inject dependencies, and distribute reusable packages across your organization.
|
|
9
|
+
|
|
10
|
+
ExisJS offers two distinct primitives for extension:
|
|
11
|
+
1. **Modules (`defineModule`)**: Used for grouping domain-level business logic (e.g., `AuthModule`, `UsersModule`).
|
|
12
|
+
2. **Plugins (`definePlugin`)**: Used for building framework-level extensions that often require configuration options (e.g., `StripePlugin({ apiKey })`).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Domain Modules (`defineModule`)
|
|
17
|
+
|
|
18
|
+
When building large-scale applications, you should avoid placing all your logic into a single file. Modules allow you to encapsulate related dependencies (Providers), sub-modules (Imports), and Routes into a single unit.
|
|
19
|
+
|
|
20
|
+
### Defining a Module
|
|
21
|
+
|
|
22
|
+
Let's build an `AuthModule` that encapsulates an authentication provider:
|
|
23
|
+
|
|
24
|
+
```typescript filename="src/modules/auth.module.ts"
|
|
25
|
+
import { defineModule } from 'exisjs/module'
|
|
26
|
+
|
|
27
|
+
export const AuthModule = defineModule({
|
|
28
|
+
name: 'auth-module',
|
|
29
|
+
|
|
30
|
+
// 1. Inject Dependencies
|
|
31
|
+
providers: [
|
|
32
|
+
['AuthService', { useClass: class AuthService { login() {} } }]
|
|
33
|
+
],
|
|
34
|
+
|
|
35
|
+
// 2. Import other modules
|
|
36
|
+
imports: [
|
|
37
|
+
DatabaseModule
|
|
38
|
+
],
|
|
39
|
+
|
|
40
|
+
// 3. Register lifecycle hooks
|
|
41
|
+
async onStart(app) {
|
|
42
|
+
console.log("AuthModule has initialized")
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Registering Modules
|
|
48
|
+
|
|
49
|
+
Once your module is defined, you can register it globally. The recommended place to register core modules is inside your `exis.config.ts` file, ensuring they boot up synchronously before your server starts accepting traffic.
|
|
50
|
+
|
|
51
|
+
```typescript filename="exis.config.ts"
|
|
52
|
+
import { defineExisConfig } from 'exisjs/config'
|
|
53
|
+
import { AuthModule } from './src/modules/auth.module'
|
|
54
|
+
|
|
55
|
+
export default defineExisConfig({
|
|
56
|
+
plugins: [
|
|
57
|
+
AuthModule
|
|
58
|
+
]
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Alternatively, you can register it manually inside your `src/http/server.ts`:
|
|
63
|
+
|
|
64
|
+
#### Functional Paradigm
|
|
65
|
+
|
|
66
|
+
```typescript filename="src/http/server.ts"
|
|
67
|
+
import { exis } from 'exisjs'
|
|
68
|
+
import { AuthModule } from '../modules/auth.module'
|
|
69
|
+
|
|
70
|
+
export default exis({
|
|
71
|
+
async onStart(app) {
|
|
72
|
+
await app.register(AuthModule)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Class-Based (OOP) Paradigm
|
|
78
|
+
|
|
79
|
+
```typescript filename="src/http/server.ts"
|
|
80
|
+
import { Server } from 'exisjs/decorators'
|
|
81
|
+
import { AuthModule } from '../modules/auth.module'
|
|
82
|
+
import type { App } from 'exisjs'
|
|
83
|
+
|
|
84
|
+
@Server({
|
|
85
|
+
plugins: [AuthModule]
|
|
86
|
+
})
|
|
87
|
+
export default class RootServer {
|
|
88
|
+
async onStart(app: App) {
|
|
89
|
+
console.log("Server with AuthModule booted")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 2. Framework Plugins (`definePlugin`)
|
|
97
|
+
|
|
98
|
+
While `defineModule` is great for internal business logic, `definePlugin` is designed for building distributable, third-party packages that require configuration options.
|
|
99
|
+
|
|
100
|
+
When you use `definePlugin`, ExisJS returns a **Hybrid Object**. This means the plugin can be registered directly if it doesn't need options, or it can be called as a function if you need to pass a configuration object.
|
|
101
|
+
|
|
102
|
+
### Building a Plugin
|
|
103
|
+
|
|
104
|
+
Let's build a `StripePlugin` that requires an API key:
|
|
105
|
+
|
|
106
|
+
```typescript filename="src/plugins/stripe.plugin.ts"
|
|
107
|
+
import { definePlugin } from 'exisjs/plugin'
|
|
108
|
+
|
|
109
|
+
interface StripeOptions {
|
|
110
|
+
apiKey: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const StripePlugin = definePlugin<StripeOptions>({
|
|
114
|
+
name: 'stripe-plugin',
|
|
115
|
+
|
|
116
|
+
async register(app, options) {
|
|
117
|
+
if (!options?.apiKey) {
|
|
118
|
+
throw new Error("Stripe API Key is required")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Initialize Stripe client and inject it globally
|
|
122
|
+
const stripeClient = new Stripe(options.apiKey)
|
|
123
|
+
|
|
124
|
+
app.provide('StripeClient', { useValue: stripeClient })
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Using the Plugin
|
|
130
|
+
|
|
131
|
+
Because we used `definePlugin`, the framework allows us to pass configuration options directly:
|
|
132
|
+
|
|
133
|
+
```typescript filename="exis.config.ts"
|
|
134
|
+
import { defineExisConfig } from 'exisjs/config'
|
|
135
|
+
import { StripePlugin } from './src/plugins/stripe.plugin'
|
|
136
|
+
import { env } from './src/env'
|
|
137
|
+
|
|
138
|
+
export default defineExisConfig({
|
|
139
|
+
plugins: [
|
|
140
|
+
// We call the plugin as a function to pass options
|
|
141
|
+
StripePlugin({ apiKey: env.STRIPE_SECRET_KEY })
|
|
142
|
+
]
|
|
143
|
+
})
|
|
144
|
+
```
|