exisjs 0.7.12 → 0.7.14

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 (132) hide show
  1. package/README.md +195 -39
  2. package/dist/cli/commands/run.d.ts.map +1 -1
  3. package/dist/cli/commands/run.js +3 -1
  4. package/dist/cli/commands/run.js.map +1 -1
  5. package/dist/config/types.d.ts +2 -2
  6. package/dist/config/types.d.ts.map +1 -1
  7. package/dist/decorators/params.d.ts +21 -0
  8. package/dist/decorators/params.d.ts.map +1 -1
  9. package/dist/decorators/params.js +21 -1
  10. package/dist/decorators/params.js.map +1 -1
  11. package/dist/di/container.d.ts +15 -6
  12. package/dist/di/container.d.ts.map +1 -1
  13. package/dist/di/container.js +26 -3
  14. package/dist/di/container.js.map +1 -1
  15. package/dist/di/index.d.ts +1 -1
  16. package/dist/di/index.d.ts.map +1 -1
  17. package/dist/di/index.js.map +1 -1
  18. package/dist/di/inject.d.ts +2 -1
  19. package/dist/di/inject.d.ts.map +1 -1
  20. package/dist/di/inject.js +2 -1
  21. package/dist/di/inject.js.map +1 -1
  22. package/dist/docs/ARCHITECTURE.md +130 -78
  23. package/dist/docs/BENCHMARKS.md +4 -4
  24. package/dist/docs/content/README.md +43 -44
  25. package/dist/docs/content/boundaries.mdx +138 -61
  26. package/dist/docs/content/cli/commands.mdx +151 -128
  27. package/dist/docs/content/cli/overview.mdx +61 -47
  28. package/dist/docs/content/cli/usage.mdx +107 -30
  29. package/dist/docs/content/config.mdx +145 -158
  30. package/dist/docs/content/controllers.mdx +193 -464
  31. package/dist/docs/content/cron.mdx +183 -69
  32. package/dist/docs/content/decorators.mdx +449 -154
  33. package/dist/docs/content/errors.mdx +202 -305
  34. package/dist/docs/content/exisjs-mcp.mdx +59 -49
  35. package/dist/docs/content/installation.mdx +243 -157
  36. package/dist/docs/content/introduction.mdx +220 -48
  37. package/dist/docs/content/middleware.mdx +171 -497
  38. package/dist/docs/content/modules.mdx +141 -83
  39. package/dist/docs/content/providers.mdx +216 -180
  40. package/dist/docs/content/requests.mdx +168 -488
  41. package/dist/docs/content/responses.mdx +200 -533
  42. package/dist/docs/content/routing.mdx +235 -598
  43. package/dist/docs/content/security.mdx +233 -57
  44. package/dist/docs/content/structure.mdx +151 -240
  45. package/dist/docs/content/swagger.mdx +192 -91
  46. package/dist/docs/content/testing.mdx +205 -74
  47. package/dist/docs/content/validation.mdx +479 -341
  48. package/dist/docs/reference/README.md +34 -2
  49. package/dist/docs/reference/config.mdx +277 -75
  50. package/dist/docs/reference/context.mdx +197 -106
  51. package/dist/docs/reference/cron.mdx +266 -152
  52. package/dist/docs/reference/database.mdx +135 -53
  53. package/dist/docs/reference/decorators.mdx +234 -116
  54. package/dist/docs/reference/di.mdx +146 -112
  55. package/dist/docs/reference/error.mdx +155 -178
  56. package/dist/docs/reference/exis.mdx +141 -70
  57. package/dist/docs/reference/logger.mdx +110 -126
  58. package/dist/docs/reference/middleware.mdx +235 -158
  59. package/dist/docs/reference/plugin.mdx +136 -64
  60. package/dist/docs/reference/router.mdx +275 -293
  61. package/dist/docs/reference/sanitize.mdx +91 -87
  62. package/dist/docs/reference/swagger.mdx +280 -100
  63. package/dist/docs/reference/testing.mdx +223 -89
  64. package/dist/docs/reference/validator.mdx +194 -206
  65. package/dist/lib/start-server.js +22 -8
  66. package/dist/lib/start-server.js.map +1 -1
  67. package/dist/module/module.d.ts +6 -3
  68. package/dist/module/module.d.ts.map +1 -1
  69. package/dist/module/module.js +17 -3
  70. package/dist/module/module.js.map +1 -1
  71. package/dist/router/controller-registrar.d.ts.map +1 -1
  72. package/dist/router/controller-registrar.js +26 -4
  73. package/dist/router/controller-registrar.js.map +1 -1
  74. package/dist/router/scanner/fs-scanner.d.ts.map +1 -1
  75. package/dist/router/scanner/fs-scanner.js +22 -0
  76. package/dist/router/scanner/fs-scanner.js.map +1 -1
  77. package/dist/router/types.d.ts +6 -4
  78. package/dist/router/types.d.ts.map +1 -1
  79. package/dist/server/app.d.ts +3 -1
  80. package/dist/server/app.d.ts.map +1 -1
  81. package/dist/server/app.js +24 -8
  82. package/dist/server/app.js.map +1 -1
  83. package/dist/server/bootstrapper.d.ts.map +1 -1
  84. package/dist/server/bootstrapper.js +19 -0
  85. package/dist/server/bootstrapper.js.map +1 -1
  86. package/dist/server/request.d.ts +2 -0
  87. package/dist/server/request.d.ts.map +1 -1
  88. package/dist/server/request.js +11 -0
  89. package/dist/server/request.js.map +1 -1
  90. package/dist/server/response.d.ts +10 -5
  91. package/dist/server/response.d.ts.map +1 -1
  92. package/dist/server/response.js +65 -18
  93. package/dist/server/response.js.map +1 -1
  94. package/dist/swagger/generator.d.ts.map +1 -1
  95. package/dist/swagger/generator.js +130 -10
  96. package/dist/swagger/generator.js.map +1 -1
  97. package/dist/telemetry/index.d.ts +12 -0
  98. package/dist/telemetry/index.d.ts.map +1 -0
  99. package/dist/telemetry/index.js +121 -0
  100. package/dist/telemetry/index.js.map +1 -0
  101. package/dist/testing/client.d.ts +1 -0
  102. package/dist/testing/client.d.ts.map +1 -1
  103. package/dist/testing/client.js +4 -1
  104. package/dist/testing/client.js.map +1 -1
  105. package/dist/testing/index.d.ts.map +1 -1
  106. package/dist/testing/index.js +11 -4
  107. package/dist/testing/index.js.map +1 -1
  108. package/dist/validator/builder.d.ts +26 -1
  109. package/dist/validator/builder.d.ts.map +1 -1
  110. package/dist/validator/builder.js +31 -0
  111. package/dist/validator/builder.js.map +1 -1
  112. package/dist/validator/engine.d.ts +23 -0
  113. package/dist/validator/engine.d.ts.map +1 -1
  114. package/dist/validator/engine.js +228 -31
  115. package/dist/validator/engine.js.map +1 -1
  116. package/dist/validator/index.d.ts +8 -4
  117. package/dist/validator/index.d.ts.map +1 -1
  118. package/dist/validator/index.js +4 -1
  119. package/dist/validator/index.js.map +1 -1
  120. package/dist/validator/tex-types.d.ts +19 -4
  121. package/dist/validator/tex-types.d.ts.map +1 -1
  122. package/dist/validator/tex-types.js +102 -0
  123. package/dist/validator/tex-types.js.map +1 -1
  124. package/dist/validator/tex.d.ts +1 -1
  125. package/dist/validator/tex.d.ts.map +1 -1
  126. package/dist/validator/tex.js +3 -1
  127. package/dist/validator/tex.js.map +1 -1
  128. package/dist/validator/types.d.ts +1 -0
  129. package/dist/validator/types.d.ts.map +1 -1
  130. package/package.json +46 -14
  131. package/dist/docs/content/dataloader.mdx +0 -170
  132. package/dist/docs/reference/dataloader.mdx +0 -91
