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,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextUrl: "/docs/security"
|
|
3
|
+
nextTitle: "Security & Auth"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dataloader (N+1 Queries)
|
|
7
|
+
|
|
8
|
+
The **N+1 problem** is a common performance issue in backend development. It occurs when your code executes one query to fetch a list of items (e.g. 100 Posts), and then executes $N$ additional queries to fetch related data (e.g. 100 separate queries for each Post's Author).
|
|
9
|
+
|
|
10
|
+
ExisJS ships with a native `Dataloader` implementation (inspired by GraphQL/Facebook's DataLoader) built directly into the core framework. It automatically **batches** and **caches** requests over a single tick of the Node.js event loop.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Defining Loaders
|
|
15
|
+
|
|
16
|
+
The best practice in ExisJS is to define a central registry of Dataloaders that are **securely scoped to the current HTTP Request** (to prevent data leaking between different users).
|
|
17
|
+
|
|
18
|
+
ExisJS provides `defineLoaders` to create this context-aware registry.
|
|
19
|
+
|
|
20
|
+
```typescript filename="src/loaders.ts"
|
|
21
|
+
import { Dataloader, defineLoaders } from 'exisjs/dataloader'
|
|
22
|
+
import { getRequest } from 'exisjs/router'
|
|
23
|
+
import { db } from './db'
|
|
24
|
+
|
|
25
|
+
export const { loaderMiddleware, getLoaders } = defineLoaders({
|
|
26
|
+
// 1. Author Loader
|
|
27
|
+
authorLoader: () => new Dataloader<number, any>(async (keys) => {
|
|
28
|
+
// You can access the current HTTP Request anywhere using getRequest()
|
|
29
|
+
const req = getRequest()
|
|
30
|
+
const authToken = req.headers.authorization
|
|
31
|
+
|
|
32
|
+
// This function will only be called ONCE per event-loop tick,
|
|
33
|
+
// receiving an array of all requested Author IDs: e.g. [1, 2, 3]
|
|
34
|
+
const authors = await db.authors.findMany({
|
|
35
|
+
where: { id: { in: keys } }
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// You MUST return an array of results in the EXACT same order as the keys
|
|
39
|
+
return keys.map(key => authors.find(a => a.id === key) || new Error('Not found'))
|
|
40
|
+
}),
|
|
41
|
+
|
|
42
|
+
// 2. Comments Loader
|
|
43
|
+
commentsLoader: () => new Dataloader<number, any[]>(async (keys) => {
|
|
44
|
+
const comments = await db.comments.findMany({
|
|
45
|
+
where: { postId: { in: keys } }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Group comments by postId
|
|
49
|
+
return keys.map(key => comments.filter(c => c.postId === key))
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 2. Registering the Middleware
|
|
57
|
+
|
|
58
|
+
`defineLoaders` returns a `loaderMiddleware`. You must register this middleware so that ExisJS can securely inject a fresh, isolated instance of your Dataloaders into every incoming request.
|
|
59
|
+
|
|
60
|
+
Because ExisJS uses lazy-initialization, there is **zero performance penalty** for routes that don't actually use the loaders.
|
|
61
|
+
|
|
62
|
+
#### Functional Paradigm
|
|
63
|
+
|
|
64
|
+
```typescript filename="src/http/server.ts"
|
|
65
|
+
import { exis } from 'exisjs'
|
|
66
|
+
import { loaderMiddleware } from '../loaders'
|
|
67
|
+
|
|
68
|
+
export default exis({
|
|
69
|
+
async onStart(app) {
|
|
70
|
+
// Register globally for all routes
|
|
71
|
+
app.use(loaderMiddleware)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Class-Based (OOP) Paradigm
|
|
77
|
+
|
|
78
|
+
```typescript filename="src/http/server.ts"
|
|
79
|
+
import { Server } from 'exisjs/decorators'
|
|
80
|
+
import { loaderMiddleware } from '../loaders'
|
|
81
|
+
import type { App } from 'exisjs'
|
|
82
|
+
|
|
83
|
+
@Server()
|
|
84
|
+
export default class RootServer {
|
|
85
|
+
async onStart(app: App) {
|
|
86
|
+
// Register globally for all routes
|
|
87
|
+
app.use(loaderMiddleware)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 3. Using Dataloader in Routes
|
|
95
|
+
|
|
96
|
+
Anywhere in your application (Routes, Controllers, Services), you can call `getLoaders()` to retrieve the securely isolated loaders for the *current* request.
|
|
97
|
+
|
|
98
|
+
#### Functional Paradigm
|
|
99
|
+
|
|
100
|
+
```typescript filename="src/http/posts/route.ts"
|
|
101
|
+
import { controller, route } from 'exisjs/router'
|
|
102
|
+
import { getLoaders } from '../../loaders'
|
|
103
|
+
|
|
104
|
+
export default controller({
|
|
105
|
+
list: route.get('/', {
|
|
106
|
+
async handle() {
|
|
107
|
+
// 1. Fetch 100 posts (1 Query)
|
|
108
|
+
const posts = await db.posts.findMany()
|
|
109
|
+
|
|
110
|
+
const { authorLoader } = getLoaders()
|
|
111
|
+
|
|
112
|
+
// 2. Loop through 100 posts and call .load()
|
|
113
|
+
const postsWithAuthors = await Promise.all(
|
|
114
|
+
posts.map(async (post) => {
|
|
115
|
+
// Instead of firing 100 queries to the database,
|
|
116
|
+
// Dataloader queues them up and fires exactly ONE batched query.
|
|
117
|
+
const author = await authorLoader.load(post.authorId)
|
|
118
|
+
return { ...post, author }
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return postsWithAuthors
|
|
123
|
+
// Total Queries: 2 (Not 101)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Class-Based (OOP) Paradigm
|
|
130
|
+
|
|
131
|
+
```typescript filename="src/http/posts/route.ts"
|
|
132
|
+
import { Controller, Get } from 'exisjs/decorators'
|
|
133
|
+
import { getLoaders } from '../../loaders'
|
|
134
|
+
|
|
135
|
+
@Controller()
|
|
136
|
+
export default class PostsController {
|
|
137
|
+
@Get('/')
|
|
138
|
+
async list() {
|
|
139
|
+
// 1. Fetch 100 posts (1 Query)
|
|
140
|
+
const posts = await db.posts.findMany()
|
|
141
|
+
|
|
142
|
+
const { authorLoader } = getLoaders()
|
|
143
|
+
|
|
144
|
+
// 2. Loop through 100 posts and call .load()
|
|
145
|
+
const postsWithAuthors = await Promise.all(
|
|
146
|
+
posts.map(async (post) => {
|
|
147
|
+
// Instead of firing 100 queries to the database,
|
|
148
|
+
// Dataloader queues them up and fires exactly ONE batched query.
|
|
149
|
+
const author = await authorLoader.load(post.authorId)
|
|
150
|
+
return { ...post, author }
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return postsWithAuthors
|
|
155
|
+
// Total Queries: 2 (Not 101)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## API Reference (`exisjs/dataloader`)
|
|
163
|
+
|
|
164
|
+
The `Dataloader` class exposes the standard, battle-tested API:
|
|
165
|
+
|
|
166
|
+
- **`load(key: K): Promise<V>`**: Queues a key to be fetched and returns a Promise.
|
|
167
|
+
- **`loadMany(keys: K[]): Promise<V[]>`**: Equivalent to calling `Promise.all(keys.map(k => load(k)))`.
|
|
168
|
+
- **`clear(key: K): this`**: Clears the cached result for a specific key.
|
|
169
|
+
- **`clearAll(): this`**: Clears the entire cache for this dataloader instance.
|
|
170
|
+
- **`prime(key: K, value: V): this`**: Primes the cache with a predefined value, preventing the batch function from being called for that key.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Decorators
|
|
2
|
+
|
|
3
|
+
ExisJS provides standard ES6 decorators for developers who prefer the Object-Oriented Programming (OOP) paradigm (inspired by NestJS).
|
|
4
|
+
|
|
5
|
+
Because ExisJS does not rely on reflection libraries like `reflect-metadata`, these decorators are fast and memory-efficient.
|
|
6
|
+
|
|
7
|
+
All decorators can be imported directly from `exisjs/decorators`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Servers & Gateways
|
|
12
|
+
|
|
13
|
+
ExisJS allows you to configure your root application and directory-scoped gateways using decorators, replacing the need for functional `exis()` and `defineGateway()` wrappers if you prefer OOP.
|
|
14
|
+
|
|
15
|
+
### `@Server(config)`
|
|
16
|
+
Used exclusively in your `src/http/server.ts` file to bootstrap the application.
|
|
17
|
+
|
|
18
|
+
```typescript filename="src/http/server.ts"
|
|
19
|
+
import { Server } from 'exisjs/decorators'
|
|
20
|
+
import { metricsPlugin } from './plugins/metrics'
|
|
21
|
+
import type { App } from 'exisjs'
|
|
22
|
+
|
|
23
|
+
@Server({
|
|
24
|
+
plugins: [metricsPlugin],
|
|
25
|
+
providers: [['Logger', { useValue: console }]]
|
|
26
|
+
})
|
|
27
|
+
export default class RootServer {
|
|
28
|
+
async onStart(app: App) {
|
|
29
|
+
console.log('Server started')
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `@Gateway(config)`
|
|
35
|
+
Used inside any folder (e.g. `src/http/admin/gateway.ts`) to act as a gatekeeper for all routes within that directory and its subdirectories.
|
|
36
|
+
|
|
37
|
+
```typescript filename="src/http/admin/gateway.ts"
|
|
38
|
+
import { Gateway } from 'exisjs/decorators'
|
|
39
|
+
import { requireAuth } from '../../middleware/auth'
|
|
40
|
+
|
|
41
|
+
@Gateway({
|
|
42
|
+
middleware: [requireAuth],
|
|
43
|
+
cors: { origin: 'https://admin.com' },
|
|
44
|
+
timeout: 10000,
|
|
45
|
+
metadata: { openapi: { tags: ['Admin'] } }
|
|
46
|
+
})
|
|
47
|
+
export default class AdminGateway {}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 1. Controllers & Routing
|
|
53
|
+
|
|
54
|
+
To map a class to a route prefix, use the `@Controller()` decorator. You can then use HTTP Method decorators to map class methods to specific endpoints.
|
|
55
|
+
|
|
56
|
+
```typescript filename="src/http/users/route.ts"
|
|
57
|
+
import { Controller, Get, Post, Put, Delete } from 'exisjs/decorators'
|
|
58
|
+
|
|
59
|
+
@Controller('/users')
|
|
60
|
+
export default class UsersController {
|
|
61
|
+
|
|
62
|
+
@Get('/')
|
|
63
|
+
async listUsers() {
|
|
64
|
+
return { users: [] }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Post('/create')
|
|
68
|
+
async createUser() {
|
|
69
|
+
return { success: true }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Delete('/:id')
|
|
73
|
+
async deleteUser() {}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Available Method Decorators
|
|
78
|
+
- `@Get(path?, schema?)`
|
|
79
|
+
- `@Post(path?, schema?)`
|
|
80
|
+
- `@Put(path?, schema?)`
|
|
81
|
+
- `@Patch(path?, schema?)`
|
|
82
|
+
- `@Delete(path?, schema?)`
|
|
83
|
+
- `@Options(path?, schema?)`
|
|
84
|
+
- `@Head(path?, schema?)`
|
|
85
|
+
- `@All(path?, schema?)`
|
|
86
|
+
- `@Ws(path?, schema?)` (For WebSockets)
|
|
87
|
+
- `@Sse(path?, schema?)` (For Server-Sent Events)
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 2. Request Parameters
|
|
92
|
+
|
|
93
|
+
Instead of manually digging through the `req` object, ExisJS allows you to inject request properties directly into your method parameters.
|
|
94
|
+
|
|
95
|
+
```typescript filename="src/http/users/route.ts"
|
|
96
|
+
import { Controller, Post, Body, Param, Headers, Req, Res } from 'exisjs/decorators'
|
|
97
|
+
|
|
98
|
+
@Controller('/users')
|
|
99
|
+
export default class UsersController {
|
|
100
|
+
|
|
101
|
+
@Post('/:id')
|
|
102
|
+
async update(
|
|
103
|
+
@Param('id') id: string,
|
|
104
|
+
@Body() payload: any,
|
|
105
|
+
@Headers('authorization') token: string,
|
|
106
|
+
@Req() req: any,
|
|
107
|
+
@Res() res: any
|
|
108
|
+
) {
|
|
109
|
+
return { success: true, id, payload }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Available Parameter Decorators
|
|
115
|
+
- `@Body(key?)` - Extracts `req.body` or a specific key.
|
|
116
|
+
- `@Param(key?)` - Extracts `req.params` or a specific key.
|
|
117
|
+
- `@Query(key?)` - Extracts `req.query` or a specific key.
|
|
118
|
+
- `@Headers(key?)` - Extracts `req.headers` or a specific key.
|
|
119
|
+
- `@Req()` - Injects the underlying `ExisRequest` object.
|
|
120
|
+
- `@Res({ passthrough?: boolean })` - Injects the underlying `ExisResponse` object.
|
|
121
|
+
- `@Ip()` - Extracts the client IP address.
|
|
122
|
+
- `@Next()` - Injects the `next()` function for middleware bypass.
|
|
123
|
+
- `@Session()` - Injects `req.session` (if a session plugin is active).
|
|
124
|
+
- `@Socket()` - Injects the `ExisWebSocket` (only valid in `@Ws` routes).
|
|
125
|
+
- `@Stream()` - Injects the `ExisSSE` stream (only valid in `@Sse` routes).
|
|
126
|
+
- `@UploadedFile()` / `@UploadedFiles()` - Automatically extracts multipart form-data files.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 3. Middleware & Lifecycles
|
|
131
|
+
|
|
132
|
+
You can bind Middleware, Guards, Exception Filters, and Interceptors directly to your Controllers or individual methods using decorators.
|
|
133
|
+
|
|
134
|
+
```typescript filename="src/http/admin/route.ts"
|
|
135
|
+
import { Controller, Get, Use, UseGuards, UseFilters } from 'exisjs/decorators'
|
|
136
|
+
import { requireAuth } from '../../middleware/auth'
|
|
137
|
+
import { RoleGuard } from '../../guards/role'
|
|
138
|
+
import { AdminErrorFilter } from '../../filters/admin'
|
|
139
|
+
|
|
140
|
+
@Controller('/admin')
|
|
141
|
+
@Use(requireAuth) // Applies to the entire class
|
|
142
|
+
@UseFilters(AdminErrorFilter) // Applies to the entire class
|
|
143
|
+
export default class AdminController {
|
|
144
|
+
|
|
145
|
+
@Get('/dashboard')
|
|
146
|
+
@UseGuards(RoleGuard) // Applies ONLY to this method
|
|
147
|
+
async getDashboard() {
|
|
148
|
+
return { secret: 'data' }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Available Lifecycle Decorators
|
|
154
|
+
- `@Use(...middlewares)` - Binds standard Express-style middleware.
|
|
155
|
+
- `@UseGuards(...guards)` - Binds ExisJS authorization Guards.
|
|
156
|
+
- `@UseInterceptors(...interceptors)` - Binds response Interceptors.
|
|
157
|
+
- `@UseFilters(...filters)` - Binds Exception Filters.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 4. Response Modifiers
|
|
162
|
+
|
|
163
|
+
Sometimes you need to modify the HTTP Response (like changing the status code or adding a header) without injecting the raw `@Res()` object.
|
|
164
|
+
|
|
165
|
+
```typescript filename="src/http/api/route.ts"
|
|
166
|
+
import { Controller, Post, HttpCode, Header, Redirect } from 'exisjs/decorators'
|
|
167
|
+
|
|
168
|
+
@Controller('/api')
|
|
169
|
+
export default class ApiController {
|
|
170
|
+
|
|
171
|
+
@Post('/create')
|
|
172
|
+
@HttpCode(201) // Forces the response status to 201 Created
|
|
173
|
+
@Header('X-Custom-Version', '2.0') // Appends a custom header
|
|
174
|
+
async create() {
|
|
175
|
+
return { success: true }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@Get('/legacy')
|
|
179
|
+
@Redirect('/new-api', 301) // Automatically redirects the client
|
|
180
|
+
async legacy() {}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Available Response & Metadata Decorators
|
|
185
|
+
- `@HttpCode(code: number)`
|
|
186
|
+
- `@Header(name: string, value: string)`
|
|
187
|
+
- `@Redirect(url: string, statusCode?: number)`
|
|
188
|
+
- `@Hosts(...hosts: string[])` - Restricts a route/controller to a specific Host header (e.g. `api.example.com`).
|
|
189
|
+
- `@Permissions(...permissions: string[])` - Restricts a route/controller to users with specific permissions.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 5. Dependency Injection
|
|
194
|
+
|
|
195
|
+
To mark a class as a service that can be injected into your controllers, use the `@Injectable()` decorator.
|
|
196
|
+
|
|
197
|
+
```typescript filename="src/services/db.ts"
|
|
198
|
+
import { Injectable } from 'exisjs/decorators'
|
|
199
|
+
|
|
200
|
+
@Injectable({ scope: 'singleton' })
|
|
201
|
+
export class DatabaseService {
|
|
202
|
+
async findUser() { ... }
|
|
203
|
+
}
|
|
204
|
+
```
|