@plazmodium/odin 0.3.3-beta → 0.3.5-beta
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/README.md +25 -10
- package/builtin/ODIN.md +1067 -0
- package/builtin/agent-definitions/README.md +170 -0
- package/builtin/agent-definitions/_shared-context.md +377 -0
- package/builtin/agent-definitions/architect.md +627 -0
- package/builtin/agent-definitions/builder.md +713 -0
- package/builtin/agent-definitions/discovery.md +293 -0
- package/builtin/agent-definitions/documenter.md +238 -0
- package/builtin/agent-definitions/guardian.md +1049 -0
- package/builtin/agent-definitions/integrator.md +189 -0
- package/builtin/agent-definitions/planning.md +236 -0
- package/builtin/agent-definitions/product.md +405 -0
- package/builtin/agent-definitions/release.md +205 -0
- package/builtin/agent-definitions/reviewer.md +447 -0
- package/builtin/agent-definitions/watcher.md +402 -0
- package/builtin/skills/api/graphql/SKILL.md +548 -0
- package/builtin/skills/api/grpc/SKILL.md +554 -0
- package/builtin/skills/api/rest-api/SKILL.md +469 -0
- package/builtin/skills/api/trpc/SKILL.md +503 -0
- package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
- package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
- package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
- package/builtin/skills/architecture/microservices/SKILL.md +143 -0
- package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
- package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
- package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
- package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
- package/builtin/skills/backend/python-django/SKILL.md +128 -0
- package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
- package/builtin/skills/database/mongodb/SKILL.md +132 -0
- package/builtin/skills/database/postgresql/SKILL.md +120 -0
- package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
- package/builtin/skills/database/redis/SKILL.md +140 -0
- package/builtin/skills/database/supabase/SKILL.md +416 -0
- package/builtin/skills/devops/aws/SKILL.md +382 -0
- package/builtin/skills/devops/docker/SKILL.md +359 -0
- package/builtin/skills/devops/github-actions/SKILL.md +435 -0
- package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
- package/builtin/skills/devops/terraform/SKILL.md +453 -0
- package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
- package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
- package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
- package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
- package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
- package/builtin/skills/generic-dev/SKILL.md +307 -0
- package/builtin/skills/testing/cypress/SKILL.md +372 -0
- package/builtin/skills/testing/jest/SKILL.md +176 -0
- package/builtin/skills/testing/playwright/SKILL.md +341 -0
- package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
- package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
- package/builtin/skills/testing/vitest/SKILL.md +249 -0
- package/dist/adapters/skills/filesystem.d.ts +1 -0
- package/dist/adapters/skills/filesystem.d.ts.map +1 -1
- package/dist/adapters/skills/filesystem.js +6 -18
- package/dist/adapters/skills/filesystem.js.map +1 -1
- package/dist/adapters/skills/types.d.ts +1 -0
- package/dist/adapters/skills/types.d.ts.map +1 -1
- package/dist/adapters/workflow-state/in-memory.d.ts +10 -2
- package/dist/adapters/workflow-state/in-memory.d.ts.map +1 -1
- package/dist/adapters/workflow-state/in-memory.js +98 -5
- package/dist/adapters/workflow-state/in-memory.js.map +1 -1
- package/dist/adapters/workflow-state/supabase.d.ts +8 -2
- package/dist/adapters/workflow-state/supabase.d.ts.map +1 -1
- package/dist/adapters/workflow-state/supabase.js +204 -0
- package/dist/adapters/workflow-state/supabase.js.map +1 -1
- package/dist/adapters/workflow-state/types.d.ts +15 -1
- package/dist/adapters/workflow-state/types.d.ts.map +1 -1
- package/dist/builtin-assets.d.ts +8 -0
- package/dist/builtin-assets.d.ts.map +1 -0
- package/dist/builtin-assets.js +90 -0
- package/dist/builtin-assets.js.map +1 -0
- package/dist/domain/skill-draft-validation.d.ts +18 -0
- package/dist/domain/skill-draft-validation.d.ts.map +1 -0
- package/dist/domain/skill-draft-validation.js +100 -0
- package/dist/domain/skill-draft-validation.js.map +1 -0
- package/dist/domain/skill-proposals.d.ts +11 -0
- package/dist/domain/skill-proposals.d.ts.map +1 -0
- package/dist/domain/skill-proposals.js +103 -0
- package/dist/domain/skill-proposals.js.map +1 -0
- package/dist/init.js +69 -11
- package/dist/init.js.map +1 -1
- package/dist/schemas.d.ts +39 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +30 -1
- package/dist/schemas.js.map +1 -1
- package/dist/server.js +38 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/apply-migrations.d.ts +10 -0
- package/dist/tools/apply-migrations.d.ts.map +1 -1
- package/dist/tools/apply-migrations.js +10 -26
- package/dist/tools/apply-migrations.js.map +1 -1
- package/dist/tools/capture-learning.d.ts.map +1 -1
- package/dist/tools/capture-learning.js +14 -1
- package/dist/tools/capture-learning.js.map +1 -1
- package/dist/tools/get-skill-proposal-queue.d.ts +5 -0
- package/dist/tools/get-skill-proposal-queue.d.ts.map +1 -0
- package/dist/tools/get-skill-proposal-queue.js +21 -0
- package/dist/tools/get-skill-proposal-queue.js.map +1 -0
- package/dist/tools/get-skill-proposals.d.ts +4 -0
- package/dist/tools/get-skill-proposals.d.ts.map +1 -0
- package/dist/tools/get-skill-proposals.js +11 -0
- package/dist/tools/get-skill-proposals.js.map +1 -0
- package/dist/tools/prepare-phase-context.d.ts.map +1 -1
- package/dist/tools/prepare-phase-context.js +5 -0
- package/dist/tools/prepare-phase-context.js.map +1 -1
- package/dist/tools/publish-skill-proposal.d.ts +5 -0
- package/dist/tools/publish-skill-proposal.d.ts.map +1 -0
- package/dist/tools/publish-skill-proposal.js +57 -0
- package/dist/tools/publish-skill-proposal.js.map +1 -0
- package/dist/tools/record-skill-proposal-decision.d.ts +4 -0
- package/dist/tools/record-skill-proposal-decision.d.ts.map +1 -0
- package/dist/tools/record-skill-proposal-decision.js +22 -0
- package/dist/tools/record-skill-proposal-decision.js.map +1 -0
- package/dist/tools/record-skill-proposal-draft.d.ts +5 -0
- package/dist/tools/record-skill-proposal-draft.d.ts.map +1 -0
- package/dist/tools/record-skill-proposal-draft.js +65 -0
- package/dist/tools/record-skill-proposal-draft.js.map +1 -0
- package/dist/tools/sync-skill-proposal-candidates.d.ts +5 -0
- package/dist/tools/sync-skill-proposal-candidates.d.ts.map +1 -0
- package/dist/tools/sync-skill-proposal-candidates.js +20 -0
- package/dist/tools/sync-skill-proposal-candidates.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/migrations/009_skill_proposal_candidates.sql +124 -0
- package/migrations/010_skill_proposals.sql +36 -0
- package/migrations/README.md +6 -0
- package/package.json +5 -3
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rest-api
|
|
3
|
+
description: REST API design and implementation expertise. Covers HTTP methods, status codes, resource naming, versioning, authentication, and OpenAPI documentation.
|
|
4
|
+
category: api
|
|
5
|
+
compatible_with:
|
|
6
|
+
- nodejs-express
|
|
7
|
+
- nodejs-fastify
|
|
8
|
+
- python-fastapi
|
|
9
|
+
- golang-gin
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# REST API Design
|
|
13
|
+
|
|
14
|
+
## Instructions
|
|
15
|
+
|
|
16
|
+
1. **Assess the API need**: CRUD operations, complex queries, or real-time data.
|
|
17
|
+
2. **Follow REST conventions**:
|
|
18
|
+
- Use nouns for resources, not verbs
|
|
19
|
+
- Proper HTTP methods and status codes
|
|
20
|
+
- Consistent naming conventions
|
|
21
|
+
- HATEOAS where appropriate
|
|
22
|
+
3. **Provide complete examples**: Include routes, handlers, and response schemas.
|
|
23
|
+
4. **Guide on best practices**: Versioning, pagination, filtering, error handling.
|
|
24
|
+
|
|
25
|
+
## HTTP Methods
|
|
26
|
+
|
|
27
|
+
| Method | Purpose | Idempotent | Safe |
|
|
28
|
+
|--------|---------|------------|------|
|
|
29
|
+
| GET | Retrieve resource(s) | Yes | Yes |
|
|
30
|
+
| POST | Create resource | No | No |
|
|
31
|
+
| PUT | Replace resource | Yes | No |
|
|
32
|
+
| PATCH | Partial update | No | No |
|
|
33
|
+
| DELETE | Remove resource | Yes | No |
|
|
34
|
+
|
|
35
|
+
## Resource Naming
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
# Good - Nouns, plural
|
|
39
|
+
GET /users
|
|
40
|
+
GET /users/:id
|
|
41
|
+
POST /users
|
|
42
|
+
PUT /users/:id
|
|
43
|
+
PATCH /users/:id
|
|
44
|
+
DELETE /users/:id
|
|
45
|
+
|
|
46
|
+
# Nested resources
|
|
47
|
+
GET /users/:userId/posts
|
|
48
|
+
GET /users/:userId/posts/:postId
|
|
49
|
+
POST /users/:userId/posts
|
|
50
|
+
|
|
51
|
+
# Bad - Verbs, actions in URL
|
|
52
|
+
GET /getUsers
|
|
53
|
+
POST /createUser
|
|
54
|
+
GET /getUserById/:id
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Status Codes
|
|
58
|
+
|
|
59
|
+
### Success (2xx)
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// 200 OK - Successful GET, PUT, PATCH
|
|
63
|
+
res.status(200).json({ data: user });
|
|
64
|
+
|
|
65
|
+
// 201 Created - Successful POST
|
|
66
|
+
res.status(201).json({ data: newUser });
|
|
67
|
+
|
|
68
|
+
// 204 No Content - Successful DELETE
|
|
69
|
+
res.status(204).send();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Client Errors (4xx)
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// 400 Bad Request - Invalid input
|
|
76
|
+
res.status(400).json({
|
|
77
|
+
error: {
|
|
78
|
+
code: 'VALIDATION_ERROR',
|
|
79
|
+
message: 'Invalid request body',
|
|
80
|
+
details: [
|
|
81
|
+
{ field: 'email', message: 'Must be a valid email' }
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 401 Unauthorized - Not authenticated
|
|
87
|
+
res.status(401).json({
|
|
88
|
+
error: {
|
|
89
|
+
code: 'UNAUTHORIZED',
|
|
90
|
+
message: 'Authentication required'
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 403 Forbidden - Authenticated but not authorized
|
|
95
|
+
res.status(403).json({
|
|
96
|
+
error: {
|
|
97
|
+
code: 'FORBIDDEN',
|
|
98
|
+
message: 'You do not have permission to access this resource'
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 404 Not Found
|
|
103
|
+
res.status(404).json({
|
|
104
|
+
error: {
|
|
105
|
+
code: 'NOT_FOUND',
|
|
106
|
+
message: 'User not found'
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 409 Conflict - Resource already exists
|
|
111
|
+
res.status(409).json({
|
|
112
|
+
error: {
|
|
113
|
+
code: 'CONFLICT',
|
|
114
|
+
message: 'Email already registered'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// 422 Unprocessable Entity - Semantic errors
|
|
119
|
+
res.status(422).json({
|
|
120
|
+
error: {
|
|
121
|
+
code: 'UNPROCESSABLE_ENTITY',
|
|
122
|
+
message: 'Cannot delete user with active subscriptions'
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// 429 Too Many Requests - Rate limiting
|
|
127
|
+
res.status(429).json({
|
|
128
|
+
error: {
|
|
129
|
+
code: 'RATE_LIMITED',
|
|
130
|
+
message: 'Too many requests',
|
|
131
|
+
retryAfter: 60
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Server Errors (5xx)
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
// 500 Internal Server Error
|
|
140
|
+
res.status(500).json({
|
|
141
|
+
error: {
|
|
142
|
+
code: 'INTERNAL_ERROR',
|
|
143
|
+
message: 'An unexpected error occurred'
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// 503 Service Unavailable
|
|
148
|
+
res.status(503).json({
|
|
149
|
+
error: {
|
|
150
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
151
|
+
message: 'Service temporarily unavailable',
|
|
152
|
+
retryAfter: 300
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Response Structure
|
|
158
|
+
|
|
159
|
+
### Single Resource
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"data": {
|
|
164
|
+
"id": "123",
|
|
165
|
+
"type": "user",
|
|
166
|
+
"attributes": {
|
|
167
|
+
"email": "user@example.com",
|
|
168
|
+
"name": "John Doe",
|
|
169
|
+
"createdAt": "2024-01-15T10:30:00Z"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Collection
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"data": [
|
|
180
|
+
{ "id": "1", "name": "Item 1" },
|
|
181
|
+
{ "id": "2", "name": "Item 2" }
|
|
182
|
+
],
|
|
183
|
+
"meta": {
|
|
184
|
+
"total": 100,
|
|
185
|
+
"page": 1,
|
|
186
|
+
"perPage": 20,
|
|
187
|
+
"totalPages": 5
|
|
188
|
+
},
|
|
189
|
+
"links": {
|
|
190
|
+
"self": "/items?page=1",
|
|
191
|
+
"first": "/items?page=1",
|
|
192
|
+
"prev": null,
|
|
193
|
+
"next": "/items?page=2",
|
|
194
|
+
"last": "/items?page=5"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Pagination
|
|
200
|
+
|
|
201
|
+
### Offset-based
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
GET /users?page=2&limit=20
|
|
205
|
+
GET /users?offset=20&limit=20
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Cursor-based (recommended for large datasets)
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
|
|
212
|
+
|
|
213
|
+
Response:
|
|
214
|
+
{
|
|
215
|
+
"data": [...],
|
|
216
|
+
"meta": {
|
|
217
|
+
"hasMore": true,
|
|
218
|
+
"nextCursor": "eyJpZCI6MTIwfQ"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Filtering & Sorting
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
# Filtering
|
|
227
|
+
GET /users?status=active
|
|
228
|
+
GET /users?role=admin&status=active
|
|
229
|
+
GET /users?createdAt[gte]=2024-01-01
|
|
230
|
+
GET /users?search=john
|
|
231
|
+
|
|
232
|
+
# Sorting
|
|
233
|
+
GET /users?sort=createdAt
|
|
234
|
+
GET /users?sort=-createdAt # Descending
|
|
235
|
+
GET /users?sort=lastName,firstName # Multiple fields
|
|
236
|
+
|
|
237
|
+
# Field selection
|
|
238
|
+
GET /users?fields=id,email,name
|
|
239
|
+
GET /users?include=posts,comments # Relations
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Versioning
|
|
243
|
+
|
|
244
|
+
### URL Path (recommended)
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
GET /api/v1/users
|
|
248
|
+
GET /api/v2/users
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Header-based
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
GET /api/users
|
|
255
|
+
Accept: application/vnd.myapi.v2+json
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Query Parameter
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
GET /api/users?version=2
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Authentication
|
|
265
|
+
|
|
266
|
+
### Bearer Token (JWT)
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
// Request
|
|
270
|
+
GET /api/users
|
|
271
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
|
|
272
|
+
|
|
273
|
+
// Middleware
|
|
274
|
+
const authenticate = (req, res, next) => {
|
|
275
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
276
|
+
if (!token) {
|
|
277
|
+
return res.status(401).json({ error: { message: 'Token required' } });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
282
|
+
req.user = decoded;
|
|
283
|
+
next();
|
|
284
|
+
} catch (err) {
|
|
285
|
+
return res.status(401).json({ error: { message: 'Invalid token' } });
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### API Key
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
// Request
|
|
294
|
+
GET /api/users
|
|
295
|
+
X-API-Key: sk_live_abc123
|
|
296
|
+
|
|
297
|
+
// Middleware
|
|
298
|
+
const apiKeyAuth = async (req, res, next) => {
|
|
299
|
+
const apiKey = req.headers['x-api-key'];
|
|
300
|
+
if (!apiKey) {
|
|
301
|
+
return res.status(401).json({ error: { message: 'API key required' } });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const client = await db.apiKeys.findOne({ key: apiKey, active: true });
|
|
305
|
+
if (!client) {
|
|
306
|
+
return res.status(401).json({ error: { message: 'Invalid API key' } });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
req.client = client;
|
|
310
|
+
next();
|
|
311
|
+
};
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Rate Limiting
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
import rateLimit from 'express-rate-limit';
|
|
318
|
+
|
|
319
|
+
const limiter = rateLimit({
|
|
320
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
321
|
+
max: 100,
|
|
322
|
+
standardHeaders: true,
|
|
323
|
+
legacyHeaders: false,
|
|
324
|
+
handler: (req, res) => {
|
|
325
|
+
res.status(429).json({
|
|
326
|
+
error: {
|
|
327
|
+
code: 'RATE_LIMITED',
|
|
328
|
+
message: 'Too many requests',
|
|
329
|
+
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000)
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
app.use('/api/', limiter);
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## OpenAPI/Swagger
|
|
339
|
+
|
|
340
|
+
```yaml
|
|
341
|
+
openapi: 3.0.3
|
|
342
|
+
info:
|
|
343
|
+
title: My API
|
|
344
|
+
version: 1.0.0
|
|
345
|
+
|
|
346
|
+
paths:
|
|
347
|
+
/users:
|
|
348
|
+
get:
|
|
349
|
+
summary: List users
|
|
350
|
+
parameters:
|
|
351
|
+
- name: page
|
|
352
|
+
in: query
|
|
353
|
+
schema:
|
|
354
|
+
type: integer
|
|
355
|
+
default: 1
|
|
356
|
+
- name: limit
|
|
357
|
+
in: query
|
|
358
|
+
schema:
|
|
359
|
+
type: integer
|
|
360
|
+
default: 20
|
|
361
|
+
responses:
|
|
362
|
+
'200':
|
|
363
|
+
description: Successful response
|
|
364
|
+
content:
|
|
365
|
+
application/json:
|
|
366
|
+
schema:
|
|
367
|
+
type: object
|
|
368
|
+
properties:
|
|
369
|
+
data:
|
|
370
|
+
type: array
|
|
371
|
+
items:
|
|
372
|
+
$ref: '#/components/schemas/User'
|
|
373
|
+
post:
|
|
374
|
+
summary: Create user
|
|
375
|
+
requestBody:
|
|
376
|
+
required: true
|
|
377
|
+
content:
|
|
378
|
+
application/json:
|
|
379
|
+
schema:
|
|
380
|
+
$ref: '#/components/schemas/CreateUserInput'
|
|
381
|
+
responses:
|
|
382
|
+
'201':
|
|
383
|
+
description: User created
|
|
384
|
+
|
|
385
|
+
components:
|
|
386
|
+
schemas:
|
|
387
|
+
User:
|
|
388
|
+
type: object
|
|
389
|
+
properties:
|
|
390
|
+
id:
|
|
391
|
+
type: string
|
|
392
|
+
email:
|
|
393
|
+
type: string
|
|
394
|
+
format: email
|
|
395
|
+
name:
|
|
396
|
+
type: string
|
|
397
|
+
createdAt:
|
|
398
|
+
type: string
|
|
399
|
+
format: date-time
|
|
400
|
+
CreateUserInput:
|
|
401
|
+
type: object
|
|
402
|
+
required:
|
|
403
|
+
- email
|
|
404
|
+
- name
|
|
405
|
+
properties:
|
|
406
|
+
email:
|
|
407
|
+
type: string
|
|
408
|
+
format: email
|
|
409
|
+
name:
|
|
410
|
+
type: string
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Best Practices
|
|
414
|
+
|
|
415
|
+
- **Use HTTPS** - Always encrypt in transit
|
|
416
|
+
- **Validate input** - Never trust client data
|
|
417
|
+
- **Return consistent responses** - Same structure for success/error
|
|
418
|
+
- **Use proper status codes** - Don't use 200 for everything
|
|
419
|
+
- **Version your API** - Plan for breaking changes
|
|
420
|
+
- **Document thoroughly** - OpenAPI/Swagger
|
|
421
|
+
- **Implement rate limiting** - Protect against abuse
|
|
422
|
+
- **Log requests** - For debugging and auditing
|
|
423
|
+
- **Use ETags** - For caching and conditional requests
|
|
424
|
+
- **CORS** - Configure properly for web clients
|
|
425
|
+
|
|
426
|
+
## Error Handling Pattern
|
|
427
|
+
|
|
428
|
+
```javascript
|
|
429
|
+
class APIError extends Error {
|
|
430
|
+
constructor(code, message, status = 400, details = null) {
|
|
431
|
+
super(message);
|
|
432
|
+
this.code = code;
|
|
433
|
+
this.status = status;
|
|
434
|
+
this.details = details;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Usage
|
|
439
|
+
throw new APIError('VALIDATION_ERROR', 'Invalid email', 400, [
|
|
440
|
+
{ field: 'email', message: 'Must be valid email format' }
|
|
441
|
+
]);
|
|
442
|
+
|
|
443
|
+
// Global error handler
|
|
444
|
+
app.use((err, req, res, next) => {
|
|
445
|
+
if (err instanceof APIError) {
|
|
446
|
+
return res.status(err.status).json({
|
|
447
|
+
error: {
|
|
448
|
+
code: err.code,
|
|
449
|
+
message: err.message,
|
|
450
|
+
details: err.details
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
console.error(err);
|
|
456
|
+
res.status(500).json({
|
|
457
|
+
error: {
|
|
458
|
+
code: 'INTERNAL_ERROR',
|
|
459
|
+
message: 'An unexpected error occurred'
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## References
|
|
466
|
+
|
|
467
|
+
- REST API Design: https://restfulapi.net/
|
|
468
|
+
- HTTP Status Codes: https://httpstatuses.com/
|
|
469
|
+
- OpenAPI Specification: https://swagger.io/specification/
|