exisjs 0.5.9 → 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/auth/session.d.ts +4 -1
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/auth/session.js +33 -5
- package/dist/auth/session.js.map +1 -1
- package/dist/cache/store.d.ts +11 -3
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +87 -19
- package/dist/cache/store.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +4 -6
- 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/middleware/backpressure.d.ts.map +1 -1
- package/dist/middleware/backpressure.js +21 -12
- package/dist/middleware/backpressure.js.map +1 -1
- package/dist/middleware/cache.d.ts +6 -0
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +84 -28
- package/dist/middleware/cache.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +4 -1
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +29 -4
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +16 -0
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +29 -12
- package/dist/middleware/rate-limit.js.map +1 -1
- package/dist/queue/drivers/MemoryDriver.d.ts +6 -3
- package/dist/queue/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/queue/drivers/MemoryDriver.js +76 -23
- package/dist/queue/drivers/MemoryDriver.js.map +1 -1
- package/dist/server/bootstrapper.d.ts +3 -0
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +56 -8
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/bun-adapter.d.ts +2 -0
- package/dist/server/bun-adapter.d.ts.map +1 -1
- package/dist/server/bun-adapter.js +23 -12
- package/dist/server/bun-adapter.js.map +1 -1
- package/dist/server/request-handler.d.ts +0 -2
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +2 -24
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +24 -11
- package/dist/server/request.js.map +1 -1
- package/dist/server/uws-adapter.d.ts +80 -0
- package/dist/server/uws-adapter.d.ts.map +1 -0
- package/dist/server/uws-adapter.js +357 -0
- package/dist/server/uws-adapter.js.map +1 -0
- package/dist/server/ws-orchestrator.d.ts +1 -0
- package/dist/server/ws-orchestrator.d.ts.map +1 -1
- package/dist/server/ws-orchestrator.js +83 -0
- package/dist/server/ws-orchestrator.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/validator/tex.d.ts +21 -16
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/controllers"
|
|
3
|
+
nextTitle: "Controllers"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Routing
|
|
7
|
+
|
|
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.
|
|
9
|
+
|
|
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` 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
|
+
- **`gateway.ts`**: Can be placed in any folder. It acts as a gatekeeper, automatically applying middleware, CORS, headers, and dependencies to *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
|
+
async onStart(app) {
|
|
42
|
+
// Boot your database here!
|
|
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 Gateways (`gateway.ts`)
|
|
63
|
+
|
|
64
|
+
Gateways apply configurations to all routes in their directory and subdirectories.
|
|
65
|
+
|
|
66
|
+
#### Functional Paradigm
|
|
67
|
+
```typescript filename="src/http/admin/gateway.ts"
|
|
68
|
+
import { defineGateway } from 'exisjs/router'
|
|
69
|
+
|
|
70
|
+
export default defineGateway({
|
|
71
|
+
headers: { 'X-Admin-Area': 'true' }
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Class-Based (OOP) Paradigm
|
|
76
|
+
```typescript filename="src/http/admin/gateway.ts"
|
|
77
|
+
import { Gateway } from 'exisjs/decorators'
|
|
78
|
+
|
|
79
|
+
@Gateway({
|
|
80
|
+
headers: { 'X-Admin-Area': 'true' }
|
|
81
|
+
})
|
|
82
|
+
export default class AdminGateway {}
|
|
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'
|
|
94
|
+
|
|
95
|
+
export default controller({
|
|
96
|
+
check: route.get('/', {
|
|
97
|
+
handle() {
|
|
98
|
+
return { status: 'ok', timestamp: new Date().toISOString() }
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
#### Class-Based (OOP) Paradigm
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
```typescript filename="src/http/health/route.ts"
|
|
109
|
+
import { Controller, Get } from 'exisjs/decorators'
|
|
110
|
+
|
|
111
|
+
@Controller()
|
|
112
|
+
export default class HealthController {
|
|
113
|
+
@Get('/')
|
|
114
|
+
check() {
|
|
115
|
+
return { status: 'ok', timestamp: new Date().toISOString() }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## Supported Methods
|
|
123
|
+
|
|
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.
|
|
125
|
+
|
|
126
|
+
#### Functional Paradigm
|
|
127
|
+
|
|
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
|
+
```typescript filename="src/http/users/route.ts"
|
|
162
|
+
import { controller, route } from 'exisjs/router'
|
|
163
|
+
|
|
164
|
+
export default controller({
|
|
165
|
+
list: route.get('/', {
|
|
166
|
+
handle() {
|
|
167
|
+
// Fetch all users from database
|
|
168
|
+
return [
|
|
169
|
+
{ id: 1, name: 'Alice' },
|
|
170
|
+
{ id: 2, name: 'Bob' }
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
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
|
+
}
|
|
180
|
+
}),
|
|
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
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Class-Based (OOP) Paradigm
|
|
192
|
+
|
|
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>
|
|
225
|
+
|
|
226
|
+
```typescript filename="src/http/users/route.ts"
|
|
227
|
+
import { Controller, Get, Post, Body } from 'exisjs/decorators'
|
|
228
|
+
|
|
229
|
+
@Controller()
|
|
230
|
+
export default class UsersController {
|
|
231
|
+
@Get('/')
|
|
232
|
+
list() {
|
|
233
|
+
// Fetch all users from database
|
|
234
|
+
return [
|
|
235
|
+
{ id: 1, name: 'Alice' },
|
|
236
|
+
{ id: 2, name: 'Bob' }
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@Post('/')
|
|
241
|
+
create(@Body() data: any) {
|
|
242
|
+
// Create a new user with parsed payload
|
|
243
|
+
return { id: 3, ...data }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
## Sub-Paths in a Single File
|
|
250
|
+
|
|
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
|
+
const { id } = params
|
|
268
|
+
return { message: \`Fetching user \${id}\` }
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
#### Class-Based (OOP) Paradigm
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
```typescript filename="src/http/users/route.ts"
|
|
279
|
+
import { Controller, Get, Param } from 'exisjs/decorators'
|
|
280
|
+
|
|
281
|
+
@Controller()
|
|
282
|
+
export default class UsersController {
|
|
283
|
+
@Get('/')
|
|
284
|
+
list() {
|
|
285
|
+
return { message: 'Fetching all users' }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@Get('/:id')
|
|
289
|
+
getSingle(@Param('id') id: string) {
|
|
290
|
+
return { message: \`Fetching user \${id}\` }
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
## Route Parameters (Dynamic Routing)
|
|
298
|
+
|
|
299
|
+
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 `[]`.
|
|
300
|
+
|
|
301
|
+
#### Functional Paradigm
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
```typescript filename="src/http/users/[id]/route.ts"
|
|
305
|
+
// Location: src/http/users/[id]/route.ts
|
|
306
|
+
import { controller, route } from 'exisjs/router'
|
|
307
|
+
|
|
308
|
+
export default controller({
|
|
309
|
+
getSingle: route.get('/', {
|
|
310
|
+
handle({ params }) {
|
|
311
|
+
const { id } = params
|
|
312
|
+
return { message: \`Fetching user \${id}\` }
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
#### Class-Based (OOP) Paradigm
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
```typescript filename="src/http/users/[id]/route.ts"
|
|
323
|
+
// Location: src/http/users/[id]/route.ts
|
|
324
|
+
import { Controller, Get, Param } from 'exisjs/decorators'
|
|
325
|
+
|
|
326
|
+
@Controller()
|
|
327
|
+
export default class SingleUserController {
|
|
328
|
+
@Get('/')
|
|
329
|
+
getSingle(@Param('id') id: string) {
|
|
330
|
+
return { message: \`Fetching user \${id}\` }
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
## Request & Response Objects
|
|
338
|
+
|
|
339
|
+
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.
|
|
340
|
+
|
|
341
|
+
#### Functional Paradigm
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
```typescript filename="src/http/profile/route.ts"
|
|
345
|
+
import { controller, route } from 'exisjs/router'
|
|
346
|
+
|
|
347
|
+
export default controller({
|
|
348
|
+
profile: route.get('/profile', {
|
|
349
|
+
handle({ req, res }) {
|
|
350
|
+
// Read Request details
|
|
351
|
+
const ip = req.ip
|
|
352
|
+
const headers = req.headers
|
|
353
|
+
|
|
354
|
+
// Chain Response methods
|
|
355
|
+
res
|
|
356
|
+
.status(202)
|
|
357
|
+
.cookie('session', 'xyz123', { httpOnly: true, secure: true })
|
|
358
|
+
.header('X-Custom-Header', 'Exis')
|
|
359
|
+
.json({ message: 'Accepted' })
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
#### Class-Based (OOP) Paradigm
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
```typescript filename="src/http/profile/route.ts"
|
|
370
|
+
import { Controller, Get, Req, Res } from 'exisjs/decorators'
|
|
371
|
+
import type { Request, Response } from 'exisjs/router'
|
|
372
|
+
|
|
373
|
+
@Controller()
|
|
374
|
+
export default class ProfileController {
|
|
375
|
+
@Get('/profile')
|
|
376
|
+
profile(@Req() req: Request, @Res() res: Response) {
|
|
377
|
+
// Read Request details
|
|
378
|
+
const ip = req.ip
|
|
379
|
+
const headers = req.headers
|
|
380
|
+
|
|
381
|
+
// Chain Response methods
|
|
382
|
+
res
|
|
383
|
+
.status(202)
|
|
384
|
+
.cookie('session', 'xyz123', { httpOnly: true, secure: true })
|
|
385
|
+
.header('X-Custom-Header', 'Exis')
|
|
386
|
+
.json({ message: 'Accepted' })
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
<Info title="Default Responses">
|
|
394
|
+
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!
|
|
395
|
+
</Info>
|
|
396
|
+
|
|
397
|
+
## Payloads & Query Parameters
|
|
398
|
+
|
|
399
|
+
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).
|
|
400
|
+
|
|
401
|
+
#### Functional Paradigm
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
```typescript filename="src/http/search/route.ts"
|
|
405
|
+
import { controller, route } from 'exisjs/router'
|
|
406
|
+
|
|
407
|
+
export default controller({
|
|
408
|
+
search: route.get('/search', {
|
|
409
|
+
handle({ req }) {
|
|
410
|
+
// e.g., GET /search?q=exis&limit=10
|
|
411
|
+
const { q, limit } = req.query
|
|
412
|
+
return { query: q, limit }
|
|
413
|
+
}
|
|
414
|
+
}),
|
|
415
|
+
create: route.post('/create', {
|
|
416
|
+
handle({ req }) {
|
|
417
|
+
// Access parsed JSON body natively
|
|
418
|
+
const data = req.body
|
|
419
|
+
return { received: data }
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
#### Class-Based (OOP) Paradigm
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
```typescript filename="src/http/search/route.ts"
|
|
430
|
+
import { Controller, Get, Post, Query, Body } from 'exisjs/decorators'
|
|
431
|
+
|
|
432
|
+
@Controller()
|
|
433
|
+
export default class SearchController {
|
|
434
|
+
@Get('/search')
|
|
435
|
+
search(@Query('q') q: string, @Query('limit') limit: number) {
|
|
436
|
+
// e.g., GET /search?q=exis&limit=10
|
|
437
|
+
return { query: q, limit }
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
@Post('/create')
|
|
441
|
+
create(@Body() data: any) {
|
|
442
|
+
// Access parsed JSON body natively
|
|
443
|
+
return { received: data }
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
## Dependency Injection in Functional Controllers
|
|
451
|
+
|
|
452
|
+
ExisJS natively supports **Request-Scoped Dependency Injection** directly inside functional routes. You don't need to use classes to get the benefits of DI!
|
|
453
|
+
|
|
454
|
+
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.
|
|
455
|
+
|
|
456
|
+
```typescript filename="src/http/users/route.ts"
|
|
457
|
+
import { controller, route } from 'exisjs/router'
|
|
458
|
+
import { UsersService } from './service'
|
|
459
|
+
|
|
460
|
+
export default controller({
|
|
461
|
+
list: route.get('/', {
|
|
462
|
+
async handle({ resolve }) {
|
|
463
|
+
// 1. Resolves UsersService from the IoC Container
|
|
464
|
+
// 2. Maintains request-scope (the same instance is returned throughout this HTTP request)
|
|
465
|
+
const usersService = resolve(UsersService)
|
|
466
|
+
|
|
467
|
+
return usersService.getAll()
|
|
468
|
+
}
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## The Context API (Advanced)
|
|
474
|
+
|
|
475
|
+
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.
|
|
476
|
+
|
|
477
|
+
#### Functional Paradigm
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
```typescript filename="src/http/users/route.ts"
|
|
481
|
+
import { controller, route, getContext, getRequest, getResponse, after } from 'exisjs/router'
|
|
482
|
+
|
|
483
|
+
export default controller({
|
|
484
|
+
create: route.post('/', {
|
|
485
|
+
async handle() {
|
|
486
|
+
// Access request/response without prop-drilling
|
|
487
|
+
const req = getRequest()
|
|
488
|
+
const res = getResponse()
|
|
489
|
+
|
|
490
|
+
// Access data injected by gateways
|
|
491
|
+
const { userId } = getContext<{ userId: string }>()
|
|
492
|
+
|
|
493
|
+
// Run background tasks after sending response to client
|
|
494
|
+
after(async () => {
|
|
495
|
+
await sendNotificationEmail(userId)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
res.status(201)
|
|
499
|
+
return { success: true }
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
})
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
#### Class-Based (OOP) Paradigm
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
```typescript filename="src/http/users/route.ts"
|
|
510
|
+
import { Controller, Post } from 'exisjs/decorators'
|
|
511
|
+
import { getContext, getRequest, getResponse, after } from 'exisjs/router'
|
|
512
|
+
|
|
513
|
+
@Controller()
|
|
514
|
+
export default class OrdersController {
|
|
515
|
+
@Post('/')
|
|
516
|
+
async create() {
|
|
517
|
+
// You can still use the Context API seamlessly in OOP classes!
|
|
518
|
+
const req = getRequest()
|
|
519
|
+
const res = getResponse()
|
|
520
|
+
const { userId } = getContext<{ userId: string }>()
|
|
521
|
+
|
|
522
|
+
after(async () => {
|
|
523
|
+
await sendNotificationEmail(userId)
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
res.status(201)
|
|
527
|
+
return { success: true }
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
## Modular Architecture
|
|
535
|
+
|
|
536
|
+
While putting all logic in `route.ts` is fine for small apps, ExisJS strongly encourages separating concerns alongside your route definition:
|
|
537
|
+
|
|
538
|
+
- **`schema.ts`**: Defines expected input validation constraints.
|
|
539
|
+
- **`service.ts`**: Pure functions that interact with your database.
|
|
540
|
+
- **`controller.ts`**: Handles business orchestration.
|
|
541
|
+
- **`route.ts`**: Wires it to the HTTP network.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/decorators"
|
|
3
|
+
nextTitle: "Custom Decorators"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RPC Client (End-to-End Type Safety)
|
|
7
|
+
|
|
8
|
+
ExisJS comes with a native RPC (Remote Procedure Call) client designed to give you **end-to-end type safety** without needing code generation (like OpenAPI/Swagger) or GraphQL.
|
|
9
|
+
|
|
10
|
+
Because ExisJS infers the types of your backend routes natively, you can share those types directly with your frontend (React, Vue, React Native) and get autocomplete, payload validation, and response typing.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Exporting the AppRouter Type
|
|
15
|
+
|
|
16
|
+
To use the strictly typed client, you simply need to export the inferred type of your ExisJS application from your main backend entry point.
|
|
17
|
+
|
|
18
|
+
```typescript filename="backend/src/http/server.ts"
|
|
19
|
+
import { exis } from 'exisjs'
|
|
20
|
+
|
|
21
|
+
const app = exis({
|
|
22
|
+
async onStart(app) {
|
|
23
|
+
// Backend boot logic...
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Export the inferred router type for the frontend
|
|
28
|
+
export type AppRouter = typeof app
|
|
29
|
+
|
|
30
|
+
export default app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
*(Note: Because this is just a TypeScript `type` export, no backend code gets compiled into your frontend bundle.)*
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Initializing the Client
|
|
38
|
+
|
|
39
|
+
On your frontend (React, Vue, Svelte, etc.), install the lightweight client package:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @exisjs/client
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then, initialize the client using the `AppRouter` type from your backend:
|
|
46
|
+
|
|
47
|
+
```typescript filename="frontend/src/api.ts"
|
|
48
|
+
import { createClient } from '@exisjs/client'
|
|
49
|
+
import type { AppRouter } from '../../backend/src/http/server'
|
|
50
|
+
|
|
51
|
+
export const client = createClient<AppRouter>({
|
|
52
|
+
baseUrl: 'http://localhost:3000',
|
|
53
|
+
|
|
54
|
+
// Optionally provide default headers (like Auth tokens)
|
|
55
|
+
headers: () => ({
|
|
56
|
+
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
57
|
+
}),
|
|
58
|
+
|
|
59
|
+
// Global error handler
|
|
60
|
+
onError: (err) => {
|
|
61
|
+
if (err.status === 401) {
|
|
62
|
+
window.location.href = '/login'
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 3. Using the Client
|
|
71
|
+
|
|
72
|
+
Because ExisJS uses an ES6 `Proxy` under the hood, the client dynamically maps your chained object calls into HTTP requests. It mirrors your backend's folder structure.
|
|
73
|
+
|
|
74
|
+
If you have a backend route at `src/http/api/users/route.ts` with a `get` handler:
|
|
75
|
+
|
|
76
|
+
```typescript filename="frontend/src/components/UserProfile.tsx"
|
|
77
|
+
import { client } from '../api'
|
|
78
|
+
|
|
79
|
+
async function fetchUser() {
|
|
80
|
+
// 1. Autocomplete for .api.users.get
|
|
81
|
+
// 2. Strong typing for the payload (e.g. { id: string })
|
|
82
|
+
// 3. The `user` variable is strictly typed to whatever the backend returns
|
|
83
|
+
const user = await client.api.users.get({
|
|
84
|
+
id: "123"
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
console.log(user.name)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Passing Query Parameters
|
|
92
|
+
|
|
93
|
+
If you need to pass HTTP query parameters (like `?sort=desc`), you can use the `options` argument (the second parameter):
|
|
94
|
+
|
|
95
|
+
```typescript filename="frontend/src/components/UserList.tsx"
|
|
96
|
+
const users = await client.api.users.get(
|
|
97
|
+
// The first argument is the JSON payload (Body/Params)
|
|
98
|
+
{},
|
|
99
|
+
// The second argument configures the Request Options
|
|
100
|
+
{
|
|
101
|
+
query: { sort: 'desc', limit: 10 },
|
|
102
|
+
headers: { 'X-Custom-Override': 'true' }
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 4. Advanced Configuration
|
|
110
|
+
|
|
111
|
+
The `createClient` function accepts configuration options to intercept requests and responses globally.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
export interface ClientConfig {
|
|
115
|
+
// The base URL of your ExisJS backend
|
|
116
|
+
baseUrl: string
|
|
117
|
+
|
|
118
|
+
// Static headers, or a dynamic function evaluated per-request
|
|
119
|
+
headers?: Record<string, string> | (() => Record<string, string>)
|
|
120
|
+
|
|
121
|
+
// Provide a custom fetch implementation (useful for testing or Polyfills)
|
|
122
|
+
fetch?: typeof fetch
|
|
123
|
+
|
|
124
|
+
// A global hook that runs BEFORE the request is dispatched
|
|
125
|
+
onRequest?: (req: RequestInit, url: string) => void | Promise<void>
|
|
126
|
+
|
|
127
|
+
// A global hook that runs AFTER the request successfully completes
|
|
128
|
+
onResponse?: (res: Response, url: string) => void | Promise<void>
|
|
129
|
+
|
|
130
|
+
// A global hook that catches any failed requests
|
|
131
|
+
onError?: (err: Error) => void | Promise<void>
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### ExisClientError
|
|
136
|
+
|
|
137
|
+
If an HTTP request fails (e.g., the backend returns a `400 Bad Request` or `500 Internal Server Error`), the client throws an `ExisClientError`. This error attaches the status code and parsed JSON response.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
try {
|
|
141
|
+
await client.api.users.post({ email: "invalid-email" })
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.log(err.status) // 400
|
|
144
|
+
console.log(err.data) // { success: false, error: "Validation Failed..." }
|
|
145
|
+
}
|
|
146
|
+
```
|