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.
@@ -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
+ }