@@ -1,753 +1,390 @@
1
1
  ---
2
- nextUrl: "/docs/controllers"
3
- nextTitle: "Controllers"
2
+ title: 'Routing & Endpoints'
3
+ description: 'File-system routing conventions, HTTP methods, dynamic route segments, catch-all parameters, and route options.'
4
+ nextUrl: '/docs/controllers'
5
+ nextTitle: 'Controllers'
4
6
  ---
5
7
 
6
- # Routing
8
+ # Routing & Endpoints
7
9
 
8
- Routes are responsible for handling incoming requests and sending responses back to the client. The routing mechanism determines which handler will process each request.
10
+ ExisJS uses file-system routing to map directory structures directly to HTTP endpoints. Dropping a `route.ts` or `<name>.route.ts` file inside a directory registers endpoints automatically with the native routing engine.
9
11
 
10
- <Info title="Functional vs OOP">
11
- ExisJS natively supports both a **Functional Paradigm** and a **Class-Based OOP Paradigm** (for dependency injection and decorators). You can mix and match them throughout your application.
12
- </Info>
13
-
14
- ## Folder-Based Routing
15
-
16
- ExisJS uses **folder-based file-system routing** instead of programmatic route declaration in a single entry file.
17
-
18
- For a folder to become an accessible API endpoint, it must contain a `route.ts` or `<feature>.route.ts` (e.g. `user.route.ts`) file that exports a route definition.
19
-
20
- In addition to `route.ts`, the ExisJS router utilizes two other special gatekeeper files:
21
- - **`server.ts`**: Placed at the root of your `http` directory, it is the entry point that boots your application, registers global plugins, and connects to databases.
22
- - **`boundary.ts`** (or `*.boundary.ts`): Can be placed in any folder. It acts as a gatekeeper and request pipeline, automatically configuring middleware, CORS, headers, and dependencies for *all* routes within its directory and subdirectories.
23
-
24
- ### Routing Conventions
25
-
26
- The ExisJS file-system router natively supports advanced folder naming conventions to handle dynamic segments and logical grouping:
27
-
28
- - **Dynamic Parameters (`[param]`)**: A folder named `[id]` creates a dynamic route segment (e.g., `/:id`). The value is accessible in your handler via `ctx.params.id`.
29
- - **Catch-all Routes (`[...param]`)**: A folder named `[...slug]` catches all subsequent URL segments, matching anything that follows (e.g., `/*slug`).
30
- - **Route Groups (`(group)`)**: A folder wrapped in parentheses (e.g., `(admin)`) is ignored in the final URL path. This is useful for grouping related routes or applying shared controller-level middleware without adding `/admin` to the URL.
31
-
32
- ### The Application Entry Point (`server.ts`)
33
-
34
- The `server.ts` file acts as the root configuration for your application. It provides lifecycle hooks to boot databases, register global plugins, and provide root-level dependencies.
35
-
36
- #### Functional Paradigm
37
- ```typescript filename="src/http/server.ts"
38
- import { exis } from 'exisjs'
39
-
40
- export default exis({
41
- providers: [
42
- ['LoggerService', { useValue: console }]
43
- ]
44
- })
45
- ```
46
-
47
- #### Class-Based (OOP) Paradigm
48
- ```typescript filename="src/http/server.ts"
49
- import { Server } from 'exisjs/decorators'
50
- import type { App } from 'exisjs'
51
-
52
- @Server({
53
- providers: [['LoggerService', { useValue: console }]]
54
- })
55
- export default class RootServer {
56
- async onStart(app: App) {
57
- // Boot your database here!
58
- }
59
- }
60
- ```
61
-
62
- ### Folder Boundaries (`boundary.ts`)
63
-
64
- Boundaries apply configurations, security policies, and request pipelines to all routes in their directory and subdirectories.
65
-
66
- #### Functional Paradigm
67
- ```typescript filename="src/http/admin/boundary.ts"
68
- import { defineBoundary } from 'exisjs/router'
69
-
70
- export const config = defineBoundary({
71
- headers: { 'X-Admin-Area': 'true' }
72
- })
73
- ```
74
-
75
- #### Class-Based (OOP) Paradigm
76
- ```typescript filename="src/http/admin/boundary.ts"
77
- import { Boundary } from 'exisjs/decorators'
78
-
79
- @Boundary({
80
- headers: { 'X-Admin-Area': 'true' }
81
- })
82
- export default class AdminBoundary {}
83
- ```
84
-
85
- ### Route Definitions (`route.ts`)
86
-
87
- For example, to create a simple `/health` endpoint, you create the folder `src/http/health/` and place a `route.ts` file inside it:
88
-
89
- #### Functional Paradigm
90
-
91
-
92
- ```typescript filename="src/http/health/route.ts"
93
- import { controller, route } from 'exisjs/router'
12
+ ---
94
13
 
95
- export default controller({
96
- check: route.get('/', {
97
- handle() {
98
- return { status: 'ok', timestamp: new Date().toISOString() }
99
- }
100
- })
101
- })
102
- ```
14
+ ## Routing Principles
103
15
 
