@intentsolutionsio/fullstack-starter-pack 1.0.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/.claude-plugin/plugin.json +31 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/agents/api-builder.md +610 -0
- package/agents/backend-architect.md +574 -0
- package/agents/database-designer.md +509 -0
- package/agents/deployment-specialist.md +603 -0
- package/agents/react-specialist.md +668 -0
- package/agents/ui-ux-expert.md +652 -0
- package/commands/auth-setup.md +422 -0
- package/commands/component-generator.md +343 -0
- package/commands/css-utility-generator.md +621 -0
- package/commands/env-config-setup.md +338 -0
- package/commands/express-api-scaffold.md +659 -0
- package/commands/fastapi-scaffold.md +674 -0
- package/commands/prisma-schema-gen.md +582 -0
- package/commands/project-scaffold.md +355 -0
- package/commands/sql-query-builder.md +461 -0
- package/package.json +52 -0
- package/skills/skill-adapter/assets/README.md +8 -0
- package/skills/skill-adapter/assets/config-template.json +32 -0
- package/skills/skill-adapter/assets/example_env_config.txt +100 -0
- package/skills/skill-adapter/assets/skill-schema.json +28 -0
- package/skills/skill-adapter/assets/test-data.json +27 -0
- package/skills/skill-adapter/references/README.md +4 -0
- package/skills/skill-adapter/references/best-practices.md +69 -0
- package/skills/skill-adapter/references/examples.md +73 -0
- package/skills/skill-adapter/scripts/README.md +7 -0
- package/skills/skill-adapter/scripts/helper-template.sh +42 -0
- package/skills/skill-adapter/scripts/validation.sh +32 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: express-api-scaffold
|
|
3
|
+
description: >
|
|
4
|
+
Generate production-ready Express.js REST API with TypeScript and auth
|
|
5
|
+
shortcut: eas
|
|
6
|
+
category: backend
|
|
7
|
+
difficulty: intermediate
|
|
8
|
+
estimated_time: 5-10 minutes
|
|
9
|
+
---
|
|
10
|
+
# Express API Scaffold
|
|
11
|
+
|
|
12
|
+
Generates a complete Express.js REST API boilerplate with TypeScript, authentication, database integration, and testing setup.
|
|
13
|
+
|
|
14
|
+
## What This Command Does
|
|
15
|
+
|
|
16
|
+
**Generated Project:**
|
|
17
|
+
- Express.js with TypeScript
|
|
18
|
+
- JWT authentication
|
|
19
|
+
- Database integration (Prisma or TypeORM)
|
|
20
|
+
- Input validation (Zod)
|
|
21
|
+
- Error handling middleware
|
|
22
|
+
- Rate limiting & security (Helmet, CORS)
|
|
23
|
+
- Testing setup (Jest + Supertest)
|
|
24
|
+
- Docker configuration
|
|
25
|
+
- Example CRUD endpoints
|
|
26
|
+
|
|
27
|
+
**Output:** Complete API project ready for development
|
|
28
|
+
|
|
29
|
+
**Time:** 5-10 minutes
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Generate full Express API
|
|
37
|
+
/express-api-scaffold "Task Management API"
|
|
38
|
+
|
|
39
|
+
# Shortcut
|
|
40
|
+
/eas "E-commerce API"
|
|
41
|
+
|
|
42
|
+
# With specific database
|
|
43
|
+
/eas "Blog API" --database postgresql
|
|
44
|
+
|
|
45
|
+
# With authentication type
|
|
46
|
+
/eas "Social API" --auth jwt --database mongodb
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Example Output
|
|
52
|
+
|
|
53
|
+
**Input:**
|
|
54
|
+
```
|
|
55
|
+
/eas "Task Management API" --database postgresql
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Generated Project Structure:**
|
|
59
|
+
```
|
|
60
|
+
task-api/
|
|
61
|
+
├── src/
|
|
62
|
+
│ ├── controllers/ # Request handlers
|
|
63
|
+
│ │ ├── auth.controller.ts
|
|
64
|
+
│ │ └── task.controller.ts
|
|
65
|
+
│ ├── middleware/ # Express middleware
|
|
66
|
+
│ │ ├── auth.middleware.ts
|
|
67
|
+
│ │ ├── error.middleware.ts
|
|
68
|
+
│ │ └── validation.middleware.ts
|
|
69
|
+
│ ├── models/ # Database models
|
|
70
|
+
│ │ └── task.model.ts
|
|
71
|
+
│ ├── routes/ # API routes
|
|
72
|
+
│ │ ├── auth.routes.ts
|
|
73
|
+
│ │ └── task.routes.ts
|
|
74
|
+
│ ├── services/ # Business logic
|
|
75
|
+
│ │ ├── auth.service.ts
|
|
76
|
+
│ │ └── task.service.ts
|
|
77
|
+
│ ├── utils/ # Utilities
|
|
78
|
+
│ │ ├── jwt.util.ts
|
|
79
|
+
│ │ └── password.util.ts
|
|
80
|
+
│ ├── config/ # Configuration
|
|
81
|
+
│ │ └── database.ts
|
|
82
|
+
│ ├── types/ # TypeScript types
|
|
83
|
+
│ │ └── express.d.ts
|
|
84
|
+
│ ├── app.ts # Express app setup
|
|
85
|
+
│ └── server.ts # Server entry point
|
|
86
|
+
├── tests/
|
|
87
|
+
│ ├── auth.test.ts
|
|
88
|
+
│ └── task.test.ts
|
|
89
|
+
├── prisma/
|
|
90
|
+
│ └── schema.prisma # Database schema
|
|
91
|
+
├── .env.example
|
|
92
|
+
├── .gitignore
|
|
93
|
+
├── package.json
|
|
94
|
+
├── tsconfig.json
|
|
95
|
+
├── jest.config.js
|
|
96
|
+
├── Dockerfile
|
|
97
|
+
├── docker-compose.yml
|
|
98
|
+
└── README.md
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Generated Files
|
|
104
|
+
|
|
105
|
+
### 1. **src/server.ts** (Entry Point)
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import app from './app'
|
|
109
|
+
import { config } from './config'
|
|
110
|
+
|
|
111
|
+
const PORT = process.env.PORT || 3000
|
|
112
|
+
|
|
113
|
+
app.listen(PORT, () => {
|
|
114
|
+
console.log(`Server running on port ${PORT}`)
|
|
115
|
+
console.log(`Environment: ${process.env.NODE_ENV}`)
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. **src/app.ts** (Express Setup)
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import express, { Application } from 'express'
|
|
123
|
+
import cors from 'cors'
|
|
124
|
+
import helmet from 'helmet'
|
|
125
|
+
import morgan from 'morgan'
|
|
126
|
+
import rateLimit from 'express-rate-limit'
|
|
127
|
+
|
|
128
|
+
import authRoutes from './routes/auth.routes'
|
|
129
|
+
import taskRoutes from './routes/task.routes'
|
|
130
|
+
import { errorHandler } from './middleware/error.middleware'
|
|
131
|
+
import { notFoundHandler } from './middleware/notFound.middleware'
|
|
132
|
+
|
|
133
|
+
const app: Application = express()
|
|
134
|
+
|
|
135
|
+
// Security middleware
|
|
136
|
+
app.use(helmet())
|
|
137
|
+
app.use(cors({
|
|
138
|
+
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
|
|
139
|
+
credentials: true
|
|
140
|
+
}))
|
|
141
|
+
|
|
142
|
+
// Rate limiting
|
|
143
|
+
const limiter = rateLimit({
|
|
144
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
145
|
+
max: 100, // 100 requests per window
|
|
146
|
+
message: 'Too many requests, please try again later'
|
|
147
|
+
})
|
|
148
|
+
app.use('/api/', limiter)
|
|
149
|
+
|
|
150
|
+
// Parsing middleware
|
|
151
|
+
app.use(express.json())
|
|
152
|
+
app.use(express.urlencoded({ extended: true }))
|
|
153
|
+
|
|
154
|
+
// Logging
|
|
155
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
156
|
+
app.use(morgan('combined'))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Health check
|
|
160
|
+
app.get('/health', (req, res) => {
|
|
161
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() })
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Routes
|
|
165
|
+
app.use('/api/auth', authRoutes)
|
|
166
|
+
app.use('/api/tasks', taskRoutes)
|
|
167
|
+
|
|
168
|
+
// Error handling
|
|
169
|
+
app.use(notFoundHandler)
|
|
170
|
+
app.use(errorHandler)
|
|
171
|
+
|
|
172
|
+
export default app
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 3. **src/controllers/auth.controller.ts**
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { Request, Response, NextFunction } from 'express'
|
|
179
|
+
import { AuthService } from '../services/auth.service'
|
|
180
|
+
import { ApiError } from '../utils/ApiError'
|
|
181
|
+
|
|
182
|
+
const authService = new AuthService()
|
|
183
|
+
|
|
184
|
+
export class AuthController {
|
|
185
|
+
async register(req: Request, res: Response, next: NextFunction) {
|
|
186
|
+
try {
|
|
187
|
+
const { email, password, name } = req.body
|
|
188
|
+
|
|
189
|
+
const result = await authService.register({ email, password, name })
|
|
190
|
+
|
|
191
|
+
res.status(201).json({
|
|
192
|
+
data: {
|
|
193
|
+
user: result.user,
|
|
194
|
+
token: result.token
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
} catch (error) {
|
|
198
|
+
next(error)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async login(req: Request, res: Response, next: NextFunction) {
|
|
203
|
+
try {
|
|
204
|
+
const { email, password } = req.body
|
|
205
|
+
|
|
206
|
+
const result = await authService.login(email, password)
|
|
207
|
+
|
|
208
|
+
res.json({
|
|
209
|
+
data: {
|
|
210
|
+
user: result.user,
|
|
211
|
+
token: result.token
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
} catch (error) {
|
|
215
|
+
next(error)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async getProfile(req: Request, res: Response, next: NextFunction) {
|
|
220
|
+
try {
|
|
221
|
+
const userId = req.user!.id
|
|
222
|
+
|
|
223
|
+
const user = await authService.getUserById(userId)
|
|
224
|
+
|
|
225
|
+
res.json({ data: user })
|
|
226
|
+
} catch (error) {
|
|
227
|
+
next(error)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 4. **src/middleware/auth.middleware.ts**
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { Request, Response, NextFunction } from 'express'
|
|
237
|
+
import jwt from 'jsonwebtoken'
|
|
238
|
+
import { ApiError } from '../utils/ApiError'
|
|
239
|
+
|
|
240
|
+
interface JwtPayload {
|
|
241
|
+
userId: string
|
|
242
|
+
email: string
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
declare global {
|
|
246
|
+
namespace Express {
|
|
247
|
+
interface Request {
|
|
248
|
+
user?: {
|
|
249
|
+
id: string
|
|
250
|
+
email: string
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function authenticate(req: Request, res: Response, next: NextFunction) {
|
|
257
|
+
try {
|
|
258
|
+
const authHeader = req.headers.authorization
|
|
259
|
+
|
|
260
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
261
|
+
throw new ApiError(401, 'No token provided')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const token = authHeader.split(' ')[1]
|
|
265
|
+
|
|
266
|
+
const decoded = jwt.verify(
|
|
267
|
+
token,
|
|
268
|
+
process.env.JWT_SECRET!
|
|
269
|
+
) as JwtPayload
|
|
270
|
+
|
|
271
|
+
req.user = {
|
|
272
|
+
id: decoded.userId,
|
|
273
|
+
email: decoded.email
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
next()
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (error instanceof jwt.JsonWebTokenError) {
|
|
279
|
+
next(new ApiError(401, 'Invalid token'))
|
|
280
|
+
} else {
|
|
281
|
+
next(error)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 5. **src/middleware/error.middleware.ts**
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { Request, Response, NextFunction } from 'express'
|
|
291
|
+
import { ApiError } from '../utils/ApiError'
|
|
292
|
+
import { ZodError } from 'zod'
|
|
293
|
+
|
|
294
|
+
export function errorHandler(
|
|
295
|
+
err: Error,
|
|
296
|
+
req: Request,
|
|
297
|
+
res: Response,
|
|
298
|
+
next: NextFunction
|
|
299
|
+
) {
|
|
300
|
+
console.error('Error:', err)
|
|
301
|
+
|
|
302
|
+
// Handle known API errors
|
|
303
|
+
if (err instanceof ApiError) {
|
|
304
|
+
return res.status(err.statusCode).json({
|
|
305
|
+
error: {
|
|
306
|
+
code: err.name,
|
|
307
|
+
message: err.message,
|
|
308
|
+
...(err.details && { details: err.details })
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Handle validation errors (Zod)
|
|
314
|
+
if (err instanceof ZodError) {
|
|
315
|
+
return res.status(400).json({
|
|
316
|
+
error: {
|
|
317
|
+
code: 'VALIDATION_ERROR',
|
|
318
|
+
message: 'Validation failed',
|
|
319
|
+
details: err.errors.map(e => ({
|
|
320
|
+
field: e.path.join('.'),
|
|
321
|
+
message: e.message
|
|
322
|
+
}))
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Handle unexpected errors
|
|
328
|
+
res.status(500).json({
|
|
329
|
+
error: {
|
|
330
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
331
|
+
message: process.env.NODE_ENV === 'production'
|
|
332
|
+
? 'An unexpected error occurred'
|
|
333
|
+
: err.message
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### 6. **src/routes/task.routes.ts**
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { Router } from 'express'
|
|
343
|
+
import { TaskController } from '../controllers/task.controller'
|
|
344
|
+
import { authenticate } from '../middleware/auth.middleware'
|
|
345
|
+
import { validate } from '../middleware/validation.middleware'
|
|
346
|
+
import { createTaskSchema, updateTaskSchema } from '../schemas/task.schema'
|
|
347
|
+
|
|
348
|
+
const router = Router()
|
|
349
|
+
const taskController = new TaskController()
|
|
350
|
+
|
|
351
|
+
// All routes require authentication
|
|
352
|
+
router.use(authenticate)
|
|
353
|
+
|
|
354
|
+
router.get('/', taskController.list)
|
|
355
|
+
router.post('/', validate(createTaskSchema), taskController.create)
|
|
356
|
+
router.get('/:id', taskController.getById)
|
|
357
|
+
router.patch('/:id', validate(updateTaskSchema), taskController.update)
|
|
358
|
+
router.delete('/:id', taskController.delete)
|
|
359
|
+
|
|
360
|
+
export default router
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### 7. **src/services/task.service.ts**
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { PrismaClient } from '@prisma/client'
|
|
367
|
+
import { ApiError } from '../utils/ApiError'
|
|
368
|
+
|
|
369
|
+
const prisma = new PrismaClient()
|
|
370
|
+
|
|
371
|
+
export class TaskService {
|
|
372
|
+
async create(userId: string, data: { title: string; description?: string }) {
|
|
373
|
+
return await prisma.task.create({
|
|
374
|
+
data: {
|
|
375
|
+
...data,
|
|
376
|
+
userId
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async findAll(userId: string) {
|
|
382
|
+
return await prisma.task.findMany({
|
|
383
|
+
where: { userId },
|
|
384
|
+
orderBy: { createdAt: 'desc' }
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async findById(id: string, userId: string) {
|
|
389
|
+
const task = await prisma.task.findUnique({
|
|
390
|
+
where: { id }
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
if (!task) {
|
|
394
|
+
throw new ApiError(404, 'Task not found')
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (task.userId !== userId) {
|
|
398
|
+
throw new ApiError(403, 'Access denied')
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return task
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async update(id: string, userId: string, data: Partial<{ title: string; description: string; completed: boolean }>) {
|
|
405
|
+
await this.findById(id, userId) // Check ownership
|
|
406
|
+
|
|
407
|
+
return await prisma.task.update({
|
|
408
|
+
where: { id },
|
|
409
|
+
data
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async delete(id: string, userId: string) {
|
|
414
|
+
await this.findById(id, userId) // Check ownership
|
|
415
|
+
|
|
416
|
+
await prisma.task.delete({
|
|
417
|
+
where: { id }
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### 8. **prisma/schema.prisma** (Database Schema)
|
|
424
|
+
|
|
425
|
+
```prisma
|
|
426
|
+
generator client {
|
|
427
|
+
provider = "prisma-client-js"
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
datasource db {
|
|
431
|
+
provider = "postgresql"
|
|
432
|
+
url = env("DATABASE_URL")
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
model User {
|
|
436
|
+
id String @id @default(uuid())
|
|
437
|
+
email String @unique
|
|
438
|
+
password String
|
|
439
|
+
name String
|
|
440
|
+
tasks Task[]
|
|
441
|
+
createdAt DateTime @default(now())
|
|
442
|
+
updatedAt DateTime @updatedAt
|
|
443
|
+
|
|
444
|
+
@@map("users")
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
model Task {
|
|
448
|
+
id String @id @default(uuid())
|
|
449
|
+
title String
|
|
450
|
+
description String?
|
|
451
|
+
completed Boolean @default(false)
|
|
452
|
+
userId String
|
|
453
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
454
|
+
createdAt DateTime @default(now())
|
|
455
|
+
updatedAt DateTime @updatedAt
|
|
456
|
+
|
|
457
|
+
@@index([userId])
|
|
458
|
+
@@map("tasks")
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### 9. **tests/task.test.ts** (Integration Tests)
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
import request from 'supertest'
|
|
466
|
+
import app from '../src/app'
|
|
467
|
+
import { PrismaClient } from '@prisma/client'
|
|
468
|
+
|
|
469
|
+
const prisma = new PrismaClient()
|
|
470
|
+
|
|
471
|
+
describe('Task API', () => {
|
|
472
|
+
let authToken: string
|
|
473
|
+
let userId: string
|
|
474
|
+
|
|
475
|
+
beforeAll(async () => {
|
|
476
|
+
// Create test user and get token
|
|
477
|
+
const res = await request(app)
|
|
478
|
+
.post('/api/auth/register')
|
|
479
|
+
.send({
|
|
480
|
+
email: '[email protected]',
|
|
481
|
+
password: 'password123',
|
|
482
|
+
name: 'Test User'
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
authToken = res.body.data.token
|
|
486
|
+
userId = res.body.data.user.id
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
afterAll(async () => {
|
|
490
|
+
// Cleanup
|
|
491
|
+
await prisma.task.deleteMany({ where: { userId } })
|
|
492
|
+
await prisma.user.delete({ where: { id: userId } })
|
|
493
|
+
await prisma.$disconnect()
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
describe('POST /api/tasks', () => {
|
|
497
|
+
it('should create a new task', async () => {
|
|
498
|
+
const res = await request(app)
|
|
499
|
+
.post('/api/tasks')
|
|
500
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
501
|
+
.send({
|
|
502
|
+
title: 'Test Task',
|
|
503
|
+
description: 'Test description'
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
expect(res.status).toBe(201)
|
|
507
|
+
expect(res.body.data).toHaveProperty('id')
|
|
508
|
+
expect(res.body.data.title).toBe('Test Task')
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
it('should require authentication', async () => {
|
|
512
|
+
const res = await request(app)
|
|
513
|
+
.post('/api/tasks')
|
|
514
|
+
.send({ title: 'Test' })
|
|
515
|
+
|
|
516
|
+
expect(res.status).toBe(401)
|
|
517
|
+
})
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
describe('GET /api/tasks', () => {
|
|
521
|
+
it('should list user tasks', async () => {
|
|
522
|
+
const res = await request(app)
|
|
523
|
+
.get('/api/tasks')
|
|
524
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
525
|
+
|
|
526
|
+
expect(res.status).toBe(200)
|
|
527
|
+
expect(Array.isArray(res.body.data)).toBe(true)
|
|
528
|
+
})
|
|
529
|
+
})
|
|
530
|
+
})
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### 10. **package.json**
|
|
534
|
+
|
|
535
|
+
```json
|
|
536
|
+
{
|
|
537
|
+
"name": "task-api",
|
|
538
|
+
"version": "1.0.0",
|
|
539
|
+
"scripts": {
|
|
540
|
+
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
|
541
|
+
"build": "tsc",
|
|
542
|
+
"start": "node dist/server.js",
|
|
543
|
+
"test": "jest --coverage",
|
|
544
|
+
"lint": "eslint src/**/*.ts",
|
|
545
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
546
|
+
"db:migrate": "prisma migrate dev",
|
|
547
|
+
"db:push": "prisma db push",
|
|
548
|
+
"db:generate": "prisma generate"
|
|
549
|
+
},
|
|
550
|
+
"dependencies": {
|
|
551
|
+
"express": "^4.18.2",
|
|
552
|
+
"cors": "^2.8.5",
|
|
553
|
+
"helmet": "^7.1.0",
|
|
554
|
+
"express-rate-limit": "^7.1.5",
|
|
555
|
+
"morgan": "^1.10.0",
|
|
556
|
+
"bcrypt": "^5.1.1",
|
|
557
|
+
"jsonwebtoken": "^9.0.2",
|
|
558
|
+
"zod": "^3.22.4",
|
|
559
|
+
"@prisma/client": "^5.8.0",
|
|
560
|
+
"dotenv": "^16.3.1"
|
|
561
|
+
},
|
|
562
|
+
"devDependencies": {
|
|
563
|
+
"@types/express": "^4.17.21",
|
|
564
|
+
"@types/node": "^20.10.6",
|
|
565
|
+
"@types/cors": "^2.8.17",
|
|
566
|
+
"@types/morgan": "^1.9.9",
|
|
567
|
+
"@types/bcrypt": "^5.0.2",
|
|
568
|
+
"@types/jsonwebtoken": "^9.0.5",
|
|
569
|
+
"@types/jest": "^29.5.11",
|
|
570
|
+
"@types/supertest": "^6.0.2",
|
|
571
|
+
"typescript": "^5.3.3",
|
|
572
|
+
"ts-node-dev": "^2.0.0",
|
|
573
|
+
"jest": "^29.7.0",
|
|
574
|
+
"ts-jest": "^29.1.1",
|
|
575
|
+
"supertest": "^6.3.3",
|
|
576
|
+
"prisma": "^5.8.0",
|
|
577
|
+
"eslint": "^8.56.0",
|
|
578
|
+
"prettier": "^3.1.1"
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## Features
|
|
586
|
+
|
|
587
|
+
**Security:**
|
|
588
|
+
- Helmet.js for HTTP headers
|
|
589
|
+
- CORS with configurable origins
|
|
590
|
+
- Rate limiting (100 req/15min)
|
|
591
|
+
- JWT authentication
|
|
592
|
+
- Password hashing (bcrypt)
|
|
593
|
+
- Input validation (Zod)
|
|
594
|
+
|
|
595
|
+
**Database:**
|
|
596
|
+
- Prisma ORM with TypeScript
|
|
597
|
+
- Automatic migrations
|
|
598
|
+
- Type-safe queries
|
|
599
|
+
- Supports PostgreSQL, MySQL, SQLite
|
|
600
|
+
|
|
601
|
+
**Testing:**
|
|
602
|
+
- Jest + Supertest
|
|
603
|
+
- Integration tests
|
|
604
|
+
- Coverage reporting
|
|
605
|
+
- Test database isolation
|
|
606
|
+
|
|
607
|
+
**Development:**
|
|
608
|
+
- Hot reload (ts-node-dev)
|
|
609
|
+
- TypeScript with strict mode
|
|
610
|
+
- ESLint + Prettier
|
|
611
|
+
- Environment variables
|
|
612
|
+
|
|
613
|
+
**Production:**
|
|
614
|
+
- Docker support
|
|
615
|
+
- Health check endpoint
|
|
616
|
+
- Error logging
|
|
617
|
+
- Graceful shutdown
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## Getting Started
|
|
622
|
+
|
|
623
|
+
**1. Install dependencies:**
|
|
624
|
+
```bash
|
|
625
|
+
npm install
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
**2. Configure environment:**
|
|
629
|
+
```bash
|
|
630
|
+
cp .env.example .env
|
|
631
|
+
# Edit .env with your database URL and secrets
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**3. Run database migrations:**
|
|
635
|
+
```bash
|
|
636
|
+
npm run db:migrate
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**4. Start development server:**
|
|
640
|
+
```bash
|
|
641
|
+
npm run dev
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**5. Run tests:**
|
|
645
|
+
```bash
|
|
646
|
+
npm test
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## Related Commands
|
|
652
|
+
|
|
653
|
+
- `/fastapi-scaffold` - Generate FastAPI boilerplate
|
|
654
|
+
- Backend Architect (agent) - Architecture review
|
|
655
|
+
- API Builder (agent) - API design guidance
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
**Build production-ready APIs. Ship faster. Scale confidently.**
|