@riligar/agents-kit 1.11.0 → 1.12.0
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/.agent/skills/riligar-dev-backend/SKILL.md +122 -87
- package/.agent/skills/riligar-dev-backend/references/elysia-basics.md +224 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-lifecycle.md +268 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-patterns.md +324 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-plugins.md +202 -0
- package/.agent/skills/riligar-dev-backend/references/elysia-validation.md +247 -0
- package/package.json +1 -1
- package/.agent/skills/riligar-dev-backend/api-style.md +0 -42
- package/.agent/skills/riligar-dev-backend/auth.md +0 -24
- package/.agent/skills/riligar-dev-backend/documentation.md +0 -26
- package/.agent/skills/riligar-dev-backend/graphql.md +0 -41
- package/.agent/skills/riligar-dev-backend/rate-limiting.md +0 -31
- package/.agent/skills/riligar-dev-backend/response.md +0 -37
- package/.agent/skills/riligar-dev-backend/rest.md +0 -40
- package/.agent/skills/riligar-dev-backend/security-testing.md +0 -122
- package/.agent/skills/riligar-dev-backend/trpc.md +0 -41
- package/.agent/skills/riligar-dev-backend/versioning.md +0 -22
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Elysia Lifecycle Hooks
|
|
2
|
+
|
|
3
|
+
Lifecycle hooks allow you to intercept requests at different stages. They're the foundation for middleware, auth, logging, and error handling.
|
|
4
|
+
|
|
5
|
+
## Lifecycle Order
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Request → onRequest → onParse → onBeforeHandle → Handler → onAfterHandle → onAfterResponse
|
|
9
|
+
↓
|
|
10
|
+
onError (if error thrown)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## onRequest
|
|
14
|
+
|
|
15
|
+
First hook executed. Minimal context, best for early checks.
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const app = new Elysia()
|
|
19
|
+
.onRequest(({ request }) => {
|
|
20
|
+
console.log(`${request.method} ${request.url}`)
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Use cases:**
|
|
25
|
+
- Request logging
|
|
26
|
+
- Rate limiting
|
|
27
|
+
- IP blocking
|
|
28
|
+
- CORS headers
|
|
29
|
+
|
|
30
|
+
**Early return skips remaining lifecycle:**
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
.onRequest(({ request, set }) => {
|
|
34
|
+
if (isBlocked(request)) {
|
|
35
|
+
set.status = 403
|
|
36
|
+
return { error: 'Forbidden' }
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## onBeforeHandle
|
|
42
|
+
|
|
43
|
+
Runs after validation, before handler. Has full context.
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
const app = new Elysia()
|
|
47
|
+
.onBeforeHandle(({ headers, set }) => {
|
|
48
|
+
if (!headers.authorization) {
|
|
49
|
+
set.status = 401
|
|
50
|
+
return { error: 'Unauthorized' }
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Use cases:**
|
|
56
|
+
- Authentication checks
|
|
57
|
+
- Authorization
|
|
58
|
+
- Custom validation
|
|
59
|
+
- Request transformation
|
|
60
|
+
|
|
61
|
+
## onAfterHandle
|
|
62
|
+
|
|
63
|
+
Runs after handler, can transform response.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const app = new Elysia()
|
|
67
|
+
.onAfterHandle(({ response, set }) => {
|
|
68
|
+
// Add header to all responses
|
|
69
|
+
set.headers['X-Powered-By'] = 'Elysia'
|
|
70
|
+
|
|
71
|
+
// Transform response
|
|
72
|
+
if (typeof response === 'object') {
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
data: response,
|
|
76
|
+
timestamp: Date.now()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Use cases:**
|
|
83
|
+
- Response transformation
|
|
84
|
+
- Adding headers
|
|
85
|
+
- Logging response
|
|
86
|
+
|
|
87
|
+
## onError
|
|
88
|
+
|
|
89
|
+
Catches any error thrown during lifecycle.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const app = new Elysia()
|
|
93
|
+
.onError(({ code, error, set }) => {
|
|
94
|
+
console.error(error)
|
|
95
|
+
|
|
96
|
+
switch (code) {
|
|
97
|
+
case 'VALIDATION':
|
|
98
|
+
set.status = 422
|
|
99
|
+
return { error: 'Validation failed', details: error.all }
|
|
100
|
+
|
|
101
|
+
case 'NOT_FOUND':
|
|
102
|
+
set.status = 404
|
|
103
|
+
return { error: 'Not found' }
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
set.status = 500
|
|
107
|
+
return { error: 'Internal server error' }
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Error codes:**
|
|
113
|
+
- `VALIDATION` - Schema validation failed
|
|
114
|
+
- `NOT_FOUND` - Route not found
|
|
115
|
+
- `PARSE` - Body parsing failed
|
|
116
|
+
- `INTERNAL_SERVER_ERROR` - Unhandled error
|
|
117
|
+
- `UNKNOWN` - Unknown error type
|
|
118
|
+
|
|
119
|
+
## onAfterResponse
|
|
120
|
+
|
|
121
|
+
Runs after response is sent. Cannot modify response.
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
const app = new Elysia()
|
|
125
|
+
.onAfterResponse(({ request, set }) => {
|
|
126
|
+
console.log(`Completed: ${request.method} ${request.url} - ${set.status}`)
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Use cases:**
|
|
131
|
+
- Logging
|
|
132
|
+
- Analytics
|
|
133
|
+
- Cleanup
|
|
134
|
+
|
|
135
|
+
## Local vs Interceptor Hooks
|
|
136
|
+
|
|
137
|
+
### Local Hook (single route)
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
app.get('/users', () => getUsers(), {
|
|
141
|
+
beforeHandle: ({ headers, set }) => {
|
|
142
|
+
if (!headers.authorization) {
|
|
143
|
+
set.status = 401
|
|
144
|
+
return { error: 'Unauthorized' }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Interceptor Hook (all subsequent routes)
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const app = new Elysia()
|
|
154
|
+
.onBeforeHandle(() => console.log('Runs on all routes below'))
|
|
155
|
+
.get('/a', () => 'A') // Hook runs
|
|
156
|
+
.get('/b', () => 'B') // Hook runs
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Practical Examples
|
|
160
|
+
|
|
161
|
+
### Authentication Middleware
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const requireAuth = ({ headers, set }) => {
|
|
165
|
+
const token = headers.authorization?.replace('Bearer ', '')
|
|
166
|
+
|
|
167
|
+
if (!token) {
|
|
168
|
+
set.status = 401
|
|
169
|
+
return { error: 'Missing token' }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const user = verifyToken(token)
|
|
174
|
+
// User available in handler
|
|
175
|
+
} catch {
|
|
176
|
+
set.status = 401
|
|
177
|
+
return { error: 'Invalid token' }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const app = new Elysia()
|
|
182
|
+
// Public routes
|
|
183
|
+
.get('/health', () => ({ status: 'ok' }))
|
|
184
|
+
|
|
185
|
+
// Protected routes
|
|
186
|
+
.guard({ beforeHandle: requireAuth }, app => app
|
|
187
|
+
.get('/profile', () => getProfile())
|
|
188
|
+
.put('/profile', ({ body }) => updateProfile(body))
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Request Logging
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const logger = new Elysia({ name: 'logger' })
|
|
196
|
+
.onRequest(({ request }) => {
|
|
197
|
+
const start = Date.now()
|
|
198
|
+
request.startTime = start
|
|
199
|
+
})
|
|
200
|
+
.onAfterResponse(({ request, set }) => {
|
|
201
|
+
const duration = Date.now() - request.startTime
|
|
202
|
+
console.log(`${request.method} ${request.url} ${set.status} ${duration}ms`)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
app.use(logger)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Error Wrapper
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
const app = new Elysia()
|
|
212
|
+
.onError(({ code, error, path, set }) => {
|
|
213
|
+
// Log error
|
|
214
|
+
console.error({
|
|
215
|
+
path,
|
|
216
|
+
code,
|
|
217
|
+
message: error.message,
|
|
218
|
+
stack: error.stack
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Don't expose internal errors
|
|
222
|
+
if (code === 'INTERNAL_SERVER_ERROR') {
|
|
223
|
+
set.status = 500
|
|
224
|
+
return {
|
|
225
|
+
error: 'Something went wrong',
|
|
226
|
+
requestId: generateRequestId()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Validation errors - expose details
|
|
231
|
+
if (code === 'VALIDATION') {
|
|
232
|
+
set.status = 422
|
|
233
|
+
return {
|
|
234
|
+
error: 'Validation failed',
|
|
235
|
+
details: error.all
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Rate Limiting
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
const rateLimits = new Map()
|
|
245
|
+
|
|
246
|
+
const rateLimit = (limit = 100, windowMs = 60000) => {
|
|
247
|
+
return new Elysia({ name: 'rate-limit' })
|
|
248
|
+
.onRequest(({ request, set }) => {
|
|
249
|
+
const ip = request.headers.get('x-forwarded-for') || 'unknown'
|
|
250
|
+
const now = Date.now()
|
|
251
|
+
const windowStart = now - windowMs
|
|
252
|
+
|
|
253
|
+
const requests = rateLimits.get(ip) || []
|
|
254
|
+
const recentRequests = requests.filter(t => t > windowStart)
|
|
255
|
+
|
|
256
|
+
if (recentRequests.length >= limit) {
|
|
257
|
+
set.status = 429
|
|
258
|
+
set.headers['Retry-After'] = Math.ceil(windowMs / 1000)
|
|
259
|
+
return { error: 'Too many requests' }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
recentRequests.push(now)
|
|
263
|
+
rateLimits.set(ip, recentRequests)
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
app.use(rateLimit(100, 60000)) // 100 requests per minute
|
|
268
|
+
```
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Elysia API Patterns
|
|
2
|
+
|
|
3
|
+
## REST Resource Naming
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Rules:
|
|
7
|
+
├── Use NOUNS, not verbs (resources, not actions)
|
|
8
|
+
├── Use PLURAL forms (/users not /user)
|
|
9
|
+
├── Use lowercase with hyphens (/user-profiles)
|
|
10
|
+
├── Nest for relationships (/users/123/posts)
|
|
11
|
+
└── Keep shallow (max 3 levels deep)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Examples
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
// Good
|
|
18
|
+
GET /users // List users
|
|
19
|
+
GET /users/:id // Get user
|
|
20
|
+
POST /users // Create user
|
|
21
|
+
PUT /users/:id // Replace user
|
|
22
|
+
PATCH /users/:id // Update user
|
|
23
|
+
DELETE /users/:id // Delete user
|
|
24
|
+
|
|
25
|
+
GET /users/:id/posts // User's posts
|
|
26
|
+
POST /users/:id/posts // Create post for user
|
|
27
|
+
|
|
28
|
+
// Bad
|
|
29
|
+
GET /getUsers
|
|
30
|
+
POST /createUser
|
|
31
|
+
GET /users/123/posts/456/comments/789/likes // Too deep
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## HTTP Status Codes
|
|
35
|
+
|
|
36
|
+
| Situation | Code | When |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Success (read) | `200` | GET succeeded |
|
|
39
|
+
| Created | `201` | POST created resource |
|
|
40
|
+
| No content | `204` | DELETE succeeded |
|
|
41
|
+
| Bad request | `400` | Malformed request |
|
|
42
|
+
| Unauthorized | `401` | Missing/invalid auth |
|
|
43
|
+
| Forbidden | `403` | Valid auth, no permission |
|
|
44
|
+
| Not found | `404` | Resource doesn't exist |
|
|
45
|
+
| Conflict | `409` | State conflict (duplicate) |
|
|
46
|
+
| Validation error | `422` | Valid syntax, invalid data |
|
|
47
|
+
| Rate limited | `429` | Too many requests |
|
|
48
|
+
| Server error | `500` | Unhandled error |
|
|
49
|
+
|
|
50
|
+
## Response Patterns
|
|
51
|
+
|
|
52
|
+
### Success Response
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// Single resource
|
|
56
|
+
app.get('/users/:id', ({ params, set }) => {
|
|
57
|
+
const user = getUserById(params.id)
|
|
58
|
+
if (!user) {
|
|
59
|
+
set.status = 404
|
|
60
|
+
return { error: 'User not found' }
|
|
61
|
+
}
|
|
62
|
+
return user
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// List response
|
|
66
|
+
app.get('/users', () => {
|
|
67
|
+
return {
|
|
68
|
+
data: getUsers(),
|
|
69
|
+
total: getUserCount()
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Created response
|
|
74
|
+
app.post('/users', ({ body, set }) => {
|
|
75
|
+
const user = createUser(body)
|
|
76
|
+
set.status = 201
|
|
77
|
+
return user
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// No content response
|
|
81
|
+
app.delete('/users/:id', ({ params, set }) => {
|
|
82
|
+
deleteUser(params.id)
|
|
83
|
+
set.status = 204
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Error Response
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
// Consistent error format
|
|
91
|
+
const errorResponse = (message, code, details = null) => ({
|
|
92
|
+
error: {
|
|
93
|
+
message,
|
|
94
|
+
code,
|
|
95
|
+
details
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
app.get('/users/:id', ({ params, set }) => {
|
|
100
|
+
const user = getUserById(params.id)
|
|
101
|
+
if (!user) {
|
|
102
|
+
set.status = 404
|
|
103
|
+
return errorResponse('User not found', 'USER_NOT_FOUND')
|
|
104
|
+
}
|
|
105
|
+
return user
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Pagination
|
|
110
|
+
|
|
111
|
+
### Offset Pagination
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
import { Elysia, t } from 'elysia'
|
|
115
|
+
|
|
116
|
+
app.get('/users', ({ query }) => {
|
|
117
|
+
const { page = 1, limit = 10 } = query
|
|
118
|
+
const offset = (page - 1) * limit
|
|
119
|
+
|
|
120
|
+
const users = getUsers({ offset, limit })
|
|
121
|
+
const total = getUserCount()
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
data: users,
|
|
125
|
+
pagination: {
|
|
126
|
+
page,
|
|
127
|
+
limit,
|
|
128
|
+
total,
|
|
129
|
+
totalPages: Math.ceil(total / limit)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, {
|
|
133
|
+
query: t.Object({
|
|
134
|
+
page: t.Optional(t.Numeric({ default: 1, minimum: 1 })),
|
|
135
|
+
limit: t.Optional(t.Numeric({ default: 10, minimum: 1, maximum: 100 }))
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Cursor Pagination
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
app.get('/posts', ({ query }) => {
|
|
144
|
+
const { cursor, limit = 10 } = query
|
|
145
|
+
|
|
146
|
+
const posts = getPosts({ cursor, limit: limit + 1 })
|
|
147
|
+
const hasMore = posts.length > limit
|
|
148
|
+
const data = hasMore ? posts.slice(0, -1) : posts
|
|
149
|
+
const nextCursor = hasMore ? data[data.length - 1].id : null
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
data,
|
|
153
|
+
pagination: {
|
|
154
|
+
nextCursor,
|
|
155
|
+
hasMore
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}, {
|
|
159
|
+
query: t.Object({
|
|
160
|
+
cursor: t.Optional(t.String()),
|
|
161
|
+
limit: t.Optional(t.Numeric({ default: 10, maximum: 100 }))
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Filtering & Sorting
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
app.get('/products', ({ query }) => {
|
|
170
|
+
const { category, minPrice, maxPrice, sort = 'createdAt', order = 'desc' } = query
|
|
171
|
+
|
|
172
|
+
return getProducts({
|
|
173
|
+
filters: {
|
|
174
|
+
category,
|
|
175
|
+
price: { min: minPrice, max: maxPrice }
|
|
176
|
+
},
|
|
177
|
+
sort: { field: sort, order }
|
|
178
|
+
})
|
|
179
|
+
}, {
|
|
180
|
+
query: t.Object({
|
|
181
|
+
category: t.Optional(t.String()),
|
|
182
|
+
minPrice: t.Optional(t.Numeric()),
|
|
183
|
+
maxPrice: t.Optional(t.Numeric()),
|
|
184
|
+
sort: t.Optional(t.Union([
|
|
185
|
+
t.Literal('createdAt'),
|
|
186
|
+
t.Literal('price'),
|
|
187
|
+
t.Literal('name')
|
|
188
|
+
])),
|
|
189
|
+
order: t.Optional(t.Union([
|
|
190
|
+
t.Literal('asc'),
|
|
191
|
+
t.Literal('desc')
|
|
192
|
+
]))
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## CRUD Resource Pattern
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// routes/users.js
|
|
201
|
+
import { Elysia, t } from 'elysia'
|
|
202
|
+
import * as userService from '../services/user'
|
|
203
|
+
|
|
204
|
+
const UserSchema = t.Object({
|
|
205
|
+
name: t.String({ minLength: 1 }),
|
|
206
|
+
email: t.String({ format: 'email' })
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const UserUpdateSchema = t.Partial(UserSchema)
|
|
210
|
+
|
|
211
|
+
export const userRoutes = new Elysia({ prefix: '/users' })
|
|
212
|
+
// List
|
|
213
|
+
.get('/', ({ query }) => {
|
|
214
|
+
return userService.findAll(query)
|
|
215
|
+
}, {
|
|
216
|
+
query: t.Object({
|
|
217
|
+
page: t.Optional(t.Numeric({ default: 1 })),
|
|
218
|
+
limit: t.Optional(t.Numeric({ default: 10 }))
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// Get by ID
|
|
223
|
+
.get('/:id', ({ params, set }) => {
|
|
224
|
+
const user = userService.findById(params.id)
|
|
225
|
+
if (!user) {
|
|
226
|
+
set.status = 404
|
|
227
|
+
return { error: 'User not found' }
|
|
228
|
+
}
|
|
229
|
+
return user
|
|
230
|
+
}, {
|
|
231
|
+
params: t.Object({ id: t.Numeric() })
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// Create
|
|
235
|
+
.post('/', ({ body, set }) => {
|
|
236
|
+
const user = userService.create(body)
|
|
237
|
+
set.status = 201
|
|
238
|
+
return user
|
|
239
|
+
}, {
|
|
240
|
+
body: UserSchema
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// Update
|
|
244
|
+
.patch('/:id', ({ params, body, set }) => {
|
|
245
|
+
const user = userService.update(params.id, body)
|
|
246
|
+
if (!user) {
|
|
247
|
+
set.status = 404
|
|
248
|
+
return { error: 'User not found' }
|
|
249
|
+
}
|
|
250
|
+
return user
|
|
251
|
+
}, {
|
|
252
|
+
params: t.Object({ id: t.Numeric() }),
|
|
253
|
+
body: UserUpdateSchema
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// Delete
|
|
257
|
+
.delete('/:id', ({ params, set }) => {
|
|
258
|
+
const deleted = userService.remove(params.id)
|
|
259
|
+
if (!deleted) {
|
|
260
|
+
set.status = 404
|
|
261
|
+
return { error: 'User not found' }
|
|
262
|
+
}
|
|
263
|
+
set.status = 204
|
|
264
|
+
}, {
|
|
265
|
+
params: t.Object({ id: t.Numeric() })
|
|
266
|
+
})
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## API Versioning
|
|
270
|
+
|
|
271
|
+
### URL Prefix (Recommended)
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const v1Routes = new Elysia({ prefix: '/api/v1' })
|
|
275
|
+
.use(userRoutesV1)
|
|
276
|
+
.use(postRoutesV1)
|
|
277
|
+
|
|
278
|
+
const v2Routes = new Elysia({ prefix: '/api/v2' })
|
|
279
|
+
.use(userRoutesV2)
|
|
280
|
+
|
|
281
|
+
const app = new Elysia()
|
|
282
|
+
.use(v1Routes)
|
|
283
|
+
.use(v2Routes)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Response Headers
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// CORS
|
|
290
|
+
app.onRequest(({ set }) => {
|
|
291
|
+
set.headers['Access-Control-Allow-Origin'] = '*'
|
|
292
|
+
set.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE'
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Cache Control
|
|
296
|
+
app.get('/static-data', ({ set }) => {
|
|
297
|
+
set.headers['Cache-Control'] = 'public, max-age=3600'
|
|
298
|
+
return getStaticData()
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
// Rate Limit Headers
|
|
302
|
+
app.onAfterHandle(({ set }) => {
|
|
303
|
+
set.headers['X-RateLimit-Limit'] = '100'
|
|
304
|
+
set.headers['X-RateLimit-Remaining'] = '99'
|
|
305
|
+
})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Health Check
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
app.get('/health', () => ({
|
|
312
|
+
status: 'ok',
|
|
313
|
+
timestamp: new Date().toISOString(),
|
|
314
|
+
uptime: process.uptime()
|
|
315
|
+
}))
|
|
316
|
+
|
|
317
|
+
app.get('/health/ready', async () => {
|
|
318
|
+
const dbOk = await checkDatabase()
|
|
319
|
+
return {
|
|
320
|
+
status: dbOk ? 'ok' : 'error',
|
|
321
|
+
database: dbOk
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
```
|