104
-
105
- #### Class-Based (OOP) Paradigm
16
+ 1. **Folder-Based URL Mapping**: The folder structure relative to `src/http/` determines the base URL path.
17
+ 2. **Dual-Paradigm Support**: Routes can be authored using either the **Functional Paradigm** (`controller()`, `route.*`) or the **Class-Based (OOP) Paradigm** (`@Controller()`, `@Get()`, `@Post()`). Choose one paradigm per project.
18
+ 3. **Automatic Parameter Inference**: Dynamic URL segments defined in path strings (such as `/:id` or `/:postId`) or folder names (`[id]`) automatically populate typed route parameters in handler contexts.
106
19
 
20
+ ---
107
21
 
108
- ```typescript filename="src/http/health/route.ts"
109
- import { Controller, Get } from 'exisjs/decorators'
22
+ ## File-System Route Conventions
110
23
 
111
- @Controller()
112
- export default class HealthController {
113
- @Get('/')
114
- check() {
115
- return { status: 'ok', timestamp: new Date().toISOString() }
116
- }
117
- }
118
- ```
24
+ | Directory Path | URL Endpoint | Description |
25
+ | :--- | :--- | :--- |
26
+ | `src/http/route.ts` | `/` | Root application endpoint |
27
+ | `src/http/health/route.ts` | `/health` | Static route endpoint |
28
+ | `src/http/users/route.ts` | `/users` | Collection endpoint |
29
+ | `src/http/users/[id]/route.ts` | `/users/:id` | Dynamic parameter route |
30
+ | `src/http/docs/[...slug]/route.ts` | `/docs/*slug` | Catch-all wildcard route |
31
+ | `src/http/(admin)/dashboard/route.ts` | `/dashboard` | Route group (parentheses are omitted from the URL path) |
119
32
 
120
-
33
+ ---
121
34
 
122
- ## Supported Methods
35
+ ## Route Definitions
123
36
 
124
- ExisJS natively exposes standard HTTP methods alongside advanced routing paradigms like WebSockets and Server-Sent Events. Choose your paradigm below to see the available methods.
37
+ ### Basic Route Handlers
125
38
 
126
39
  #### Functional Paradigm
127
40
 
128
- <div className="overflow-x-auto mb-6">
129
- <table className="w-full text-sm text-left border border-border/50 rounded-lg overflow-hidden">
130
- <thead className="bg-muted/50 text-foreground">
131
- <tr>
132
- <th className="px-4 py-3 border-b border-border/50 font-semibold w-1/3">Method</th>
133
- <th className="px-4 py-3 border-b border-border/50 font-semibold">Description</th>
134
- </tr>
135
- </thead>
136
- <tbody className="divide-y divide-border/50">
137
- <tr className="bg-background hover:bg-muted/30 transition-colors">
138
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>get() / post() / put()</td>
139
- <td className="px-4 py-3 text-muted-foreground">Standard HTTP methods. Also includes patch, delete, options, head, connect, and trace.</td>
140
- </tr>
141
- <tr className="bg-background hover:bg-muted/30 transition-colors">
142
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>all()</td>
143
- <td className="px-4 py-3 text-muted-foreground">Matches any HTTP request method for the given path.</td>
144
- </tr>
145
- <tr className="bg-background hover:bg-muted/30 transition-colors">
146
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>query()</td>
147
- <td className="px-4 py-3 text-muted-foreground">The new experimental HTTP QUERY method, solving complex idempotent fetches.</td>
148
- </tr>
149
- <tr className="bg-background hover:bg-muted/30 transition-colors">
150
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>ws()</td>
151
- <td className="px-4 py-3 text-muted-foreground">Exposes a native WebSocket endpoint with automatic pub/sub support.</td>
152
- </tr>
153
- <tr className="bg-background hover:bg-muted/30 transition-colors">
154
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">route.</span>sse()</td>
155
- <td className="px-4 py-3 text-muted-foreground">Exposes a Server-Sent Events stream for one-way realtime data.</td>
156
- </tr>
157
- </tbody>
158
- </table>
159
- </div>
160
-
161
41
  ```typescript filename="src/http/users/route.ts"
162
42
  import { controller, route } from 'exisjs/router'
163
43
 
164
44
  export default controller({
165
- list: route.get('/', {
166
- handle() {
167
- // Fetch all users from database
45
+ // GET /users
46
+ listUsers: route.get('/', {
47
+ summary: 'List users',
48
+ handle: async () => {
168
49
  return [
169
- { id: 1, name: 'Alice' },
170
- { id: 2, name: 'Bob' }
50
+ { id: '1', name: 'Alice' },
51
+ { id: '2', name: 'Bob' },
171
52
  ]
172
- }
53
+ },
173
54
  }),
174
- create: route.post('/', {
175
- handle({ req }) {
176
- // Create a new user with parsed payload
177
- const payload = req.body
178
- return { id: 3, ...payload }
179
- }
55
+
56
+ // POST /users
57
+ createUser: route.post('/', {
58
+ summary: 'Create user',
59
+ handle: async ({ body }) => {
60
+ return { id: '3', ...body }
61
+ },
180
62
  }),
181
- preflight: route.options('/', {
182
- handle({ res }) {
183
- // Handle custom preflight logic if `preflightContinue: true` is configured in CORS
184
- res.header('X-Custom-Options', 'true')
185
- return { allowed: true }
186
- }
187
- })
188
63
  })
189
64
  ```
