exisjs 0.5.10 → 0.5.11
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/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/server/bootstrapper.js +1 -1
- package/dist/server/bootstrapper.js.map +1 -1
- package/package.json +8 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/validation"
|
|
3
|
+
nextTitle: "Validation"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gateways (Middleware & Modules)
|
|
7
|
+
|
|
8
|
+
Gateways act as a configuration entry point for specific folders in your file-system routing architecture.
|
|
9
|
+
|
|
10
|
+
Instead of writing programmatic middleware declarations like `app.use('/admin', requireAuth)`, you can place a `gateway.ts` file inside a folder to scope **Middleware**, **CORS**, and **Dependency Injection** to that specific domain.
|
|
11
|
+
|
|
12
|
+
## Cascading Middleware
|
|
13
|
+
|
|
14
|
+
The most common use-case for a gateway is applying middleware arrays to a group of routes.
|
|
15
|
+
|
|
16
|
+
### Functional Paradigm
|
|
17
|
+
|
|
18
|
+
```typescript className="language-typescript" filename="src/http/admin/gateway.ts"
|
|
19
|
+
import { defineGateway } from 'exisjs/router'
|
|
20
|
+
import { requireAuth } from '../../middleware/auth'
|
|
21
|
+
import { cors, helmet } from 'exisjs/middleware'
|
|
22
|
+
|
|
23
|
+
export default defineGateway({
|
|
24
|
+
// This middleware array runs before ANY route in /admin/*
|
|
25
|
+
middleware: [
|
|
26
|
+
helmet(),
|
|
27
|
+
compress(),
|
|
28
|
+
cache({ ttlMs: 60000 }),
|
|
29
|
+
requireAuth
|
|
30
|
+
],
|
|
31
|
+
|
|
32
|
+
// Enforce strict CORS for this folder
|
|
33
|
+
cors: {
|
|
34
|
+
origin: 'https://admin.yourdomain.com',
|
|
35
|
+
methods: ['GET', 'POST', 'DELETE']
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Append custom default headers to all responses
|
|
39
|
+
headers: {
|
|
40
|
+
'X-Admin-Api-Version': '2.0'
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Class-Based (OOP) Paradigm
|
|
46
|
+
|
|
47
|
+
```typescript className="language-typescript" filename="src/http/admin/gateway.ts"
|
|
48
|
+
import { Gateway } from 'exisjs/decorators'
|
|
49
|
+
import { requireAuth } from '../../middleware/auth'
|
|
50
|
+
import { helmet } from 'exisjs/middleware'
|
|
51
|
+
|
|
52
|
+
@Gateway({
|
|
53
|
+
// This middleware array runs before ANY route in /admin/*
|
|
54
|
+
middleware: [
|
|
55
|
+
helmet(),
|
|
56
|
+
compress(),
|
|
57
|
+
cache({ ttlMs: 60000 }),
|
|
58
|
+
requireAuth
|
|
59
|
+
],
|
|
60
|
+
|
|
61
|
+
// Enforce strict CORS for this folder
|
|
62
|
+
cors: {
|
|
63
|
+
origin: 'https://admin.yourdomain.com',
|
|
64
|
+
methods: ['GET', 'POST', 'DELETE']
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Append custom default headers to all responses
|
|
68
|
+
headers: {
|
|
69
|
+
'X-Admin-Api-Version': '2.0'
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
export default class AdminGateway {}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
When a request comes in for `/admin/users`, ExisJS processes the Gateway's middleware sequentially before it ever reaches your `route.ts` file.
|
|
76
|
+
|
|
77
|
+
## Folder-Scoped CORS
|
|
78
|
+
|
|
79
|
+
Handling CORS across an API can require granular control. For instance, you might want your `/public` folder to accept requests from anywhere, while restricting your `/billing` folder to a strict frontend domain. ExisJS allows you to define the `cors` property in your Gateway, overriding the global configuration for that specific folder.
|
|
80
|
+
|
|
81
|
+
## File-Based Modules (Domain-Driven Design)
|
|
82
|
+
|
|
83
|
+
In addition to serving as an interceptor for middleware, `gateway.ts` acts as a module boundary, enabling **Domain-Driven Design (DDD)** via the file system.
|
|
84
|
+
|
|
85
|
+
### Providers (Dependency Injection)
|
|
86
|
+
|
|
87
|
+
If you have a database service unique to your `/users` domain, encapsulate it strictly inside the users gateway:
|
|
88
|
+
|
|
89
|
+
```typescript className="language-typescript" filename="src/http/users/gateway.ts"
|
|
90
|
+
import { defineGateway } from 'exisjs/router'
|
|
91
|
+
import { UsersRepository } from './repository'
|
|
92
|
+
|
|
93
|
+
export default defineGateway({
|
|
94
|
+
// Provide the UsersRepository directly to this folder's DI container!
|
|
95
|
+
providers: [
|
|
96
|
+
['UsersRepository', { useClass: UsersRepository }]
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
When ExisJS boots, it automatically discovers `gateway.ts`, parses the `providers`, and hydrates them into the internal Dependency Injection container before executing the routes. Inside your route handler, you can inject them:
|
|
102
|
+
|
|
103
|
+
#### Functional Paradigm
|
|
104
|
+
|
|
105
|
+
```typescript className="language-typescript" filename="src/http/users/route.ts"
|
|
106
|
+
import { controller, route } from 'exisjs/router'
|
|
107
|
+
import { inject } from 'exisjs/di'
|
|
108
|
+
import type { UsersRepository } from './repository'
|
|
109
|
+
|
|
110
|
+
export default controller({
|
|
111
|
+
list: route.get('/', {
|
|
112
|
+
async handle() {
|
|
113
|
+
// Access the scoped dependency
|
|
114
|
+
const usersRepo = inject<UsersRepository>('UsersRepository')
|
|
115
|
+
return await usersRepo.findAll()
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Class-Based (OOP) Paradigm
|
|
122
|
+
|
|
123
|
+
```typescript className="language-typescript" filename="src/http/users/route.ts"
|
|
124
|
+
import { Controller, Get } from 'exisjs/decorators'
|
|
125
|
+
import { inject } from 'exisjs/di'
|
|
126
|
+
import type { UsersRepository } from './repository'
|
|
127
|
+
|
|
128
|
+
@Controller()
|
|
129
|
+
export default class UsersController {
|
|
130
|
+
@Get('/')
|
|
131
|
+
async list() {
|
|
132
|
+
const usersRepo = inject<UsersRepository>('UsersRepository')
|
|
133
|
+
return await usersRepo.findAll()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Imports & Plugins
|
|
139
|
+
|
|
140
|
+
Gateways allow you to bundle other modules and plugins into a domain.
|
|
141
|
+
|
|
142
|
+
### Functional Paradigm
|
|
143
|
+
|
|
144
|
+
```typescript className="language-typescript" filename="src/http/orders/gateway.ts"
|
|
145
|
+
import { defineGateway } from 'exisjs/router'
|
|
146
|
+
import AuthModule from '../auth/module'
|
|
147
|
+
import { stripePlugin } from '../../plugins/stripe'
|
|
148
|
+
|
|
149
|
+
export default defineGateway({
|
|
150
|
+
// Pull in the Auth feature set
|
|
151
|
+
imports: [AuthModule],
|
|
152
|
+
|
|
153
|
+
// Isolate Stripe setup for the orders domain
|
|
154
|
+
plugins: [stripePlugin({ apiKey: process.env.STRIPE_KEY })]
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Class-Based (OOP) Paradigm
|
|
159
|
+
|
|
160
|
+
```typescript className="language-typescript" filename="src/http/orders/gateway.ts"
|
|
161
|
+
import { Gateway } from 'exisjs/decorators'
|
|
162
|
+
import AuthModule from '../auth/module'
|
|
163
|
+
import { stripePlugin } from '../../plugins/stripe'
|
|
164
|
+
|
|
165
|
+
@Gateway({
|
|
166
|
+
// Pull in the Auth feature set
|
|
167
|
+
imports: [AuthModule],
|
|
168
|
+
|
|
169
|
+
// Isolate Stripe setup for the orders domain
|
|
170
|
+
plugins: [stripePlugin({ apiKey: process.env.STRIPE_KEY })]
|
|
171
|
+
})
|
|
172
|
+
export default class OrdersGateway {}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
<Hint>
|
|
176
|
+
- **Imports**: Automatically deduplicated. If multiple gateways import `AuthModule`, its internal singletons are only initialized once.
|
|
177
|
+
- **Plugins**: Setup logic runs specifically for the scoped folder.
|
|
178
|
+
</Hint>
|
|
179
|
+
|
|
180
|
+
## Gateway Inheritance (Cascading)
|
|
181
|
+
|
|
182
|
+
Gateways inherently **cascade**. If you define a Gateway at the absolute root (`src/http/gateway.ts`), it applies globally.
|
|
183
|
+
|
|
184
|
+
If you define another Gateway at `src/http/admin/gateway.ts`, requests to `/admin/dashboard` will sequentially pass through the Root Gateway, and *then* pass through the Admin Gateway!
|
|
185
|
+
|
|
186
|
+
```text className="language-text"
|
|
187
|
+
src/
|
|
188
|
+
└── http/
|
|
189
|
+
├── gateway.ts <-- Runs first (Global Rate Limits)
|
|
190
|
+
└── admin/
|
|
191
|
+
├── gateway.ts <-- Runs second (JWT Validation)
|
|
192
|
+
└── users/
|
|
193
|
+
└── route.ts <-- Runs last (Only if both gateways succeed!)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Advanced Features
|
|
197
|
+
|
|
198
|
+
Gateways provide features to manage cross-cutting concerns like Exception Filters or Guards for an entire folder.
|
|
199
|
+
|
|
200
|
+
### 1. Folder-Scoped Guards
|
|
201
|
+
|
|
202
|
+
Apply Authorization Guards directly to a folder. If the guard returns `false`, the entire folder is protected.
|
|
203
|
+
|
|
204
|
+
#### Functional Paradigm
|
|
205
|
+
|
|
206
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
207
|
+
import { defineGateway } from 'exisjs/router'
|
|
208
|
+
import { RequireAdminRoleGuard } from '../../guards'
|
|
209
|
+
|
|
210
|
+
export default defineGateway({
|
|
211
|
+
// Protects the entire /api folder
|
|
212
|
+
guards: [RequireAdminRoleGuard]
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Class-Based (OOP) Paradigm
|
|
217
|
+
|
|
218
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
219
|
+
import { Gateway } from 'exisjs/decorators'
|
|
220
|
+
import { RequireAdminRoleGuard } from '../../guards'
|
|
221
|
+
|
|
222
|
+
@Gateway({
|
|
223
|
+
// Protects the entire /api folder
|
|
224
|
+
guards: [RequireAdminRoleGuard]
|
|
225
|
+
})
|
|
226
|
+
export default class ApiGateway {}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 2. Folder-Scoped Exception Filters
|
|
230
|
+
|
|
231
|
+
Catch exceptions for a specific domain. For example, you can return HTML error pages for `/views`, but standardized JSON errors for `/api`.
|
|
232
|
+
|
|
233
|
+
#### Functional Paradigm
|
|
234
|
+
|
|
235
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
236
|
+
import { defineGateway } from 'exisjs/router'
|
|
237
|
+
import { ApiVersion2ErrorFilter } from '../../filters'
|
|
238
|
+
|
|
239
|
+
export default defineGateway({
|
|
240
|
+
// Catches all exceptions thrown inside /api/*
|
|
241
|
+
filters: [ApiVersion2ErrorFilter]
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Class-Based (OOP) Paradigm
|
|
246
|
+
|
|
247
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
248
|
+
import { Gateway } from 'exisjs/decorators'
|
|
249
|
+
import { ApiVersion2ErrorFilter } from '../../filters'
|
|
250
|
+
|
|
251
|
+
@Gateway({
|
|
252
|
+
// Catches all exceptions thrown inside /api/*
|
|
253
|
+
filters: [ApiVersion2ErrorFilter]
|
|
254
|
+
})
|
|
255
|
+
export default class ApiGateway {}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 3. Folder-Scoped Interceptors
|
|
259
|
+
|
|
260
|
+
Transform or wrap all responses exiting a specific folder.
|
|
261
|
+
|
|
262
|
+
#### Functional Paradigm
|
|
263
|
+
|
|
264
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
265
|
+
import { defineGateway } from 'exisjs/router'
|
|
266
|
+
import { StandardResponseInterceptor } from '../../interceptors'
|
|
267
|
+
|
|
268
|
+
export default defineGateway({
|
|
269
|
+
// Wraps all responses from /api/* in a standard format
|
|
270
|
+
interceptors: [StandardResponseInterceptor]
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Class-Based (OOP) Paradigm
|
|
275
|
+
|
|
276
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
277
|
+
import { Gateway } from 'exisjs/decorators'
|
|
278
|
+
import { StandardResponseInterceptor } from '../../interceptors'
|
|
279
|
+
|
|
280
|
+
@Gateway({
|
|
281
|
+
// Wraps all responses from /api/* in a standard format
|
|
282
|
+
interceptors: [StandardResponseInterceptor]
|
|
283
|
+
})
|
|
284
|
+
export default class ApiGateway {}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 4. Bypassing Gateways (Exclude)
|
|
288
|
+
|
|
289
|
+
Bypass the Gateway's middleware, guards, and filters for specific sub-paths or methods without breaking your folder structure.
|
|
290
|
+
|
|
291
|
+
#### Functional Paradigm
|
|
292
|
+
|
|
293
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
294
|
+
import { defineGateway } from 'exisjs/router'
|
|
295
|
+
|
|
296
|
+
export default defineGateway({
|
|
297
|
+
exclude: [
|
|
298
|
+
'/api/public/status', // Exact Match
|
|
299
|
+
'/api/webhooks/*', // Wildcard Match
|
|
300
|
+
{ path: '/api/users', methods: ['GET'] } // Method-specific bypass
|
|
301
|
+
]
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Class-Based (OOP) Paradigm
|
|
306
|
+
|
|
307
|
+
```typescript className="language-typescript" filename="src/http/api/gateway.ts"
|
|
308
|
+
import { Gateway } from 'exisjs/decorators'
|
|
309
|
+
|
|
310
|
+
@Gateway({
|
|
311
|
+
exclude: [
|
|
312
|
+
'/api/public/status', // Exact Match
|
|
313
|
+
'/api/webhooks/*', // Wildcard Match
|
|
314
|
+
{ path: '/api/users', methods: ['GET'] } // Method-specific bypass
|
|
315
|
+
]
|
|
316
|
+
})
|
|
317
|
+
export default class ApiGateway {}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 5. Dynamic Request Timeouts
|
|
321
|
+
|
|
322
|
+
Define dynamic, folder-scoped request timeouts. For example, give your `/reports` folder 60 seconds, but restrict `/api` to 5 seconds. You can evaluate the timeout dynamically per-request.
|
|
323
|
+
|
|
324
|
+
#### Functional Paradigm
|
|
325
|
+
|
|
326
|
+
```typescript className="language-typescript" filename="src/http/reports/gateway.ts"
|
|
327
|
+
import { defineGateway } from 'exisjs/router'
|
|
328
|
+
|
|
329
|
+
export default defineGateway({
|
|
330
|
+
// Dynamic request timeouts evaluated per-request
|
|
331
|
+
timeout: (req) => req.headers['x-premium'] ? 30000 : 5000
|
|
332
|
+
})
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Class-Based (OOP) Paradigm
|
|
336
|
+
|
|
337
|
+
```typescript className="language-typescript" filename="src/http/reports/gateway.ts"
|
|
338
|
+
import { Gateway } from 'exisjs/decorators'
|
|
339
|
+
|
|
340
|
+
@Gateway({
|
|
341
|
+
// Dynamic request timeouts evaluated per-request
|
|
342
|
+
timeout: (req) => req.headers['x-premium'] ? 30000 : 5000
|
|
343
|
+
})
|
|
344
|
+
export default class ReportsGateway {}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 6. Cascading Metadata
|
|
348
|
+
|
|
349
|
+
Define tags (like OpenAPI/Swagger tags or Role definitions) at the folder level that automatically cascade down to every route inside.
|
|
350
|
+
|
|
351
|
+
#### Functional Paradigm
|
|
352
|
+
|
|
353
|
+
```typescript className="language-typescript" filename="src/http/admin/gateway.ts"
|
|
354
|
+
import { defineGateway } from 'exisjs/router'
|
|
355
|
+
|
|
356
|
+
export default defineGateway({
|
|
357
|
+
// Automatically cascades OpenAPI tags to every route in the folder
|
|
358
|
+
metadata: {
|
|
359
|
+
openapi: { tags: ['Admin API'], security: ['bearerAuth'] }
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### Class-Based (OOP) Paradigm
|
|
365
|
+
|
|
366
|
+
```typescript className="language-typescript" filename="src/http/admin/gateway.ts"
|
|
367
|
+
import { Gateway } from 'exisjs/decorators'
|
|
368
|
+
|
|
369
|
+
@Gateway({
|
|
370
|
+
// Automatically cascades OpenAPI tags to every route in the folder
|
|
371
|
+
metadata: {
|
|
372
|
+
openapi: { tags: ['Admin API'], security: ['bearerAuth'] }
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
export default class AdminGateway {}
|
|
376
|
+
```
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: '/docs/structure'
|
|
3
|
+
nextTitle: 'Project Structure'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Getting Started
|
|
7
|
+
|
|
8
|
+
In this guide, you'll learn the core fundamentals of ExisJS. To get familiar with the essential building blocks, we'll scaffold a new project, explore the file structure, and create our very first API server.
|
|
9
|
+
|
|
10
|
+
<Info title="Language Support">
|
|
11
|
+
We recommend **TypeScript** for type-safety and automatic frontend client generation. However, ExisJS fully supports vanilla JavaScript as well. ExisJS leverages esbuild under the hood, meaning you do not need to manually run `tsc` to start your server.
|
|
12
|
+
</Info>
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
Please make sure that **Node.js (version >= 20)** is installed on your operating system.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
Setting up a new project can be done using our CLI tool. Open your terminal and run the following command to scaffold a new ExisJS project:
|
|
21
|
+
|
|
22
|
+
<div className="my-6 max-w-lg">
|
|
23
|
+
<CopyCommand command="npx @exisjs/create@latest my-backend" />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
The CLI will prompt you with a few simple questions to customize your project:
|
|
27
|
+
|
|
28
|
+
```text filename="Terminal"
|
|
29
|
+
√ Would you like to use TypeScript? ... No / Yes
|
|
30
|
+
√ Would you like to use ESLint? ... No / Yes
|
|
31
|
+
√ Would you like your code inside a `src/` directory? ... No / Yes
|
|
32
|
+
√ Would you like to customize the import alias (`@/*` by default)? ... No / Yes
|
|
33
|
+
|
|
34
|
+
Creating a new Exis JS app in my-backend.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The CLI will then create the `my-backend` directory, install all required dependencies, and populate the project with a conventional base structure.
|
|
38
|
+
|
|
39
|
+
## Core Files
|
|
40
|
+
|
|
41
|
+
Once the installation is complete, your project directory will look something like this:
|
|
42
|
+
|
|
43
|
+
<div className="bg-muted/30 border border-border/50 rounded-md p-4 mb-6 font-mono text-sm">
|
|
44
|
+
<div className="flex gap-4 mb-2">
|
|
45
|
+
<span className="text-muted-foreground w-40 shrink-0">exis.config.ts</span>{' '}
|
|
46
|
+
<span>
|
|
47
|
+
The root configuration for your application (CORS, Logger, Port).
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="flex gap-4 mb-2">
|
|
51
|
+
<span className="text-muted-foreground w-40 shrink-0">src/env.ts</span>{' '}
|
|
52
|
+
<span>Environment variable validation and typing.</span>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="flex gap-4 mb-2">
|
|
55
|
+
<span className="text-muted-foreground w-40 shrink-0">src/http/</span>{' '}
|
|
56
|
+
<span>The file-system routing directory.</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="flex gap-4 mb-2 pl-4">
|
|
59
|
+
<span className="text-muted-foreground w-36 shrink-0">server.ts</span>{' '}
|
|
60
|
+
<span>The strict entry point of your application.</span>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="flex gap-4 pl-4">
|
|
63
|
+
<span className="text-muted-foreground w-36 shrink-0">route.ts</span>{' '}
|
|
64
|
+
<span>A basic GET endpoint mapped to the root route.</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
## Your First Server
|
|
69
|
+
|
|
70
|
+
Let's look at the generated `src/http/server.ts`. This is the strict entry point of your application, where you can connect to databases and register global plugins.
|
|
71
|
+
|
|
72
|
+
### Functional Server Setup
|
|
73
|
+
|
|
74
|
+
```typescript filename="src/http/server.ts"
|
|
75
|
+
import { exis } from 'exisjs'
|
|
76
|
+
|
|
77
|
+
export default exis({
|
|
78
|
+
async onStart(app) {
|
|
79
|
+
// 1. Connect to your database
|
|
80
|
+
// await db.connect()
|
|
81
|
+
// 2. Register plugins
|
|
82
|
+
// app.plugin(authPlugin)
|
|
83
|
+
// The Exis CLI automatically boots the server and file-system routes
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async onClose(app) {
|
|
87
|
+
// Gracefully close database connections here
|
|
88
|
+
// await db.disconnect()
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Class-Based (OOP) Server Setup
|
|
94
|
+
|
|
95
|
+
```typescript filename="src/http/server.ts"
|
|
96
|
+
import { Server } from 'exisjs/decorators'
|
|
97
|
+
import type { App } from 'exisjs'
|
|
98
|
+
|
|
99
|
+
@Server()
|
|
100
|
+
export default class RootServer {
|
|
101
|
+
async onStart(app: App) {
|
|
102
|
+
// 1. Connect to your database
|
|
103
|
+
// await db.connect()
|
|
104
|
+
// 2. Register plugins
|
|
105
|
+
// app.plugin(authPlugin)
|
|
106
|
+
// The Exis CLI automatically boots the server and file-system routes
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async onClose(app: App) {
|
|
110
|
+
// Gracefully close database connections here
|
|
111
|
+
// await db.disconnect()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
With ExisJS, the framework automatically handles booting up your server and loading your routes based on the file system. You don't need to manually call `app.listen()`!
|
|
117
|
+
|
|
118
|
+
## Your First Route
|
|
119
|
+
|
|
120
|
+
ExisJS uses **folder-based routing**. Your API structure maps directly to your folders in `src/http/`.
|
|
121
|
+
|
|
122
|
+
ExisJS supports two primary styles of writing routes: **Functional** and **OOP (Class-based)**. You are free to use whichever paradigm fits your team best.
|
|
123
|
+
|
|
124
|
+
### Functional Routing
|
|
125
|
+
|
|
126
|
+
Functional routing provides a lean, inference-heavy developer experience similar to modern frontend frameworks. Let's declare our first route in `src/http/users/route.ts`:
|
|
127
|
+
|
|
128
|
+
```typescript filename="src/http/users/route.ts"
|
|
129
|
+
import { controller, route } from 'exisjs/router'
|
|
130
|
+
|
|
131
|
+
export default controller({
|
|
132
|
+
getUsers: route.get('/', {
|
|
133
|
+
handle(req) {
|
|
134
|
+
// Return a realistic response structure
|
|
135
|
+
return {
|
|
136
|
+
data: [
|
|
137
|
+
{ id: 1, name: 'Alice' },
|
|
138
|
+
{ id: 2, name: 'Bob' }
|
|
139
|
+
],
|
|
140
|
+
total: 2
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### OOP Routing
|
|
148
|
+
|
|
149
|
+
If you prefer a highly-structured architecture heavily inspired by Angular and NestJS, ExisJS natively provides robust class-based routing using Decorators:
|
|
150
|
+
|
|
151
|
+
```typescript filename="src/http/users/route.ts"
|
|
152
|
+
import { Controller, Get, Req } from 'exisjs/decorators'
|
|
153
|
+
import type { Request } from 'exisjs'
|
|
154
|
+
|
|
155
|
+
@Controller()
|
|
156
|
+
export default class UsersController {
|
|
157
|
+
@Get('/')
|
|
158
|
+
getUsers(@Req() req: Request) {
|
|
159
|
+
// Return a realistic response structure
|
|
160
|
+
return {
|
|
161
|
+
data: [
|
|
162
|
+
{ id: 1, name: 'Alice' },
|
|
163
|
+
{ id: 2, name: 'Bob' }
|
|
164
|
+
],
|
|
165
|
+
total: 2
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
In both examples, because this file is named `route.ts` and is located in the `users` folder, it automatically responds to `GET /users` requests. Whatever you return is automatically serialized to JSON.
|
|
172
|
+
|
|
173
|
+
## Running the Application
|
|
174
|
+
|
|
175
|
+
Your generated `package.json` comes pre-configured with the Exis CLI commands. Start the development server with hot-reload by running:
|
|
176
|
+
|
|
177
|
+
<div className="my-6 max-w-lg">
|
|
178
|
+
<CopyCommand command="npm run dev" />
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
You will see the server boot up in your terminal:
|
|
182
|
+
|
|
183
|
+
```text filename="Terminal"
|
|
184
|
+
6:18:28 pm [exis] starting development server (v0.1.0)...
|
|
185
|
+
|
|
186
|
+
EXIS v0.1.0 ready in 125 ms
|
|
187
|
+
|
|
188
|
+
→ Local: http://localhost:4000/
|
|
189
|
+
→ Network: http://10.39.14.177:4000/
|
|
190
|
+
→ Environ: development
|
|
191
|
+
→ press h + enter to show help
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Open your browser and navigate to [http://localhost:4000/](http://localhost:4000/) (or the port defined in your `.env`). You will see:
|
|
195
|
+
|
|
196
|
+
```json filename="Response"
|
|
197
|
+
{
|
|
198
|
+
"data": [
|
|
199
|
+
{
|
|
200
|
+
"id": 1,
|
|
201
|
+
"name": "Alice"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"id": 2,
|
|
205
|
+
"name": "Bob"
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
"total": 2
|
|
209
|
+
}
|
|
210
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: '/docs/installation'
|
|
3
|
+
nextTitle: 'Installation'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Introduction
|
|
7
|
+
|
|
8
|
+
Exis (ExisJS) is a batteries-included backend framework for Node.js. It brings the file-based, convention-over-configuration developer experience of modern frontend meta-frameworks to backend API development: folders and files instead of manual wiring, with the essentials (routing, validation, queues, WebSockets, cron, DI) built in rather than assembled from a dozen separate packages.
|
|
9
|
+
|
|
10
|
+
Routing is handled by a radix-tree matcher, and the framework supports two ways to define the same routes: **Functional** (factory functions) and **Class-Based / OOP** (decorators, in the style of Angular/NestJS). Both compile to the same internal route representation, run through the same middleware pipeline, and can be mixed file-by-file in the same project. Pick whichever fits a given part of your codebase.
|
|
11
|
+
|
|
12
|
+
## Philosophy
|
|
13
|
+
|
|
14
|
+
Frontend frameworks like Next.js, Remix, and Nuxt improved developer productivity significantly through file-based routing and convention-over-configuration architecture. Good tools exist for Node.js backends too, but assembling them into a coherent application still typically means a lot of manual wiring and architectural boilerplate.
|
|
15
|
+
|
|
16
|
+
Exis aims to provide that out-of-the-box application architecture for backends: create a folder, get a route; create a `gateway.ts`, get folder-scoped middleware; import a module, get its providers registered. Job queues, WebSockets, cron scheduling, and schema validation are part of the standard library rather than bring-your-own dependencies. This means a new project starts with a coherent structure instead of a blank `index.js` and a pile of choices to make first.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
The fastest way to start is the Exis CLI, which scaffolds a new project directory with the core files and a conventional base structure already in place. This is the recommended path for first-time users.
|
|
21
|
+
|
|
22
|
+
<div className="my-6 max-w-lg">
|
|
23
|
+
<CopyCommand command="npx @exisjs/create@latest my-backend" />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<Hint>
|
|
27
|
+
The CLI walks you through initial project setup (TypeScript config, import
|
|
28
|
+
aliases, and package manager selection: it detects npm, yarn, pnpm, or bun
|
|
29
|
+
automatically). See [Installation](/docs/installation) for the full list of
|
|
30
|
+
prompts and options.
|
|
31
|
+
</Hint>
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
Both paradigms are shown throughout these docs side by side. Pick whichever you're using, or mix them across your project.
|
|
36
|
+
|
|
37
|
+
### Functional API
|
|
38
|
+
|
|
39
|
+
```typescript filename="src/http/users/route.ts"
|
|
40
|
+
import { controller, route } from 'exisjs/router'
|
|
41
|
+
|
|
42
|
+
export default controller({
|
|
43
|
+
getUsers: route.get('/', {
|
|
44
|
+
handle(req) {
|
|
45
|
+
// Return a realistic response structure
|
|
46
|
+
return {
|
|
47
|
+
data: [
|
|
48
|
+
{ id: 1, name: 'Alice' },
|
|
49
|
+
{ id: 2, name: 'Bob' }
|
|
50
|
+
],
|
|
51
|
+
total: 2
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Class-Based (OOP) API
|
|
59
|
+
|
|
60
|
+
```typescript filename="src/http/users/route.ts"
|
|
61
|
+
import { Controller, Get, Req } from 'exisjs/decorators'
|
|
62
|
+
import type { Request } from 'exisjs'
|
|
63
|
+
|
|
64
|
+
@Controller()
|
|
65
|
+
export default class UsersController {
|
|
66
|
+
@Get('/')
|
|
67
|
+
getUsers(@Req() req: Request) {
|
|
68
|
+
// Return a realistic response structure
|
|
69
|
+
return {
|
|
70
|
+
data: [
|
|
71
|
+
{ id: 1, name: 'Alice' },
|
|
72
|
+
{ id: 2, name: 'Bob' }
|
|
73
|
+
],
|
|
74
|
+
total: 2
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Save this as `src/http/users/route.ts`, run `exis dev`, and `GET /users` will return your mocked users list.
|
|
81
|
+
|
|
82
|
+
## Adding to an existing project
|
|
83
|
+
|
|
84
|
+
You can also add Exis to an existing Node.js project instead of scaffolding a new one:
|
|
85
|
+
|
|
86
|
+
<div className="my-6 max-w-lg">
|
|
87
|
+
<CopyCommand command="npm install exisjs" />
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
In this case you'll set up the project structure yourself: an `exis.config.ts` at the project root and a `src/http` directory for your routes. See [Structure](/docs/structure) for the conventions the framework expects.
|
|
91
|
+
|
|
92
|
+
## What's next
|
|
93
|
+
|
|
94
|
+
- [Installation](/docs/installation): CLI setup and project scaffolding in detail.
|
|
95
|
+
- [Structure](/docs/structure): the folder conventions Exis expects and why.
|
|
96
|
+
- [Routing](/docs/routing): how file-based routing, dynamic segments, and gateways work together.
|