express-backend-starter 1.0.1
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 +236 -0
- package/bin/cli.js +103 -0
- package/package.json +39 -0
- package/src/db-config.js +172 -0
- package/src/generators.js +143 -0
- package/src/installers.js +154 -0
- package/src/prompts.js +66 -0
- package/src/templates/index.js +817 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template generator functions
|
|
3
|
+
* Contains all template generation logic for project files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate .env.example file
|
|
8
|
+
*/
|
|
9
|
+
export function generateEnvExample(answers) {
|
|
10
|
+
let envContent = `# Server Configuration
|
|
11
|
+
PORT=5000
|
|
12
|
+
NODE_ENV=development
|
|
13
|
+
|
|
14
|
+
# JWT Configuration
|
|
15
|
+
JWT_SECRET=your_jwt_secret_key_change_this_in_production
|
|
16
|
+
JWT_EXPIRE=7d
|
|
17
|
+
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
// Database-specific environment variables
|
|
21
|
+
if (answers.database === 'mongodb') {
|
|
22
|
+
envContent += `# MongoDB Configuration
|
|
23
|
+
MONGO_URI=mongodb://localhost:27017/your_database_name
|
|
24
|
+
|
|
25
|
+
`;
|
|
26
|
+
} else if (answers.database === 'postgresql') {
|
|
27
|
+
envContent += `# PostgreSQL Configuration
|
|
28
|
+
DATABASE_URL="postgresql://username:password@localhost:5432/your_database_name?schema=public"
|
|
29
|
+
|
|
30
|
+
`;
|
|
31
|
+
} else if (answers.database === 'mysql') {
|
|
32
|
+
envContent += `# MySQL Configuration
|
|
33
|
+
DATABASE_URL="mysql://username:password@localhost:3306/your_database_name"
|
|
34
|
+
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Optional configurations
|
|
39
|
+
if (answers.includeNodemailer) {
|
|
40
|
+
envContent += `# Email Configuration (Nodemailer)
|
|
41
|
+
EMAIL_HOST=smtp.gmail.com
|
|
42
|
+
EMAIL_PORT=587
|
|
43
|
+
EMAIL_USER=your_email@gmail.com
|
|
44
|
+
EMAIL_PASSWORD=your_app_password
|
|
45
|
+
EMAIL_FROM=noreply@yourapp.com
|
|
46
|
+
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
envContent += `# CORS Configuration
|
|
51
|
+
CLIENT_URL=http://localhost:3000
|
|
52
|
+
|
|
53
|
+
# Rate Limiting
|
|
54
|
+
RATE_LIMIT_WINDOW_MS=900000
|
|
55
|
+
RATE_LIMIT_MAX_REQUESTS=100
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
return envContent;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate .gitignore file
|
|
63
|
+
*/
|
|
64
|
+
export function generateGitignore() {
|
|
65
|
+
return `# Dependencies
|
|
66
|
+
node_modules/
|
|
67
|
+
package-lock.json
|
|
68
|
+
yarn.lock
|
|
69
|
+
|
|
70
|
+
# Environment variables
|
|
71
|
+
.env
|
|
72
|
+
.env.local
|
|
73
|
+
.env.*.local
|
|
74
|
+
|
|
75
|
+
# Logs
|
|
76
|
+
logs
|
|
77
|
+
*.log
|
|
78
|
+
npm-debug.log*
|
|
79
|
+
yarn-debug.log*
|
|
80
|
+
yarn-error.log*
|
|
81
|
+
|
|
82
|
+
# Runtime data
|
|
83
|
+
pids
|
|
84
|
+
*.pid
|
|
85
|
+
*.seed
|
|
86
|
+
*.pid.lock
|
|
87
|
+
|
|
88
|
+
# Testing
|
|
89
|
+
coverage/
|
|
90
|
+
.nyc_output
|
|
91
|
+
|
|
92
|
+
# Build files
|
|
93
|
+
dist/
|
|
94
|
+
build/
|
|
95
|
+
|
|
96
|
+
# IDE
|
|
97
|
+
.vscode/
|
|
98
|
+
.idea/
|
|
99
|
+
*.swp
|
|
100
|
+
*.swo
|
|
101
|
+
*~
|
|
102
|
+
|
|
103
|
+
# OS
|
|
104
|
+
.DS_Store
|
|
105
|
+
Thumbs.db
|
|
106
|
+
|
|
107
|
+
# Uploads
|
|
108
|
+
uploads/
|
|
109
|
+
public/uploads/
|
|
110
|
+
|
|
111
|
+
# Prisma
|
|
112
|
+
prisma/migrations/
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate main app.js file
|
|
118
|
+
*/
|
|
119
|
+
export function generateAppJs(answers) {
|
|
120
|
+
let imports = `/**
|
|
121
|
+
* Main Application Entry Point
|
|
122
|
+
* Express.js server with all configurations
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
import express from 'express';
|
|
126
|
+
import dotenv from 'dotenv';
|
|
127
|
+
import cors from 'cors';
|
|
128
|
+
import helmet from 'helmet';
|
|
129
|
+
import cookieParser from 'cookie-parser';
|
|
130
|
+
import rateLimit from 'express-rate-limit';
|
|
131
|
+
import { connectDB } from './config/db.js';
|
|
132
|
+
import { errorHandler } from './middleware/errorHandler.js';
|
|
133
|
+
import healthRoutes from './routes/healthRoutes.js';
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
if (answers.includeSwagger) {
|
|
137
|
+
imports += `import { swaggerUi, swaggerSpec } from './config/swagger.js';\n`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let appSetup = `
|
|
141
|
+
// Load environment variables
|
|
142
|
+
dotenv.config();
|
|
143
|
+
|
|
144
|
+
// Initialize Express app
|
|
145
|
+
const app = express();
|
|
146
|
+
|
|
147
|
+
// Connect to database
|
|
148
|
+
connectDB();
|
|
149
|
+
|
|
150
|
+
// Security middleware
|
|
151
|
+
app.use(helmet());
|
|
152
|
+
|
|
153
|
+
// CORS configuration
|
|
154
|
+
app.use(cors({
|
|
155
|
+
origin: process.env.CLIENT_URL || 'http://localhost:3000',
|
|
156
|
+
credentials: true
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
// Body parser middleware
|
|
160
|
+
app.use(express.json());
|
|
161
|
+
app.use(express.urlencoded({ extended: true }));
|
|
162
|
+
|
|
163
|
+
// Cookie parser
|
|
164
|
+
app.use(cookieParser());
|
|
165
|
+
|
|
166
|
+
// Rate limiting
|
|
167
|
+
const limiter = rateLimit({
|
|
168
|
+
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes
|
|
169
|
+
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100,
|
|
170
|
+
message: 'Too many requests from this IP, please try again later.',
|
|
171
|
+
standardHeaders: true,
|
|
172
|
+
legacyHeaders: false,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
app.use('/api/', limiter);
|
|
176
|
+
`;
|
|
177
|
+
|
|
178
|
+
if (answers.includeSwagger) {
|
|
179
|
+
appSetup += `
|
|
180
|
+
// Swagger documentation
|
|
181
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
appSetup += `
|
|
186
|
+
// Routes
|
|
187
|
+
app.get('/', (req, res) => {
|
|
188
|
+
res.json({
|
|
189
|
+
success: true,
|
|
190
|
+
message: 'Welcome to ${answers.projectName} API',
|
|
191
|
+
version: '1.0.0'
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
app.use('/api/health', healthRoutes);
|
|
196
|
+
|
|
197
|
+
// 404 handler
|
|
198
|
+
app.use('*', (req, res) => {
|
|
199
|
+
res.status(404).json({
|
|
200
|
+
success: false,
|
|
201
|
+
message: 'Route not found'
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Global error handler (must be last)
|
|
206
|
+
app.use(errorHandler);
|
|
207
|
+
|
|
208
|
+
// Start server
|
|
209
|
+
const PORT = process.env.PORT || 5000;
|
|
210
|
+
|
|
211
|
+
app.listen(PORT, () => {
|
|
212
|
+
console.log(\`🚀 Server running on port \${PORT}\`);
|
|
213
|
+
console.log(\`📝 Environment: \${process.env.NODE_ENV || 'development'}\`);`;
|
|
214
|
+
|
|
215
|
+
if (answers.includeSwagger) {
|
|
216
|
+
appSetup += `\n console.log(\`📚 API Docs: http://localhost:\${PORT}/api-docs\`);\n`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
appSetup += `});
|
|
220
|
+
|
|
221
|
+
export default app;
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
return imports + appSetup;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Generate sample health controller
|
|
229
|
+
*/
|
|
230
|
+
export function generateSampleController() {
|
|
231
|
+
return `/**
|
|
232
|
+
* Health Check Controller
|
|
233
|
+
* Simple endpoints to verify server is running
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @desc Get server health status
|
|
238
|
+
* @route GET /api/health
|
|
239
|
+
* @access Public
|
|
240
|
+
*/
|
|
241
|
+
export const healthCheck = async (req, res) => {
|
|
242
|
+
res.status(200).json({
|
|
243
|
+
success: true,
|
|
244
|
+
message: 'Server is healthy',
|
|
245
|
+
timestamp: new Date().toISOString(),
|
|
246
|
+
uptime: process.uptime()
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @desc Get detailed server info
|
|
252
|
+
* @route GET /api/health/info
|
|
253
|
+
* @access Public
|
|
254
|
+
*/
|
|
255
|
+
export const serverInfo = async (req, res) => {
|
|
256
|
+
res.status(200).json({
|
|
257
|
+
success: true,
|
|
258
|
+
data: {
|
|
259
|
+
nodeVersion: process.version,
|
|
260
|
+
platform: process.platform,
|
|
261
|
+
memory: {
|
|
262
|
+
total: \`\${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)}MB\`,
|
|
263
|
+
used: \`\${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB\`
|
|
264
|
+
},
|
|
265
|
+
uptime: \`\${Math.floor(process.uptime())}s\`
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Generate sample routes
|
|
274
|
+
*/
|
|
275
|
+
export function generateSampleRoute() {
|
|
276
|
+
return `/**
|
|
277
|
+
* Health Routes
|
|
278
|
+
* Routes for health check endpoints
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
import express from 'express';
|
|
282
|
+
import { healthCheck, serverInfo } from '../controllers/healthController.js';
|
|
283
|
+
|
|
284
|
+
const router = express.Router();
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @swagger
|
|
288
|
+
* /api/health:
|
|
289
|
+
* get:
|
|
290
|
+
* summary: Health check endpoint
|
|
291
|
+
* tags: [Health]
|
|
292
|
+
* responses:
|
|
293
|
+
* 200:
|
|
294
|
+
* description: Server is healthy
|
|
295
|
+
*/
|
|
296
|
+
router.get('/', healthCheck);
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @swagger
|
|
300
|
+
* /api/health/info:
|
|
301
|
+
* get:
|
|
302
|
+
* summary: Get server information
|
|
303
|
+
* tags: [Health]
|
|
304
|
+
* responses:
|
|
305
|
+
* 200:
|
|
306
|
+
* description: Server information
|
|
307
|
+
*/
|
|
308
|
+
router.get('/info', serverInfo);
|
|
309
|
+
|
|
310
|
+
export default router;
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Generate error handling middleware
|
|
316
|
+
*/
|
|
317
|
+
export function generateErrorMiddleware() {
|
|
318
|
+
return `/**
|
|
319
|
+
* Global Error Handler Middleware
|
|
320
|
+
* Catches and formats all errors
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Error handling middleware
|
|
325
|
+
*/
|
|
326
|
+
export const errorHandler = (err, req, res, next) => {
|
|
327
|
+
let statusCode = err.statusCode || 500;
|
|
328
|
+
let message = err.message || 'Internal Server Error';
|
|
329
|
+
|
|
330
|
+
// Mongoose bad ObjectId
|
|
331
|
+
if (err.name === 'CastError') {
|
|
332
|
+
statusCode = 400;
|
|
333
|
+
message = 'Resource not found';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Mongoose duplicate key
|
|
337
|
+
if (err.code === 11000) {
|
|
338
|
+
statusCode = 400;
|
|
339
|
+
message = 'Duplicate field value entered';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Mongoose validation error
|
|
343
|
+
if (err.name === 'ValidationError') {
|
|
344
|
+
statusCode = 400;
|
|
345
|
+
message = Object.values(err.errors)
|
|
346
|
+
.map(val => val.message)
|
|
347
|
+
.join(', ');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// JWT errors
|
|
351
|
+
if (err.name === 'JsonWebTokenError') {
|
|
352
|
+
statusCode = 401;
|
|
353
|
+
message = 'Invalid token';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (err.name === 'TokenExpiredError') {
|
|
357
|
+
statusCode = 401;
|
|
358
|
+
message = 'Token expired';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
res.status(statusCode).json({
|
|
362
|
+
success: false,
|
|
363
|
+
message,
|
|
364
|
+
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Async handler wrapper to avoid try-catch blocks
|
|
370
|
+
*/
|
|
371
|
+
export const asyncHandler = (fn) => (req, res, next) =>
|
|
372
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
373
|
+
`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Generate auth middleware
|
|
378
|
+
*/
|
|
379
|
+
export function generateAuthMiddleware() {
|
|
380
|
+
return `/**
|
|
381
|
+
* Authentication Middleware
|
|
382
|
+
* JWT-based authentication
|
|
383
|
+
*/
|
|
384
|
+
|
|
385
|
+
import jwt from 'jsonwebtoken';
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Protect routes - verify JWT token
|
|
389
|
+
*/
|
|
390
|
+
export const protect = async (req, res, next) => {
|
|
391
|
+
let token;
|
|
392
|
+
|
|
393
|
+
// Check for token in headers
|
|
394
|
+
if (
|
|
395
|
+
req.headers.authorization &&
|
|
396
|
+
req.headers.authorization.startsWith('Bearer')
|
|
397
|
+
) {
|
|
398
|
+
token = req.headers.authorization.split(' ')[1];
|
|
399
|
+
}
|
|
400
|
+
// Check for token in cookies
|
|
401
|
+
else if (req.cookies.token) {
|
|
402
|
+
token = req.cookies.token;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Make sure token exists
|
|
406
|
+
if (!token) {
|
|
407
|
+
return res.status(401).json({
|
|
408
|
+
success: false,
|
|
409
|
+
message: 'Not authorized to access this route'
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
// Verify token
|
|
415
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
416
|
+
|
|
417
|
+
// Add user info to request
|
|
418
|
+
req.user = decoded;
|
|
419
|
+
|
|
420
|
+
next();
|
|
421
|
+
} catch (error) {
|
|
422
|
+
return res.status(401).json({
|
|
423
|
+
success: false,
|
|
424
|
+
message: 'Not authorized to access this route'
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Generate JWT token
|
|
431
|
+
*/
|
|
432
|
+
export const generateToken = (userId) => {
|
|
433
|
+
return jwt.sign(
|
|
434
|
+
{ id: userId },
|
|
435
|
+
process.env.JWT_SECRET,
|
|
436
|
+
{ expiresIn: process.env.JWT_EXPIRE || '7d' }
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Generate Swagger configuration
|
|
444
|
+
*/
|
|
445
|
+
export function generateSwaggerConfig(answers) {
|
|
446
|
+
return `/**
|
|
447
|
+
* Swagger API Documentation Configuration
|
|
448
|
+
*/
|
|
449
|
+
|
|
450
|
+
import swaggerJsdoc from 'swagger-jsdoc';
|
|
451
|
+
import swaggerUi from 'swagger-ui-express';
|
|
452
|
+
|
|
453
|
+
const options = {
|
|
454
|
+
definition: {
|
|
455
|
+
openapi: '3.0.0',
|
|
456
|
+
info: {
|
|
457
|
+
title: '${answers.projectName} API',
|
|
458
|
+
version: '1.0.0',
|
|
459
|
+
description: 'API documentation for ${answers.projectName}',
|
|
460
|
+
contact: {
|
|
461
|
+
name: 'API Support',
|
|
462
|
+
email: 'support@${answers.projectName}.com'
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
servers: [
|
|
466
|
+
{
|
|
467
|
+
url: 'http://localhost:5000',
|
|
468
|
+
description: 'Development server'
|
|
469
|
+
}
|
|
470
|
+
],
|
|
471
|
+
components: {
|
|
472
|
+
securitySchemes: {
|
|
473
|
+
bearerAuth: {
|
|
474
|
+
type: 'http',
|
|
475
|
+
scheme: 'bearer',
|
|
476
|
+
bearerFormat: 'JWT'
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
security: [
|
|
481
|
+
{
|
|
482
|
+
bearerAuth: []
|
|
483
|
+
}
|
|
484
|
+
]
|
|
485
|
+
},
|
|
486
|
+
apis: ['./src/routes/*.js', './src/controllers/*.js']
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const swaggerSpec = swaggerJsdoc(options);
|
|
490
|
+
|
|
491
|
+
export { swaggerUi, swaggerSpec };
|
|
492
|
+
`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Generate Nodemailer configuration
|
|
497
|
+
*/
|
|
498
|
+
export function generateNodemailerConfig() {
|
|
499
|
+
return `/**
|
|
500
|
+
* Nodemailer Email Configuration
|
|
501
|
+
*/
|
|
502
|
+
|
|
503
|
+
import nodemailer from 'nodemailer';
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Create email transporter
|
|
507
|
+
*/
|
|
508
|
+
const transporter = nodemailer.createTransport({
|
|
509
|
+
host: process.env.EMAIL_HOST,
|
|
510
|
+
port: process.env.EMAIL_PORT,
|
|
511
|
+
secure: false, // true for 465, false for other ports
|
|
512
|
+
auth: {
|
|
513
|
+
user: process.env.EMAIL_USER,
|
|
514
|
+
pass: process.env.EMAIL_PASSWORD
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Send email function
|
|
520
|
+
* @param {Object} options - Email options
|
|
521
|
+
*/
|
|
522
|
+
export const sendEmail = async (options) => {
|
|
523
|
+
const mailOptions = {
|
|
524
|
+
from: process.env.EMAIL_FROM || process.env.EMAIL_USER,
|
|
525
|
+
to: options.to,
|
|
526
|
+
subject: options.subject,
|
|
527
|
+
text: options.text,
|
|
528
|
+
html: options.html
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const info = await transporter.sendMail(mailOptions);
|
|
533
|
+
console.log('Email sent:', info.messageId);
|
|
534
|
+
return info;
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.error('Error sending email:', error);
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
export default transporter;
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Generate Zod validation example
|
|
547
|
+
*/
|
|
548
|
+
export function generateZodValidation() {
|
|
549
|
+
return `/**
|
|
550
|
+
* Zod Validation Schemas
|
|
551
|
+
* Type-safe validation for request data
|
|
552
|
+
*/
|
|
553
|
+
|
|
554
|
+
import { z } from 'zod';
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* User registration validation schema
|
|
558
|
+
*/
|
|
559
|
+
export const registerSchema = z.object({
|
|
560
|
+
name: z.string().min(2, 'Name must be at least 2 characters'),
|
|
561
|
+
email: z.string().email('Invalid email address'),
|
|
562
|
+
password: z.string().min(6, 'Password must be at least 6 characters')
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* User login validation schema
|
|
567
|
+
*/
|
|
568
|
+
export const loginSchema = z.object({
|
|
569
|
+
email: z.string().email('Invalid email address'),
|
|
570
|
+
password: z.string().min(1, 'Password is required')
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Validation middleware wrapper
|
|
575
|
+
*/
|
|
576
|
+
export const validate = (schema) => {
|
|
577
|
+
return async (req, res, next) => {
|
|
578
|
+
try {
|
|
579
|
+
await schema.parseAsync(req.body);
|
|
580
|
+
next();
|
|
581
|
+
} catch (error) {
|
|
582
|
+
if (error instanceof z.ZodError) {
|
|
583
|
+
return res.status(400).json({
|
|
584
|
+
success: false,
|
|
585
|
+
message: 'Validation error',
|
|
586
|
+
errors: error.errors.map(err => ({
|
|
587
|
+
field: err.path.join('.'),
|
|
588
|
+
message: err.message
|
|
589
|
+
}))
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
next(error);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
};
|
|
596
|
+
`;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Generate Mongoose User model
|
|
601
|
+
*/
|
|
602
|
+
export function generateMongooseModel() {
|
|
603
|
+
return `/**
|
|
604
|
+
* User Model (Mongoose)
|
|
605
|
+
* MongoDB user schema and model
|
|
606
|
+
*/
|
|
607
|
+
|
|
608
|
+
import mongoose from 'mongoose';
|
|
609
|
+
import bcrypt from 'bcryptjs';
|
|
610
|
+
|
|
611
|
+
const userSchema = new mongoose.Schema(
|
|
612
|
+
{
|
|
613
|
+
name: {
|
|
614
|
+
type: String,
|
|
615
|
+
required: [true, 'Please provide a name'],
|
|
616
|
+
trim: true
|
|
617
|
+
},
|
|
618
|
+
email: {
|
|
619
|
+
type: String,
|
|
620
|
+
required: [true, 'Please provide an email'],
|
|
621
|
+
unique: true,
|
|
622
|
+
lowercase: true,
|
|
623
|
+
match: [
|
|
624
|
+
/^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$/,
|
|
625
|
+
'Please provide a valid email'
|
|
626
|
+
]
|
|
627
|
+
},
|
|
628
|
+
password: {
|
|
629
|
+
type: String,
|
|
630
|
+
required: [true, 'Please provide a password'],
|
|
631
|
+
minlength: 6,
|
|
632
|
+
select: false
|
|
633
|
+
},
|
|
634
|
+
role: {
|
|
635
|
+
type: String,
|
|
636
|
+
enum: ['user', 'admin'],
|
|
637
|
+
default: 'user'
|
|
638
|
+
},
|
|
639
|
+
isActive: {
|
|
640
|
+
type: Boolean,
|
|
641
|
+
default: true
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
timestamps: true
|
|
646
|
+
}
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
// Hash password before saving
|
|
650
|
+
userSchema.pre('save', async function (next) {
|
|
651
|
+
if (!this.isModified('password')) {
|
|
652
|
+
next();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const salt = await bcrypt.genSalt(10);
|
|
656
|
+
this.password = await bcrypt.hash(this.password, salt);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// Compare password method
|
|
660
|
+
userSchema.methods.comparePassword = async function (enteredPassword) {
|
|
661
|
+
return await bcrypt.compare(enteredPassword, this.password);
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const User = mongoose.model('User', userSchema);
|
|
665
|
+
|
|
666
|
+
export default User;
|
|
667
|
+
`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Generate README.md
|
|
672
|
+
*/
|
|
673
|
+
export function generateReadme(answers) {
|
|
674
|
+
let readme = `# ${answers.projectName}
|
|
675
|
+
|
|
676
|
+
Backend project generated by Express Backend Starter CLI
|
|
677
|
+
|
|
678
|
+
## 🚀 Quick Start
|
|
679
|
+
|
|
680
|
+
### Prerequisites
|
|
681
|
+
|
|
682
|
+
- Node.js (v14 or higher)
|
|
683
|
+
- npm or yarn
|
|
684
|
+
`;
|
|
685
|
+
|
|
686
|
+
if (answers.database === 'mongodb') {
|
|
687
|
+
readme += `- MongoDB installed and running\n`;
|
|
688
|
+
} else if (answers.database === 'postgresql') {
|
|
689
|
+
readme += `- PostgreSQL installed and running\n`;
|
|
690
|
+
} else if (answers.database === 'mysql') {
|
|
691
|
+
readme += `- MySQL installed and running\n`;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
readme += `
|
|
695
|
+
### Installation
|
|
696
|
+
|
|
697
|
+
1. Install dependencies:
|
|
698
|
+
\`\`\`bash
|
|
699
|
+
npm install
|
|
700
|
+
\`\`\`
|
|
701
|
+
|
|
702
|
+
2. Create a \`.env\` file from \`.env.example\`:
|
|
703
|
+
\`\`\`bash
|
|
704
|
+
cp .env.example .env
|
|
705
|
+
\`\`\`
|
|
706
|
+
|
|
707
|
+
3. Update the \`.env\` file with your configuration
|
|
708
|
+
|
|
709
|
+
`;
|
|
710
|
+
|
|
711
|
+
if (answers.database === 'postgresql' || answers.database === 'mysql') {
|
|
712
|
+
readme += `4. Run Prisma migrations:
|
|
713
|
+
\`\`\`bash
|
|
714
|
+
npx prisma migrate dev
|
|
715
|
+
\`\`\`
|
|
716
|
+
|
|
717
|
+
5. Generate Prisma Client:
|
|
718
|
+
\`\`\`bash
|
|
719
|
+
npx prisma generate
|
|
720
|
+
\`\`\`
|
|
721
|
+
|
|
722
|
+
`;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
readme += `### Running the Application
|
|
726
|
+
|
|
727
|
+
Development mode with auto-reload:
|
|
728
|
+
\`\`\`bash
|
|
729
|
+
npm run dev
|
|
730
|
+
\`\`\`
|
|
731
|
+
|
|
732
|
+
Production mode:
|
|
733
|
+
\`\`\`bash
|
|
734
|
+
npm start
|
|
735
|
+
\`\`\`
|
|
736
|
+
|
|
737
|
+
## 📦 Tech Stack
|
|
738
|
+
|
|
739
|
+
- **Framework:** Express.js
|
|
740
|
+
- **Database:** ${answers.database.charAt(0).toUpperCase() + answers.database.slice(1)}
|
|
741
|
+
`;
|
|
742
|
+
|
|
743
|
+
if (answers.database === 'mongodb') {
|
|
744
|
+
readme += `- **ORM:** Mongoose\n`;
|
|
745
|
+
} else {
|
|
746
|
+
readme += `- **ORM:** Prisma\n`;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
readme += `- **Authentication:** JWT (jsonwebtoken)
|
|
750
|
+
- **Security:** Helmet, CORS, Rate Limiting
|
|
751
|
+
- **File Upload:** Multer
|
|
752
|
+
`;
|
|
753
|
+
|
|
754
|
+
if (answers.includeSwagger) {
|
|
755
|
+
readme += `- **Documentation:** Swagger/OpenAPI\n`;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (answers.includeZod) {
|
|
759
|
+
readme += `- **Validation:** Zod\n`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (answers.includeNodemailer) {
|
|
763
|
+
readme += `- **Email:** Nodemailer\n`;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
readme += `
|
|
767
|
+
## 📁 Project Structure
|
|
768
|
+
|
|
769
|
+
\`\`\`
|
|
770
|
+
src/
|
|
771
|
+
├── config/ # Configuration files (DB, Swagger, etc.)
|
|
772
|
+
├── controllers/ # Route controllers
|
|
773
|
+
├── middleware/ # Custom middleware
|
|
774
|
+
├── models/ # Database models
|
|
775
|
+
├── routes/ # API routes
|
|
776
|
+
├── services/ # Business logic
|
|
777
|
+
└── utils/ # Utility functions
|
|
778
|
+
\`\`\`
|
|
779
|
+
|
|
780
|
+
## 🔑 Environment Variables
|
|
781
|
+
|
|
782
|
+
See \`.env.example\` for all required environment variables.
|
|
783
|
+
|
|
784
|
+
## 📚 API Documentation
|
|
785
|
+
|
|
786
|
+
`;
|
|
787
|
+
|
|
788
|
+
if (answers.includeSwagger) {
|
|
789
|
+
readme += `Swagger documentation is available at: \`http://localhost:5000/api-docs\`
|
|
790
|
+
|
|
791
|
+
`;
|
|
792
|
+
} else {
|
|
793
|
+
readme += `API documentation coming soon...
|
|
794
|
+
|
|
795
|
+
`;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
readme += `## 🛡️ Security Features
|
|
799
|
+
|
|
800
|
+
- Helmet for security headers
|
|
801
|
+
- CORS configuration
|
|
802
|
+
- Rate limiting
|
|
803
|
+
- JWT authentication
|
|
804
|
+
- Password hashing with bcrypt
|
|
805
|
+
- Cookie parser
|
|
806
|
+
|
|
807
|
+
## 📝 License
|
|
808
|
+
|
|
809
|
+
ISC
|
|
810
|
+
|
|
811
|
+
## 🤝 Contributing
|
|
812
|
+
|
|
813
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
814
|
+
`;
|
|
815
|
+
|
|
816
|
+
return readme;
|
|
817
|
+
}
|