190
-
191
- #### Class-Based (OOP) Paradigm
192
65
 
193
- <div className="overflow-x-auto mb-6">
194
- <table className="w-full text-sm text-left border border-border/50 rounded-lg overflow-hidden">
195
- <thead className="bg-muted/50 text-foreground">
196
- <tr>
197
- <th className="px-4 py-3 border-b border-border/50 font-semibold w-1/3">Decorator</th>
198
- <th className="px-4 py-3 border-b border-border/50 font-semibold">Description</th>
199
- </tr>
200
- </thead>
201
- <tbody className="divide-y divide-border/50">
202
- <tr className="bg-background hover:bg-muted/30 transition-colors">
203
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Get() / <span className="text-primary">@</span>Post() / <span className="text-primary">@</span>Put()</td>
204
- <td className="px-4 py-3 text-muted-foreground">Standard HTTP methods. Also includes @Patch, @Delete, @Options, @Head, @Connect, and @Trace.</td>
205
- </tr>
206
- <tr className="bg-background hover:bg-muted/30 transition-colors">
207
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>All()</td>
208
- <td className="px-4 py-3 text-muted-foreground">Matches any HTTP request method for the given path.</td>
209
- </tr>
210
- <tr className="bg-background hover:bg-muted/30 transition-colors">
211
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Query()</td>
212
- <td className="px-4 py-3 text-muted-foreground">The new experimental HTTP QUERY method, solving complex idempotent fetches.</td>
213
- </tr>
214
- <tr className="bg-background hover:bg-muted/30 transition-colors">
215
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Ws()</td>
216
- <td className="px-4 py-3 text-muted-foreground">Exposes a native WebSocket endpoint with automatic pub/sub support.</td>
217
- </tr>
218
- <tr className="bg-background hover:bg-muted/30 transition-colors">
219
- <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Sse()</td>
220
- <td className="px-4 py-3 text-muted-foreground">Exposes a Server-Sent Events stream for one-way realtime data.</td>
221
- </tr>
222
- </tbody>
223
- </table>
224
- </div>
66
+ #### Class-Based (OOP) Paradigm
225
67
 
226
68
  ```typescript filename="src/http/users/route.ts"
227
- import { Controller, Get, Post, Body } from 'exisjs/decorators'
69
+ import { Controller, Get, Post, Body, ApiOperation } from 'exisjs/decorators'
228
70
 
229
71
  @Controller()
230
72
  export default class UsersController {
231
73
  @Get('/')
232
- list() {
233
- // Fetch all users from database
74
+ @ApiOperation({ summary: 'List users' })
75
+ async listUsers() {
234
76
  return [
235
- { id: 1, name: 'Alice' },
236
- { id: 2, name: 'Bob' }
77
+ { id: '1', name: 'Alice' },
78
+ { id: '2', name: 'Bob' },
237
79
  ]
238
80
  }
239
81
 
240
82
  @Post('/')
241
- create(@Body() data: any) {
242
- // Create a new user with parsed payload
243
- return { id: 3, ...data }
83
+ @ApiOperation({ summary: 'Create user' })
84
+ async createUser(@Body() body: Record<string, any>) {
85
+ return { id: '3', ...body }
244
86
  }
245
87
  }
246
88
  ```
247
-
248
-
249
- ## Sub-Paths in a Single File
250
89
 
251
- While folder-based routing is standard, you can also define multiple sub-paths in a single `route.ts` file to group related endpoints.
252
-
253
- #### Functional Paradigm
254
-
255
-
256
- ```typescript filename="src/http/users/route.ts"
257
- import { controller, route } from 'exisjs/router'
258
-
259
- export default controller({
260
- list: route.get('/', {
261
- handle() {
262
- return { message: 'Fetching all users' }
263
- }
264
- }),
265
- getSingle: route.get('/:id', {
266
- handle({ params }) {
267
- // params.id is automatically inferred as string with full autocomplete!
268
- const { id } = params
269
- return { message: `Fetching user ${id}` }
270
- }
271
- })
272
- })
273
- ```
274
-
275
- <Info title="Automatic Route Parameter Type Inference">
276
- In ExisJS v0.7+, paths with named parameters like `route.get('/users/:userId/posts/:postId', ...)` automatically parse the path string literal to infer `params: { userId: string; postId: string }` on the `handle` context. No manual casting or boilerplate interfaces required.
277
- </Info>
278
-
279
-
280
- #### Class-Based (OOP) Paradigm
90
+ ---
281
91
 
92
+ ## Supported HTTP Methods
282
93
 
283
- ```typescript filename="src/http/users/route.ts"
284
- import { Controller, Get, Param } from 'exisjs/decorators'
94
+ ExisJS supports all standard HTTP methods across both paradigms:
285
95
 
286
- @Controller()
287
- export default class UsersController {
288
- @Get('/')
289
- list() {
290
- return { message: 'Fetching all users' }
291
- }
96
+ | HTTP Verb | Functional Method | OOP Decorator | Description |
97
+ | :--- | :--- | :--- | :--- |
98
+ | `GET` | `route.get(path, config)` | `@Get(path, schema)` | Retrieve resources |
99
+ | `POST` | `route.post(path, config)` | `@Post(path, schema)` | Create resources or trigger actions |
100
+ | `PUT` | `route.put(path, config)` | `@Put(path, schema)` | Replace existing resources |
101
+ | `PATCH` | `route.patch(path, config)` | `@Patch(path, schema)` | Apply partial updates |
102
+ | `DELETE` | `route.delete(path, config)` | `@Delete(path, schema)` | Remove resources |
103
+ | `OPTIONS` | `route.options(path, config)` | `@Options(path, schema)` | Describe communication options / preflight |
104
+ | `HEAD` | `route.head(path, config)` | `@Head(path, schema)` | Retrieve headers identical to a GET request |
105
+ | `CONNECT` | `route.connect(path, config)` | `@Connect(path, schema)` | Establish a two-way tunnel |
106
+ | `TRACE` | `route.trace(path, config)` | `@Trace(path, schema)` | Loop-back testing along network path |
107
+ | `QUERY` | `route.query(path, config)` | `@Query(path, schema)` | Safe query method with request payload (RFC 9535) |
108
+ | `ALL` | `route.all(path, config)` | `@All(path, schema)` | Matches any HTTP method for the given path |
292
109
 
293
- @Get('/:id')
294
- getSingle(@Param('id') id: string) {
295
- return { message: \`Fetching user \${id}\` }
296
- }
297
- }
298
- ```
110
+ ---
299
111
 
300
-
112
+ ## Dynamic Route Parameters
301
113
 
302
- ## Route Parameters (Dynamic Routing)
114
+ ### Folder-Based Parameters (`[param]`)
303
115
 
304
- Routes with static paths won’t work when you need to accept dynamic data as part of the request (e.g., `GET /users/123`). To create dynamic routes using folders, enclose the folder name in brackets `[]`.
116
+ Creating a folder named `[id]` creates a parameterized route segment.
305
117
 
306
118
  #### Functional Paradigm
307
119
 
308
-
309
120
  ```typescript filename="src/http/users/[id]/route.ts"
310
- // Location: src/http/users/[id]/route.ts
311
121
  import { controller, route } from 'exisjs/router'
122
+ import { tex } from 'exisjs/validator'
123
+
124
+ const UserParamsSchema = tex.object({
125
+ id: tex.string({ min: 1 }),
126
+ })
312
127
 
313
128
  export default controller({
314
- getSingle: route.get('/', {
315
- handle({ params }) {
316
- const { id } = params
317
- return { message: \`Fetching user \${id}\` }
318
- }
319
- })
129
+ getUser: route.get('/', {
130
+ params: UserParamsSchema,
131
+ handle: async ({ params }) => {
132
+ return { id: params.id, name: 'Alice' }
133
+ },
134
+ }),
320
135
  })
321
136
  ```
322
137
 
323
-
324
138
  #### Class-Based (OOP) Paradigm
325
139
 
326
-
327
140
  ```typescript filename="src/http/users/[id]/route.ts"
328
- // Location: src/http/users/[id]/route.ts
329
- import { Controller, Get, Param } from 'exisjs/decorators'
141
+ import { Controller, Get, Param, ApiOperation } from 'exisjs/decorators'
142
+ import { tex } from 'exisjs/validator'
143
+
144
+ const UserParamsSchema = tex.object({
145
+ id: tex.string({ min: 1 }),
146
+ })
330
147
 
331
148
  @Controller()
332
149
  export default class SingleUserController {
333
- @Get('/')
334
- getSingle(@Param('id') id: string) {
335
- return { message: \`Fetching user \${id}\` }
150
+ @Get('/', { params: UserParamsSchema })
151
+ @ApiOperation({ summary: 'Get user by ID' })
152
+ async getUser(@Param('id') id: string) {
153
+ return { id, name: 'Alice' }
336
154
  }
337
155
  }
338
156
  ```
339
157
 
340
-
341
-
342
- ## Request & Response Objects
343
-
344
- Handlers often need access to the client’s request details, or fine-grained control over the response headers and status codes. Every route handler natively receives powerful `req` and `res` objects.
158
+ ---
345
159
 
346
- #### Functional Paradigm
160
+ ### In-File Sub-Paths
347
161
 
162
+ You can also define multiple sub-paths with route parameters inside a single `route.ts` file:
348
163
 
349
- ```typescript filename="src/http/profile/route.ts"
164
+ ```typescript filename="src/http/users/route.ts"
350
165
  import { controller, route } from 'exisjs/router'
166
+ import { tex } from 'exisjs/validator'
351
167
 
352
168
  export default controller({
353
- profile: route.get('/profile', {
354
- handle({ req, res }) {
355
- // Read Request details
356
- const ip = req.ip
357
- const headers = req.headers
358
-
359
- // Chain Response methods
360
- res
361
- .status(202)
362
- .cookie('session', 'xyz123', { httpOnly: true, secure: true })
363
- .header('X-Custom-Header', 'Exis')
364
- .json({ message: 'Accepted' })
365
- }
366
- })
367
- })
368
- ```
369
-
370
-
371
- #### Class-Based (OOP) Paradigm
372
-
169
+ // GET /users
170
+ list: route.get('/', {
171
+ handle: async () => [{ id: '1', name: 'Alice' }],
172
+ }),
373
173
 
374
- ```typescript filename="src/http/profile/route.ts"
375
- import { Controller, Get, Req, Res } from 'exisjs/decorators'
376
- import type { Request, Response } from 'exisjs/router'
174
+ // GET /users/:id
175
+ getById: route.get('/:id', {
176
+ params: tex.object({ id: tex.string() }),
177
+ handle: async ({ params }) => ({ id: params.id, name: 'Alice' }),
178
+ }),
377
179
 
378
- @Controller()
379
- export default class ProfileController {
380
- @Get('/profile')
381
- profile(@Req() req: Request, @Res() res: Response) {
382
- // Read Request details
383
- const ip = req.ip
384
- const headers = req.headers
385
-
386
- // Chain Response methods
387
- res
388
- .status(202)
389
- .cookie('session', 'xyz123', { httpOnly: true, secure: true })
390
- .header('X-Custom-Header', 'Exis')
391
- .json({ message: 'Accepted' })
392
- }
393
- }
180
+ // GET /users/:id/posts/:postId
181
+ getUserPost: route.get('/:id/posts/:postId', {
182
+ params: tex.object({
183
+ id: tex.string(),
184
+ postId: tex.string(),
185
+ }),
186
+ handle: async ({ params }) => {
187
+ return { userId: params.id, postId: params.postId, title: 'Hello ExisJS' }
188
+ },
189
+ }),
190
+ })
394
191
  ```
395
192
 
396
-
397
-
398
- <Info title="Default Responses">
399
- When a request handler returns a JavaScript object or array, it is automatically serialized to JSON and sent with a default `200 OK` status code. You only need to use the `res` object when you want to explicitly modify headers, cookies, or status codes!
400
- </Info>
401
-
402
- ## Payloads & Query Parameters
403
-
404
- You can easily extract query strings from the URL or parsed JSON bodies from POST requests directly through the `req` object (or via decorators in OOP).
193
+ ---
405
194
 
406
- #### Functional Paradigm
195
+ ## Catch-All Routes (`[...slug]`)
407
196
 
197
+ Folders named `[...slug]` match all subsequent nested URL paths.
408
198
 
409
- ```typescript filename="src/http/search/route.ts"
199
+ ```typescript filename="src/http/docs/[...slug]/route.ts"
410
200
  import { controller, route } from 'exisjs/router'
411
201
 
412
202
  export default controller({
413
- search: route.get('/search', {
414
- handle({ req }) {
415
- // e.g., GET /search?q=exis&limit=10
416
- const { q, limit } = req.query
417
- return { query: q, limit }
418
- }
203
+ renderDoc: route.get('/', {
204
+ handle: async ({ params }) => {
205
+ // For a request to /docs/getting-started/installation
206
+ // params.slug contains "getting-started/installation"
207
+ return { slug: params.slug }
208
+ },
419
209
  }),
420
- create: route.post('/create', {
421
- handle({ req }) {
422
- // Access parsed JSON body natively
423
- const data = req.body
424
- return { received: data }
425
- }
426
- })
427
210
  })
428
211
  ```
429
212
 
430
-
431
- #### Class-Based (OOP) Paradigm
213
+ ---
432
214
 
215
+ ## Route Groups (`(group)`)
433
216
 
434
- ```typescript filename="src/http/search/route.ts"
435
- import { Controller, Get, Post, Query, Body } from 'exisjs/decorators'
217
+ Parenthesized directory names group routes logically without appending the folder name to the URL path:
436
218
 
437
- @Controller()
438
- export default class SearchController {
439
- @Get('/search')
440
- search(@Query('q') q: string, @Query('limit') limit: number) {
441
- // e.g., GET /search?q=exis&limit=10
442
- return { query: q, limit }
443
- }
444
-
445
- @Post('/create')
446
- create(@Body() data: any) {
447
- // Access parsed JSON body natively
448
- return { received: data }
449
- }
450
- }
219
+ ```
220
+ src/http/
221
+ ├── (auth)/
222
+ │ ├── login/
223
+ │ │ └── route.ts # Maps to /login (not /auth/login)
224
+ │ └── register/
225
+ │ └── route.ts # Maps to /register (not /auth/register)
226
+ └── (marketing)/
227
+ └── pricing/
228
+ └── route.ts # Maps to /pricing (not /marketing/pricing)
451
229
  ```
452
230
 
453
-
454
-
455
- ## Declarative Route Options (Uploads, Timeouts & Validation)
456
-
457
- ExisJS allows configuring input validation schemas, multipart file uploads, and per-route timeouts directly inside the route definition without manual middleware plumbing.
458
-
459
- ### Declarative File Uploads
231
+ ---
460
232
 
461
- Specify `upload` in your route definition to automatically handle `multipart/form-data`. Uploaded files are parsed and attached to `req.file` or `req.files`, while non-file form fields are populated in `req.body`.
233
+ ## Handler Execution Context (`SuperContext`)
462
234
 
463
- #### Functional Paradigm
464
- ```typescript filename="src/http/avatar/route.ts"
465
- import { controller, route } from 'exisjs/router'
466
- import { tex } from 'exisjs/validator'
235
+ In the functional paradigm, `handle` receives a single context object containing all request properties and utilities:
467
236
 
237
+ ```typescript
468
238
  export default controller({
469
- uploadAvatar: route.post('/', {
470
- upload: {
471
- field: 'avatar',
472
- maxBytes: 5 * 1024 * 1024, // 5MB limit
473
- mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
239
+ endpoint: route.post('/example', {
240
+ handle: async (ctx) => {
241
+ const { body, query, params, headers, req, res, app, resolve, file } = ctx
242
+ // ...
474
243
  },
475
- body: tex.object({
476
- caption: tex.string({ optional: true }),
477
- }),
478
- handle({ req }) {
479
- const file = req.file! // UploadedFile { filename, buffer, size, mimetype }
480
- const caption = req.body?.caption
481
-
482
- return {
483
- success: true,
484
- filename: file.filename,
485
- size: file.size,
486
- caption,
487
- }
488
- }
489
- })
244
+ }),
490
245
  })
491
246
  ```
492
247
 
493
- #### Class-Based (OOP) Paradigm
494
- ```typescript filename="src/http/avatar/route.ts"
495
- import { Controller, Post, Body } from 'exisjs/decorators'
496
- import { Upload, UploadedFile } from 'exisjs/decorators'
497
- import type { UploadedFile as UploadedFileType } from 'exisjs/middleware'
248
+ ### Context Properties
249
+
250
+ | Property | Type | Description |
251
+ | :--- | :--- | :--- |
252
+ | `ctx.body` | `TBody` | Parsed and validated request body payload |
253
+ | `ctx.query` | `TQuery` | Parsed and validated URL query parameters |
254
+ | `ctx.params` | `TParams` | Extracted route parameters |
255
+ | `ctx.headers` | `Record<string, string>` | Request headers map |
256
+ | `ctx.req` | `Request` | The underlying `ExisRequest` instance |
257
+ | `ctx.res` | `Response` | The underlying `ExisResponse` instance |
258
+ | `ctx.resolve` | `(token) => T` | Resolves a dependency from the IoC container |
259
+ | `ctx.file` | `ExisFile` | Uploaded file instance (when multipart upload is enabled) |
260
+ | `ctx.files` | `ExisFile[]` | Uploaded files array (for multiple file uploads) |
261
+ | `ctx.state` | `Record<string, any>` | Request-scoped custom state |
498
262
 
499
- @Controller('/avatar')
500
- export default class AvatarController {
501
- @Post('/')
502
- @Upload('avatar', {
503
- maxBytes: 5 * 1024 * 1024,
504
- mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
505
- })
506
- upload(
507
- @UploadedFile() file: UploadedFileType,
508
- @Body('caption') caption?: string
509
- ) {
510
- return {
511
- success: true,
512
- filename: file.filename,
513
- size: file.size,
514
- caption,
515
- }
516
- }
517
- }
518
- ```
263
+ ---
519
264
 
520
- ### Route-Level Timeout Overrides
265
+ ## Declarative Route Configuration Options
521
266
 
522
- While ExisJS provides a generous 60-second default global timeout, long-running batch jobs, PDF generation, or third-party AI integrations can override the timeout at the individual route level:
267
+ Each route handler accepts an options object supporting validation, security policies, file uploads, and timeouts:
523
268
 
524
- #### Functional Paradigm
525
- ```typescript filename="src/http/reports/route.ts"
269
+ ### 1. Request Validation Schemas
270
+
271
+ ```typescript filename="src/http/products/route.ts"
526
272
  import { controller, route } from 'exisjs/router'
273
+ import { tex } from 'exisjs/validator'
527
274
 
528
- export default controller({
529
- generateAnnualReport: route.post('/annual', {
530
- timeout: 120000, // 2 minutes specifically for this route
531
- async handle() {
532
- // Intensive document rendering or AI workflow
533
- const reportUrl = await buildReport()
534
- return { success: true, reportUrl }
535
- }
536
- })
275
+ const ProductQuerySchema = tex.object({
276
+ page: tex.number({ coerce: true, default: 1 }),
277
+ limit: tex.number({ coerce: true, default: 20 }),
537
278
  })
538
- ```
539
279
 
540
- ### Route-Level CORS Overrides
541
-
542
- Routes can also override CORS policies defined in parent `boundary.ts` files or `exis.config.ts`. Route-level CORS has the **highest precedence** in the framework:
543
-
544
- #### Functional Paradigm
545
- ```typescript filename="src/http/webhooks/stripe/route.ts"
546
- import { controller, route } from 'exisjs/router'
280
+ const ProductBodySchema = tex.object({
281
+ name: tex.string({ min: 2 }),
282
+ price: tex.number({ min: 0 }),
283
+ })
547
284
 
548
285
  export default controller({
549
- // Open webhook endpoint to Stripe servers only, bypassing restrictive boundary CORS
550
- webhook: route.post('/', {
551
- cors: {
552
- origin: 'https://hooks.stripe.com',
553
- methods: ['POST'],
286
+ create: route.post('/', {
287
+ query: ProductQuerySchema,
288
+ body: ProductBodySchema,
289
+ handle: async ({ query, body }) => {
290
+ return { page: query.page, product: body }
554
291
  },
555
- handle({ req }) {
556
- return { received: true }
557
- }
558
292
  }),
559
-
560
- // Disable CORS completely for internal endpoints
561
- internalHealth: route.get('/health', {
562
- cors: false,
563
- handle() {
564
- return { status: 'healthy' }
565
- }
566
- })
567
293
  })
568
294
  ```
569
295
 
570
- #### Class-Based (OOP) Paradigm
571
- ```typescript filename="src/http/webhooks/stripe/route.ts"
572
- import { Controller, Post, Cors } from 'exisjs/decorators'
573
-
574
- @Controller('/webhooks/stripe')
575
- export default class StripeWebhookController {
576
- @Post('/')
577
- @Cors({ origin: 'https://hooks.stripe.com', methods: ['POST'] })
578
- handleWebhook() {
579
- return { received: true }
580
- }
581
- }
582
- ```
583
-
584
- ### Query Pagination Schemas
296
+ ---
585
297
 
586
- ExisJS provides the `tex.pagination()` primitive for declarative pagination, sorting, and ordering query parameters. String query parameters are automatically coerced into numbers:
298
+ ### 2. Multipart File Uploads
587
299
 
588
- ```typescript filename="src/http/products/route.ts"
300
+ ```typescript filename="src/http/upload/route.ts"
589
301
  import { controller, route } from 'exisjs/router'
590
- import { tex, paginate, getPaginationSkip } from 'exisjs/validator'
591
-
592
- const ProductQuerySchema = tex.pagination({
593
- defaultLimit: 20,
594
- maxLimit: 100,
595
- })
596
302
 
597
303
  export default controller({
598
- list: route.get('/', {
599
- query: ProductQuerySchema,
600
- async handle({ query }) {
601
- // query.page and query.limit are guaranteed positive numbers
602
- const { skip, limit, page } = getPaginationSkip(query)
603
-
604
- // Fetch data using your preferred database query
605
- const [items, total] = await Promise.all([
606
- db.products.findMany({ skip, take: limit }),
607
- db.products.count()
608
- ])
609
-
610
- return paginate(items, total, { page, limit })
611
- }
612
- })
304
+ uploadFile: route.post('/', {
305
+ upload: {
306
+ field: 'file',
307
+ maxBytes: 5 * 1024 * 1024, // 5MB limit
308
+ mimeTypes: ['image/png', 'image/jpeg', 'application/pdf'],
309
+ },
310
+ handle: async ({ file }) => {
311
+ return {
312
+ filename: file?.filename,
313
+ size: file?.size,
314
+ mimeType: file?.mimeType,
315
+ }
316
+ },
317
+ }),
613
318
  })
614
319
  ```
615
320
 
616
- ## Dependency Injection in Functional Controllers
321
+ ---
617
322
 
618
- ExisJS natively supports **Request-Scoped Dependency Injection** directly inside functional routes. You don't need to use classes to get the benefits of DI!
323
+ ### 3. Route-Specific Timeout Overrides
619
324
 
620
- By destructuring `resolve` from the context, you can dynamically fetch services without relying on global imports. This perfectly solves the "Fat Handler" problem by encouraging you to abstract business logic into clean Service classes, and makes unit testing incredibly easy.
325
+ You can override default timeouts for long-running endpoints:
621
326
 
622
- ```typescript filename="src/http/users/route.ts"
327
+ ```typescript filename="src/http/reports/route.ts"
623
328
  import { controller, route } from 'exisjs/router'
624
- import { UsersService } from './service'
625
329
 
626
330
  export default controller({
627
- list: route.get('/', {
628
- async handle({ resolve }) {
629
- // 1. Resolves UsersService from the IoC Container
630
- // 2. Maintains request-scope (the same instance is returned throughout this HTTP request)
631
- const usersService = resolve(UsersService)
632
-
633
- return usersService.getAll()
634
- }
635
- })
331
+ generateReport: route.post('/annual', {
332
+ timeout: 120000, // 120 seconds in milliseconds
333
+ handle: async () => {
334
+ // Perform long-running export
335
+ return { status: 'completed' }
336
+ },
337
+ }),
636
338
  })
637
339
  ```
638
340
 
639
- ## The Context API (Advanced)
640
-
641
- For applications where passing `(req, res)` arguments through multiple service layers becomes tedious, ExisJS offers a Next.js-inspired approach: **The Context API**. Powered by Node.js `AsyncLocalStorage`, ExisJS provides hooks to access the current request state from anywhere in the execution context.
341
+ ---
642
342
 
643
- #### Functional Paradigm
343
+ ### 4. Route-Specific CORS Overrides
644
344
 
345
+ Route-level CORS configuration takes precedence over boundary and global settings:
645
346
 
646
- ```typescript filename="src/http/users/route.ts"
647
- import { controller, route, getContext, getRequest, getResponse, after } from 'exisjs/router'
347
+ ```typescript filename="src/http/webhooks/stripe/route.ts"
348
+ import { controller, route } from 'exisjs/router'
648
349
 
649
350
  export default controller({
650
- create: route.post('/', {
651
- async handle() {
652
- // Access request/response without prop-drilling
653
- const req = getRequest()
654
- const res = getResponse()
655
-
656
- // Access data injected by gateways
657
- const { userId } = getContext<{ userId: string }>()
658
-
659
- // Run background tasks after sending response to client
660
- after(async () => {
661
- await sendNotificationEmail(userId)
662
- })
663
-
664
- res.status(201)
665
- return { success: true }
666
- }
667
- })
351
+ stripeWebhook: route.post('/', {
352
+ cors: {
353
+ origin: 'https://hooks.stripe.com',
354
+ methods: ['POST'],
355
+ },
356
+ handle: async ({ body }) => {
357
+ return { received: true }
358
+ },
359
+ }),
668
360
  })
669
361
  ```
670
362
 
671
-
672
- #### Class-Based (OOP) Paradigm
673
-
674
-
675
- ```typescript filename="src/http/users/route.ts"
676
- import { Controller, Post } from 'exisjs/decorators'
677
- import { getContext, getRequest, getResponse, after } from 'exisjs/router'
678
-
679
- @Controller()
680
- export default class OrdersController {
681
- @Post('/')
682
- async create() {
683
- // You can still use the Context API seamlessly in OOP classes!
684
- const req = getRequest()
685
- const res = getResponse()
686
- const { userId } = getContext<{ userId: string }>()
687
-
688
- after(async () => {
689
- await sendNotificationEmail(userId)
690
- })
691
-
692
- res.status(201)
693
- return { success: true }
694
- }
695
- }
696
- ```
697
-
698
-
699
-
700
- ## Type-Safe Client Route Manifest Contract (`AppRouter`)
363
+ ---
701
364
 
702
- ExisJS generates a compile-time route manifest in `.exis/types.d.ts` (and exposed via root `exis.d.ts`), converting your filesystem routes into an `AppRouter` type definition.
365
+ ## Dependency Resolution in Functional Routes
703
366
 
704
- ### Zero-Cost Return Type & DTO Preservation
705
- When you return data from `handle()`, ExisJS preserves the exact TypeScript return type (including Drizzle/Prisma models, Tex schemas, or custom records) on the route definition:
367
+ Functional routes can resolve dependencies directly from the IoC container using `ctx.resolve()`:
706
368
 
707
369
  ```typescript filename="src/http/users/route.ts"
708
370
  import { controller, route } from 'exisjs/router'
709
- import { tex } from 'exisjs/validator'
371
+ import { UserService } from './service'
710
372
 
711
373
  export default controller({
712
- getUser: route.get('/:id', {
713
- params: tex.object({ id: tex.string() }),
714
- async handle({ params }) {
715
- // Inferred return type is strictly preserved as { id: string, name: string, active: boolean }
716
- return { id: params.id, name: 'Alice', active: true }
717
- }
718
- })
719
- })
720
- ```
721
-
722
- ### Consuming `AppRouter` with `@exisjs/fetch`
723
- You can import `AppRouter` in your frontend or external client applications for zero-cost end-to-end type safety and autocomplete:
724
-
725
- ```typescript filename="src/client/api.ts"
726
- import { createClient } from '@exisjs/fetch'
727
- import type { AppRouter } from '../server/.exis/types'
728
-
729
- const api = createClient<AppRouter>({
730
- baseUrl: 'http://localhost:4000',
374
+ list: route.get('/', {
375
+ handle: async ({ resolve }) => {
376
+ const userService = resolve(UserService)
377
+ return userService.findAll()
378
+ },
379
+ }),
731
380
  })
732
-
733
- // 1. Hierarchical dot-notation RPC call:
734
- const user = await api.users('123').getUser.get()
735
-
736
- // 2. Direct path index call:
737
- const userByPath = await api['/users/:id'].getUser.get({ params: { id: '123' } })
738
381
  ```
739
382
 
740
- ### Dynamic Dev Watcher Sync
741
- During `exis dev`, adding, renaming, moving, or deleting route files automatically updates `.exis/types.d.ts` and `.exis/types/index.d.ts` in real-time without requiring a dev server restart or manual `.exis` folder deletion.
742
-
743
383
  ---
744
384
 
745
- ## Modular Architecture
746
-
747
- While putting all logic in `route.ts` is fine for small apps, ExisJS strongly encourages separating concerns alongside your route definition:
748
-
749
- - **`schema.ts`**: Defines expected input validation constraints.
750
- - **`service.ts`**: Pure functions that interact with your database.
751
- - **`controller.ts`**: Handles business orchestration.
752
- - **`route.ts`**: Wires it to the HTTP network.
385
+ ## Next Steps
753
386
 
387
+ - **[Controllers & Decorators](/docs/controllers)**: Complete guide to class-based routing and parameter decorators.
388
+ - **[Request Object](/docs/requests)**: Deep dive into the `ExisRequest` API.
389
+ - **[Response Object](/docs/responses)**: Status codes, cookie management, streaming, and Server-Sent Events.
390
+ - **[Boundaries](/docs/boundaries)**: Scoped middleware cascading, directory guards, and error boundaries.