@sachin-thakur/create-nodejs-app 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/DEVELOPMENT.md +183 -0
- package/README.md +238 -0
- package/bin/cli.js +177 -0
- package/package.json +36 -0
- package/src/generator.js +121 -0
- package/src/templates/clean.js +11 -0
- package/src/templates/featureBased.js +11 -0
- package/src/templates/index.js +76 -0
- package/src/templates/mvc.js +890 -0
- package/src/utils/cicd.js +99 -0
- package/src/utils/docker.js +157 -0
- package/src/utils/envFile.js +102 -0
- package/src/utils/packageJson.js +142 -0
- package/src/utils/readme.js +277 -0
- package/test.sh +40 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
async function generateMVCStructure(srcPath, config) {
|
|
5
|
+
const ext = config.language === 'typescript' ? 'ts' : 'js';
|
|
6
|
+
const isTS = config.language === 'typescript';
|
|
7
|
+
|
|
8
|
+
// Create directory structure
|
|
9
|
+
const dirs = ['config', 'controllers', 'models', 'routes', 'middlewares', 'services', 'utils'];
|
|
10
|
+
for (const dir of dirs) {
|
|
11
|
+
await fs.ensureDir(path.join(srcPath, dir));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Generate main index file
|
|
15
|
+
await generateIndexFile(srcPath, config, ext, isTS);
|
|
16
|
+
|
|
17
|
+
// Generate config files
|
|
18
|
+
await generateConfigFiles(path.join(srcPath, 'config'), config, ext, isTS);
|
|
19
|
+
|
|
20
|
+
// Generate middleware files
|
|
21
|
+
await generateMiddlewareFiles(path.join(srcPath, 'middlewares'), config, ext, isTS);
|
|
22
|
+
|
|
23
|
+
// Generate utility files
|
|
24
|
+
await generateUtilityFiles(path.join(srcPath, 'utils'), config, ext, isTS);
|
|
25
|
+
|
|
26
|
+
// Generate controllers
|
|
27
|
+
await generateControllers(path.join(srcPath, 'controllers'), config, ext, isTS);
|
|
28
|
+
|
|
29
|
+
// Generate routes
|
|
30
|
+
await generateRoutes(path.join(srcPath, 'routes'), config, ext, isTS);
|
|
31
|
+
|
|
32
|
+
// Generate models
|
|
33
|
+
if (config.database !== 'none') {
|
|
34
|
+
await generateModels(path.join(srcPath, 'models'), config, ext, isTS);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Generate services
|
|
38
|
+
await generateServices(path.join(srcPath, 'services'), config, ext, isTS);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function generateIndexFile(srcPath, config, ext, isTS) {
|
|
42
|
+
const imports = isTS
|
|
43
|
+
? `import express, { Application } from 'express';
|
|
44
|
+
import dotenv from 'dotenv';
|
|
45
|
+
import cors from 'cors';
|
|
46
|
+
${config.features.includes('logging') ? "import morgan from 'morgan';" : ''}
|
|
47
|
+
${config.features.includes('rate-limit') ? "import rateLimit from 'express-rate-limit';" : ''}
|
|
48
|
+
import { errorHandler } from './middlewares/errorHandler';
|
|
49
|
+
${config.features.includes('logging') ? "import logger from './config/logger';" : ''}
|
|
50
|
+
${config.database !== 'none' ? "import { connectDatabase } from './config/database';" : ''}
|
|
51
|
+
import routes from './routes';
|
|
52
|
+
${config.features.includes('swagger') ? "import { setupSwagger } from './config/swagger';" : ''}`
|
|
53
|
+
: `const express = require('express');
|
|
54
|
+
const dotenv = require('dotenv');
|
|
55
|
+
const cors = require('cors');
|
|
56
|
+
${config.features.includes('logging') ? "const morgan = require('morgan');" : ''}
|
|
57
|
+
${config.features.includes('rate-limit') ? "const rateLimit = require('express-rate-limit');" : ''}
|
|
58
|
+
const { errorHandler } = require('./middlewares/errorHandler');
|
|
59
|
+
${config.features.includes('logging') ? "const logger = require('./config/logger');" : ''}
|
|
60
|
+
${config.database !== 'none' ? "const { connectDatabase } = require('./config/database');" : ''}
|
|
61
|
+
const routes = require('./routes');
|
|
62
|
+
${config.features.includes('swagger') ? "const { setupSwagger } = require('./config/swagger');" : ''}`;
|
|
63
|
+
|
|
64
|
+
const indexContent = `${imports}
|
|
65
|
+
|
|
66
|
+
// Load environment variables
|
|
67
|
+
dotenv.config();
|
|
68
|
+
|
|
69
|
+
const app${isTS ? ': Application' : ''} = express();
|
|
70
|
+
const PORT = process.env.PORT || 3000;
|
|
71
|
+
|
|
72
|
+
// Middleware
|
|
73
|
+
app.use(express.json());
|
|
74
|
+
app.use(express.urlencoded({ extended: true }));
|
|
75
|
+
app.use(cors(${config.features.includes('cors') ? `{
|
|
76
|
+
origin: process.env.CORS_ORIGIN || '*',
|
|
77
|
+
credentials: true,
|
|
78
|
+
}` : ''}));
|
|
79
|
+
|
|
80
|
+
${config.features.includes('logging') ? `// Request logging
|
|
81
|
+
app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } }));
|
|
82
|
+
` : ''}
|
|
83
|
+
${config.features.includes('rate-limit') ? `// Rate limiting
|
|
84
|
+
const limiter = rateLimit({
|
|
85
|
+
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'),
|
|
86
|
+
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'),
|
|
87
|
+
message: 'Too many requests from this IP, please try again later.',
|
|
88
|
+
});
|
|
89
|
+
app.use('/api/', limiter);
|
|
90
|
+
` : ''}
|
|
91
|
+
// Routes
|
|
92
|
+
app.use('/api', routes);
|
|
93
|
+
|
|
94
|
+
// Health check
|
|
95
|
+
app.get('/health', (req, res) => {
|
|
96
|
+
res.status(200).json({ status: 'OK', timestamp: new Date().toISOString() });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
${config.features.includes('swagger') ? `// API Documentation
|
|
100
|
+
setupSwagger(app);
|
|
101
|
+
` : ''}
|
|
102
|
+
// Error handling middleware (must be last)
|
|
103
|
+
app.use(errorHandler);
|
|
104
|
+
|
|
105
|
+
// Start server
|
|
106
|
+
async function startServer() {
|
|
107
|
+
try {
|
|
108
|
+
${config.database !== 'none' ? ` // Connect to database
|
|
109
|
+
await connectDatabase();
|
|
110
|
+
` : ''}
|
|
111
|
+
app.listen(PORT, () => {
|
|
112
|
+
${config.features.includes('logging') ? `logger.info(\`Server running on port \${PORT}\`);` : `console.log(\`Server running on port \${PORT}\`);`}
|
|
113
|
+
${config.features.includes('swagger') ? `console.log(\`API Documentation: http://localhost:\${PORT}/api-docs\`);` : ''}
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
${config.features.includes('logging') ? `logger.error('Failed to start server:', error);` : `console.error('Failed to start server:', error);`}
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
startServer();
|
|
122
|
+
|
|
123
|
+
${isTS ? 'export default app;' : 'module.exports = app;'}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
await fs.writeFile(path.join(srcPath, `index.${ext}`), indexContent);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function generateConfigFiles(configPath, config, ext, isTS) {
|
|
130
|
+
// Database config
|
|
131
|
+
if (config.database === 'postgresql') {
|
|
132
|
+
const dbConfig = isTS
|
|
133
|
+
? `import { Pool } from 'pg';
|
|
134
|
+
import logger from './logger';
|
|
135
|
+
|
|
136
|
+
const pool = new Pool({
|
|
137
|
+
host: process.env.DB_HOST || 'localhost',
|
|
138
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
139
|
+
database: process.env.DB_NAME,
|
|
140
|
+
user: process.env.DB_USER,
|
|
141
|
+
password: process.env.DB_PASSWORD,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
pool.on('connect', () => {
|
|
145
|
+
logger.info('Connected to PostgreSQL database');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
pool.on('error', (err) => {
|
|
149
|
+
logger.error('Unexpected error on PostgreSQL client', err);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export async function connectDatabase(): Promise<void> {
|
|
153
|
+
try {
|
|
154
|
+
await pool.query('SELECT NOW()');
|
|
155
|
+
logger.info('Database connection established');
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error('Failed to connect to database:', error);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export { pool };
|
|
163
|
+
export default pool;`
|
|
164
|
+
: `const { Pool } = require('pg');
|
|
165
|
+
${config.features.includes('logging') ? "const logger = require('./logger');" : ''}
|
|
166
|
+
|
|
167
|
+
const pool = new Pool({
|
|
168
|
+
host: process.env.DB_HOST || 'localhost',
|
|
169
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
170
|
+
database: process.env.DB_NAME,
|
|
171
|
+
user: process.env.DB_USER,
|
|
172
|
+
password: process.env.DB_PASSWORD,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
pool.on('connect', () => {
|
|
176
|
+
${config.features.includes('logging') ? "logger.info('Connected to PostgreSQL database');" : "console.log('Connected to PostgreSQL database');"}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
pool.on('error', (err) => {
|
|
180
|
+
${config.features.includes('logging') ? "logger.error('Unexpected error on PostgreSQL client', err);" : "console.error('Unexpected error on PostgreSQL client', err);"}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
async function connectDatabase() {
|
|
184
|
+
try {
|
|
185
|
+
await pool.query('SELECT NOW()');
|
|
186
|
+
${config.features.includes('logging') ? "logger.info('Database connection established');" : "console.log('Database connection established');"}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
${config.features.includes('logging') ? "logger.error('Failed to connect to database:', error);" : "console.error('Failed to connect to database:', error);"}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { pool, connectDatabase };`;
|
|
194
|
+
|
|
195
|
+
await fs.writeFile(path.join(configPath, `database.${ext}`), dbConfig);
|
|
196
|
+
} else if (config.database === 'mongodb') {
|
|
197
|
+
const dbConfig = isTS
|
|
198
|
+
? `import mongoose from 'mongoose';
|
|
199
|
+
import logger from './logger';
|
|
200
|
+
|
|
201
|
+
export async function connectDatabase(): Promise<void> {
|
|
202
|
+
try {
|
|
203
|
+
const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp';
|
|
204
|
+
await mongoose.connect(uri);
|
|
205
|
+
logger.info('Connected to MongoDB');
|
|
206
|
+
} catch (error) {
|
|
207
|
+
logger.error('MongoDB connection error:', error);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
mongoose.connection.on('disconnected', () => {
|
|
213
|
+
logger.warn('MongoDB disconnected');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export default mongoose;`
|
|
217
|
+
: `const mongoose = require('mongoose');
|
|
218
|
+
${config.features.includes('logging') ? "const logger = require('./logger');" : ''}
|
|
219
|
+
|
|
220
|
+
async function connectDatabase() {
|
|
221
|
+
try {
|
|
222
|
+
const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp';
|
|
223
|
+
await mongoose.connect(uri);
|
|
224
|
+
${config.features.includes('logging') ? "logger.info('Connected to MongoDB');" : "console.log('Connected to MongoDB');"}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
${config.features.includes('logging') ? "logger.error('MongoDB connection error:', error);" : "console.error('MongoDB connection error:', error);"}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
mongoose.connection.on('disconnected', () => {
|
|
232
|
+
${config.features.includes('logging') ? "logger.warn('MongoDB disconnected');" : "console.log('MongoDB disconnected');"}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
module.exports = { connectDatabase, mongoose };`;
|
|
236
|
+
|
|
237
|
+
await fs.writeFile(path.join(configPath, `database.${ext}`), dbConfig);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Logger config
|
|
241
|
+
if (config.features.includes('logging')) {
|
|
242
|
+
const loggerConfig = isTS
|
|
243
|
+
? `import winston from 'winston';
|
|
244
|
+
import path from 'path';
|
|
245
|
+
|
|
246
|
+
const logger = winston.createLogger({
|
|
247
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
248
|
+
format: winston.format.combine(
|
|
249
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
250
|
+
winston.format.errors({ stack: true }),
|
|
251
|
+
winston.format.splat(),
|
|
252
|
+
winston.format.json()
|
|
253
|
+
),
|
|
254
|
+
defaultMeta: { service: 'app' },
|
|
255
|
+
transports: [
|
|
256
|
+
new winston.transports.File({
|
|
257
|
+
filename: path.join('logs', 'error.log'),
|
|
258
|
+
level: 'error',
|
|
259
|
+
}),
|
|
260
|
+
new winston.transports.File({
|
|
261
|
+
filename: path.join('logs', 'combined.log'),
|
|
262
|
+
}),
|
|
263
|
+
],
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
267
|
+
logger.add(
|
|
268
|
+
new winston.transports.Console({
|
|
269
|
+
format: winston.format.combine(
|
|
270
|
+
winston.format.colorize(),
|
|
271
|
+
winston.format.simple()
|
|
272
|
+
),
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export default logger;`
|
|
278
|
+
: `const winston = require('winston');
|
|
279
|
+
const path = require('path');
|
|
280
|
+
|
|
281
|
+
const logger = winston.createLogger({
|
|
282
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
283
|
+
format: winston.format.combine(
|
|
284
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
285
|
+
winston.format.errors({ stack: true }),
|
|
286
|
+
winston.format.splat(),
|
|
287
|
+
winston.format.json()
|
|
288
|
+
),
|
|
289
|
+
defaultMeta: { service: 'app' },
|
|
290
|
+
transports: [
|
|
291
|
+
new winston.transports.File({
|
|
292
|
+
filename: path.join('logs', 'error.log'),
|
|
293
|
+
level: 'error',
|
|
294
|
+
}),
|
|
295
|
+
new winston.transports.File({
|
|
296
|
+
filename: path.join('logs', 'combined.log'),
|
|
297
|
+
}),
|
|
298
|
+
],
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
302
|
+
logger.add(
|
|
303
|
+
new winston.transports.Console({
|
|
304
|
+
format: winston.format.combine(
|
|
305
|
+
winston.format.colorize(),
|
|
306
|
+
winston.format.simple()
|
|
307
|
+
),
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = logger;`;
|
|
313
|
+
|
|
314
|
+
await fs.writeFile(path.join(configPath, `logger.${ext}`), loggerConfig);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Swagger config
|
|
318
|
+
if (config.features.includes('swagger')) {
|
|
319
|
+
const swaggerConfig = isTS
|
|
320
|
+
? `import swaggerJsdoc from 'swagger-jsdoc';
|
|
321
|
+
import swaggerUi from 'swagger-ui-express';
|
|
322
|
+
import { Application } from 'express';
|
|
323
|
+
|
|
324
|
+
const options = {
|
|
325
|
+
definition: {
|
|
326
|
+
openapi: '3.0.0',
|
|
327
|
+
info: {
|
|
328
|
+
title: 'API Documentation',
|
|
329
|
+
version: '1.0.0',
|
|
330
|
+
description: 'API documentation for the application',
|
|
331
|
+
},
|
|
332
|
+
servers: [
|
|
333
|
+
{
|
|
334
|
+
url: \`http://localhost:\${process.env.PORT || 3000}\`,
|
|
335
|
+
description: 'Development server',
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
components: {
|
|
339
|
+
securitySchemes: {
|
|
340
|
+
bearerAuth: {
|
|
341
|
+
type: 'http',
|
|
342
|
+
scheme: 'bearer',
|
|
343
|
+
bearerFormat: 'JWT',
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
apis: ['./src/routes/*.${ext}'],
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const specs = swaggerJsdoc(options);
|
|
352
|
+
|
|
353
|
+
export function setupSwagger(app: Application): void {
|
|
354
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export default specs;`
|
|
358
|
+
: `const swaggerJsdoc = require('swagger-jsdoc');
|
|
359
|
+
const swaggerUi = require('swagger-ui-express');
|
|
360
|
+
|
|
361
|
+
const options = {
|
|
362
|
+
definition: {
|
|
363
|
+
openapi: '3.0.0',
|
|
364
|
+
info: {
|
|
365
|
+
title: 'API Documentation',
|
|
366
|
+
version: '1.0.0',
|
|
367
|
+
description: 'API documentation for the application',
|
|
368
|
+
},
|
|
369
|
+
servers: [
|
|
370
|
+
{
|
|
371
|
+
url: \`http://localhost:\${process.env.PORT || 3000}\`,
|
|
372
|
+
description: 'Development server',
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
components: {
|
|
376
|
+
securitySchemes: {
|
|
377
|
+
bearerAuth: {
|
|
378
|
+
type: 'http',
|
|
379
|
+
scheme: 'bearer',
|
|
380
|
+
bearerFormat: 'JWT',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
apis: ['./src/routes/*.${ext}'],
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const specs = swaggerJsdoc(options);
|
|
389
|
+
|
|
390
|
+
function setupSwagger(app) {
|
|
391
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
module.exports = { setupSwagger, specs };`;
|
|
395
|
+
|
|
396
|
+
await fs.writeFile(path.join(configPath, `swagger.${ext}`), swaggerConfig);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function generateMiddlewareFiles(middlewarePath, config, ext, isTS) {
|
|
401
|
+
// Error handler
|
|
402
|
+
const errorHandler = isTS
|
|
403
|
+
? `import { Request, Response, NextFunction } from 'express';
|
|
404
|
+
${config.features.includes('logging') ? "import logger from '../config/logger';" : ''}
|
|
405
|
+
|
|
406
|
+
export class AppError extends Error {
|
|
407
|
+
statusCode: number;
|
|
408
|
+
isOperational: boolean;
|
|
409
|
+
|
|
410
|
+
constructor(message: string, statusCode: number = 500) {
|
|
411
|
+
super(message);
|
|
412
|
+
this.statusCode = statusCode;
|
|
413
|
+
this.isOperational = true;
|
|
414
|
+
Error.captureStackTrace(this, this.constructor);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function errorHandler(
|
|
419
|
+
err: AppError | Error,
|
|
420
|
+
req: Request,
|
|
421
|
+
res: Response,
|
|
422
|
+
next: NextFunction
|
|
423
|
+
): void {
|
|
424
|
+
const statusCode = 'statusCode' in err ? err.statusCode : 500;
|
|
425
|
+
const message = err.message || 'Internal Server Error';
|
|
426
|
+
|
|
427
|
+
${config.features.includes('logging') ? `logger.error({
|
|
428
|
+
message: err.message,
|
|
429
|
+
stack: err.stack,
|
|
430
|
+
url: req.url,
|
|
431
|
+
method: req.method,
|
|
432
|
+
});` : `console.error('Error:', {
|
|
433
|
+
message: err.message,
|
|
434
|
+
stack: err.stack,
|
|
435
|
+
url: req.url,
|
|
436
|
+
method: req.method,
|
|
437
|
+
});`}
|
|
438
|
+
|
|
439
|
+
res.status(statusCode).json({
|
|
440
|
+
success: false,
|
|
441
|
+
message,
|
|
442
|
+
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
|
|
443
|
+
});
|
|
444
|
+
}`
|
|
445
|
+
: `${config.features.includes('logging') ? "const logger = require('../config/logger');" : ''}
|
|
446
|
+
|
|
447
|
+
class AppError extends Error {
|
|
448
|
+
constructor(message, statusCode = 500) {
|
|
449
|
+
super(message);
|
|
450
|
+
this.statusCode = statusCode;
|
|
451
|
+
this.isOperational = true;
|
|
452
|
+
Error.captureStackTrace(this, this.constructor);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function errorHandler(err, req, res, next) {
|
|
457
|
+
const statusCode = err.statusCode || 500;
|
|
458
|
+
const message = err.message || 'Internal Server Error';
|
|
459
|
+
|
|
460
|
+
${config.features.includes('logging') ? `logger.error({
|
|
461
|
+
message: err.message,
|
|
462
|
+
stack: err.stack,
|
|
463
|
+
url: req.url,
|
|
464
|
+
method: req.method,
|
|
465
|
+
});` : `console.error('Error:', {
|
|
466
|
+
message: err.message,
|
|
467
|
+
stack: err.stack,
|
|
468
|
+
url: req.url,
|
|
469
|
+
method: req.method,
|
|
470
|
+
});`}
|
|
471
|
+
|
|
472
|
+
res.status(statusCode).json({
|
|
473
|
+
success: false,
|
|
474
|
+
message,
|
|
475
|
+
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
module.exports = { errorHandler, AppError };`;
|
|
480
|
+
|
|
481
|
+
await fs.writeFile(path.join(middlewarePath, `errorHandler.${ext}`), errorHandler);
|
|
482
|
+
|
|
483
|
+
// Auth middleware (if auth feature is enabled)
|
|
484
|
+
if (config.features.includes('auth')) {
|
|
485
|
+
const authMiddleware = isTS
|
|
486
|
+
? `import { Request, Response, NextFunction } from 'express';
|
|
487
|
+
import jwt from 'jsonwebtoken';
|
|
488
|
+
import { AppError } from './errorHandler';
|
|
489
|
+
|
|
490
|
+
export interface AuthRequest extends Request {
|
|
491
|
+
user?: any;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function authenticate(
|
|
495
|
+
req: AuthRequest,
|
|
496
|
+
res: Response,
|
|
497
|
+
next: NextFunction
|
|
498
|
+
): void {
|
|
499
|
+
try {
|
|
500
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
501
|
+
|
|
502
|
+
if (!token) {
|
|
503
|
+
throw new AppError('No token provided', 401);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
|
|
507
|
+
req.user = decoded;
|
|
508
|
+
next();
|
|
509
|
+
} catch (error) {
|
|
510
|
+
next(new AppError('Invalid or expired token', 401));
|
|
511
|
+
}
|
|
512
|
+
}`
|
|
513
|
+
: `const jwt = require('jsonwebtoken');
|
|
514
|
+
const { AppError } = require('./errorHandler');
|
|
515
|
+
|
|
516
|
+
function authenticate(req, res, next) {
|
|
517
|
+
try {
|
|
518
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
519
|
+
|
|
520
|
+
if (!token) {
|
|
521
|
+
throw new AppError('No token provided', 401);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
|
|
525
|
+
req.user = decoded;
|
|
526
|
+
next();
|
|
527
|
+
} catch (error) {
|
|
528
|
+
next(new AppError('Invalid or expired token', 401));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
module.exports = { authenticate };`;
|
|
533
|
+
|
|
534
|
+
await fs.writeFile(path.join(middlewarePath, `auth.${ext}`), authMiddleware);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Validation middleware (if validation feature is enabled)
|
|
538
|
+
if (config.features.includes('validation')) {
|
|
539
|
+
const validationMiddleware = isTS
|
|
540
|
+
? `import { Request, Response, NextFunction } from 'express';
|
|
541
|
+
import { AnyZodObject, ZodError } from 'zod';
|
|
542
|
+
import { AppError } from './errorHandler';
|
|
543
|
+
|
|
544
|
+
export function validate(schema: AnyZodObject) {
|
|
545
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
546
|
+
try {
|
|
547
|
+
await schema.parseAsync({
|
|
548
|
+
body: req.body,
|
|
549
|
+
query: req.query,
|
|
550
|
+
params: req.params,
|
|
551
|
+
});
|
|
552
|
+
next();
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (error instanceof ZodError) {
|
|
555
|
+
const message = error.errors.map((e) => e.message).join(', ');
|
|
556
|
+
next(new AppError(message, 400));
|
|
557
|
+
} else {
|
|
558
|
+
next(error);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}`
|
|
563
|
+
: `const { AppError } = require('./errorHandler');
|
|
564
|
+
|
|
565
|
+
function validate(schema) {
|
|
566
|
+
return async (req, res, next) => {
|
|
567
|
+
try {
|
|
568
|
+
await schema.parseAsync({
|
|
569
|
+
body: req.body,
|
|
570
|
+
query: req.query,
|
|
571
|
+
params: req.params,
|
|
572
|
+
});
|
|
573
|
+
next();
|
|
574
|
+
} catch (error) {
|
|
575
|
+
if (error.name === 'ZodError') {
|
|
576
|
+
const message = error.errors.map((e) => e.message).join(', ');
|
|
577
|
+
next(new AppError(message, 400));
|
|
578
|
+
} else {
|
|
579
|
+
next(error);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
module.exports = { validate };`;
|
|
586
|
+
|
|
587
|
+
await fs.writeFile(path.join(middlewarePath, `validation.${ext}`), validationMiddleware);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async function generateUtilityFiles(utilsPath, config, ext, isTS) {
|
|
592
|
+
// Response helper
|
|
593
|
+
const responseHelper = isTS
|
|
594
|
+
? `import { Response } from 'express';
|
|
595
|
+
|
|
596
|
+
export function sendSuccess(res: Response, data: any, message: string = 'Success', statusCode: number = 200): void {
|
|
597
|
+
res.status(statusCode).json({
|
|
598
|
+
success: true,
|
|
599
|
+
message,
|
|
600
|
+
data,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export function sendError(res: Response, message: string, statusCode: number = 500): void {
|
|
605
|
+
res.status(statusCode).json({
|
|
606
|
+
success: false,
|
|
607
|
+
message,
|
|
608
|
+
});
|
|
609
|
+
}`
|
|
610
|
+
: `function sendSuccess(res, data, message = 'Success', statusCode = 200) {
|
|
611
|
+
res.status(statusCode).json({
|
|
612
|
+
success: true,
|
|
613
|
+
message,
|
|
614
|
+
data,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function sendError(res, message, statusCode = 500) {
|
|
619
|
+
res.status(statusCode).json({
|
|
620
|
+
success: false,
|
|
621
|
+
message,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
module.exports = { sendSuccess, sendError };`;
|
|
626
|
+
|
|
627
|
+
await fs.writeFile(path.join(utilsPath, `response.${ext}`), responseHelper);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async function generateControllers(controllersPath, config, ext, isTS) {
|
|
631
|
+
const healthController = isTS
|
|
632
|
+
? `import { Request, Response, NextFunction } from 'express';
|
|
633
|
+
import { sendSuccess } from '../utils/response';
|
|
634
|
+
|
|
635
|
+
export async function getHealth(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
636
|
+
try {
|
|
637
|
+
sendSuccess(res, {
|
|
638
|
+
status: 'healthy',
|
|
639
|
+
timestamp: new Date().toISOString(),
|
|
640
|
+
uptime: process.uptime(),
|
|
641
|
+
}, 'Service is healthy');
|
|
642
|
+
} catch (error) {
|
|
643
|
+
next(error);
|
|
644
|
+
}
|
|
645
|
+
}`
|
|
646
|
+
: `const { sendSuccess } = require('../utils/response');
|
|
647
|
+
|
|
648
|
+
async function getHealth(req, res, next) {
|
|
649
|
+
try {
|
|
650
|
+
sendSuccess(res, {
|
|
651
|
+
status: 'healthy',
|
|
652
|
+
timestamp: new Date().toISOString(),
|
|
653
|
+
uptime: process.uptime(),
|
|
654
|
+
}, 'Service is healthy');
|
|
655
|
+
} catch (error) {
|
|
656
|
+
next(error);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
module.exports = { getHealth };`;
|
|
661
|
+
|
|
662
|
+
await fs.writeFile(path.join(controllersPath, `health.controller.${ext}`), healthController);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async function generateRoutes(routesPath, config, ext, isTS) {
|
|
666
|
+
const indexRoutes = isTS
|
|
667
|
+
? `import { Router } from 'express';
|
|
668
|
+
import healthRoutes from './health.routes';
|
|
669
|
+
|
|
670
|
+
const router = Router();
|
|
671
|
+
|
|
672
|
+
router.use('/health', healthRoutes);
|
|
673
|
+
|
|
674
|
+
export default router;`
|
|
675
|
+
: `const express = require('express');
|
|
676
|
+
const healthRoutes = require('./health.routes');
|
|
677
|
+
|
|
678
|
+
const router = express.Router();
|
|
679
|
+
|
|
680
|
+
router.use('/health', healthRoutes);
|
|
681
|
+
|
|
682
|
+
module.exports = router;`;
|
|
683
|
+
|
|
684
|
+
await fs.writeFile(path.join(routesPath, `index.${ext}`), indexRoutes);
|
|
685
|
+
|
|
686
|
+
const healthRoutes = isTS
|
|
687
|
+
? `import { Router } from 'express';
|
|
688
|
+
import { getHealth } from '../controllers/health.controller';
|
|
689
|
+
|
|
690
|
+
const router = Router();
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @swagger
|
|
694
|
+
* /api/health:
|
|
695
|
+
* get:
|
|
696
|
+
* summary: Check API health
|
|
697
|
+
* tags: [Health]
|
|
698
|
+
* responses:
|
|
699
|
+
* 200:
|
|
700
|
+
* description: API is healthy
|
|
701
|
+
*/
|
|
702
|
+
router.get('/', getHealth);
|
|
703
|
+
|
|
704
|
+
export default router;`
|
|
705
|
+
: `const express = require('express');
|
|
706
|
+
const { getHealth } = require('../controllers/health.controller');
|
|
707
|
+
|
|
708
|
+
const router = express.Router();
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* @swagger
|
|
712
|
+
* /api/health:
|
|
713
|
+
* get:
|
|
714
|
+
* summary: Check API health
|
|
715
|
+
* tags: [Health]
|
|
716
|
+
* responses:
|
|
717
|
+
* 200:
|
|
718
|
+
* description: API is healthy
|
|
719
|
+
*/
|
|
720
|
+
router.get('/', getHealth);
|
|
721
|
+
|
|
722
|
+
module.exports = router;`;
|
|
723
|
+
|
|
724
|
+
await fs.writeFile(path.join(routesPath, `health.routes.${ext}`), healthRoutes);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function generateModels(modelsPath, config, ext, isTS) {
|
|
728
|
+
// Generate example model based on database type
|
|
729
|
+
if (config.database === 'mongodb') {
|
|
730
|
+
const userModel = isTS
|
|
731
|
+
? `import mongoose, { Schema, Document } from 'mongoose';
|
|
732
|
+
|
|
733
|
+
export interface IUser extends Document {
|
|
734
|
+
username: string;
|
|
735
|
+
email: string;
|
|
736
|
+
password: string;
|
|
737
|
+
createdAt: Date;
|
|
738
|
+
updatedAt: Date;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const userSchema = new Schema<IUser>(
|
|
742
|
+
{
|
|
743
|
+
username: { type: String, required: true, unique: true },
|
|
744
|
+
email: { type: String, required: true, unique: true },
|
|
745
|
+
password: { type: String, required: true },
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
timestamps: true,
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
export default mongoose.model<IUser>('User', userSchema);`
|
|
753
|
+
: `const mongoose = require('mongoose');
|
|
754
|
+
|
|
755
|
+
const userSchema = new mongoose.Schema(
|
|
756
|
+
{
|
|
757
|
+
username: { type: String, required: true, unique: true },
|
|
758
|
+
email: { type: String, required: true, unique: true },
|
|
759
|
+
password: { type: String, required: true },
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
timestamps: true,
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
module.exports = mongoose.model('User', userSchema);`;
|
|
767
|
+
|
|
768
|
+
await fs.writeFile(path.join(modelsPath, `user.model.${ext}`), userModel);
|
|
769
|
+
} else if (config.database === 'postgresql') {
|
|
770
|
+
const userModel = isTS
|
|
771
|
+
? `import { pool } from '../config/database';
|
|
772
|
+
|
|
773
|
+
export interface IUser {
|
|
774
|
+
id?: number;
|
|
775
|
+
username: string;
|
|
776
|
+
email: string;
|
|
777
|
+
password: string;
|
|
778
|
+
created_at?: Date;
|
|
779
|
+
updated_at?: Date;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export class User {
|
|
783
|
+
static async findById(id: number): Promise<IUser | null> {
|
|
784
|
+
const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
|
|
785
|
+
return result.rows[0] || null;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
static async findByEmail(email: string): Promise<IUser | null> {
|
|
789
|
+
const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
|
|
790
|
+
return result.rows[0] || null;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
static async create(user: IUser): Promise<IUser> {
|
|
794
|
+
const result = await pool.query(
|
|
795
|
+
'INSERT INTO users (username, email, password) VALUES ($1, $2, $3) RETURNING *',
|
|
796
|
+
[user.username, user.email, user.password]
|
|
797
|
+
);
|
|
798
|
+
return result.rows[0];
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
static async update(id: number, updates: Partial<IUser>): Promise<IUser | null> {
|
|
802
|
+
const fields = Object.keys(updates);
|
|
803
|
+
const values = Object.values(updates);
|
|
804
|
+
const setClause = fields.map((field, i) => \`\${field} = $\${i + 2}\`).join(', ');
|
|
805
|
+
|
|
806
|
+
const result = await pool.query(
|
|
807
|
+
\`UPDATE users SET \${setClause}, updated_at = NOW() WHERE id = $1 RETURNING *\`,
|
|
808
|
+
[id, ...values]
|
|
809
|
+
);
|
|
810
|
+
return result.rows[0] || null;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
static async delete(id: number): Promise<boolean> {
|
|
814
|
+
const result = await pool.query('DELETE FROM users WHERE id = $1', [id]);
|
|
815
|
+
return result.rowCount! > 0;
|
|
816
|
+
}
|
|
817
|
+
}`
|
|
818
|
+
: `const { pool } = require('../config/database');
|
|
819
|
+
|
|
820
|
+
class User {
|
|
821
|
+
static async findById(id) {
|
|
822
|
+
const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
|
|
823
|
+
return result.rows[0] || null;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
static async findByEmail(email) {
|
|
827
|
+
const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
|
|
828
|
+
return result.rows[0] || null;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
static async create(user) {
|
|
832
|
+
const result = await pool.query(
|
|
833
|
+
'INSERT INTO users (username, email, password) VALUES ($1, $2, $3) RETURNING *',
|
|
834
|
+
[user.username, user.email, user.password]
|
|
835
|
+
);
|
|
836
|
+
return result.rows[0];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
static async update(id, updates) {
|
|
840
|
+
const fields = Object.keys(updates);
|
|
841
|
+
const values = Object.values(updates);
|
|
842
|
+
const setClause = fields.map((field, i) => \`\${field} = $\${i + 2}\`).join(', ');
|
|
843
|
+
|
|
844
|
+
const result = await pool.query(
|
|
845
|
+
\`UPDATE users SET \${setClause}, updated_at = NOW() WHERE id = $1 RETURNING *\`,
|
|
846
|
+
[id, ...values]
|
|
847
|
+
);
|
|
848
|
+
return result.rows[0] || null;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
static async delete(id) {
|
|
852
|
+
const result = await pool.query('DELETE FROM users WHERE id = $1', [id]);
|
|
853
|
+
return result.rowCount > 0;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
module.exports = User;`;
|
|
858
|
+
|
|
859
|
+
await fs.writeFile(path.join(modelsPath, `user.model.${ext}`), userModel);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
async function generateServices(servicesPath, config, ext, isTS) {
|
|
864
|
+
// Generate a placeholder service file
|
|
865
|
+
const exampleService = isTS
|
|
866
|
+
? `${config.features.includes('logging') ? "import logger from '../config/logger';" : ''}
|
|
867
|
+
|
|
868
|
+
export class ExampleService {
|
|
869
|
+
static async performAction(data: any): Promise<any> {
|
|
870
|
+
${config.features.includes('logging') ? "logger.info('Performing action with data:', data);" : "console.log('Performing action with data:', data);"}
|
|
871
|
+
// Add your business logic here
|
|
872
|
+
return { success: true, data };
|
|
873
|
+
}
|
|
874
|
+
}`
|
|
875
|
+
: `${config.features.includes('logging') ? "const logger = require('../config/logger');" : ''}
|
|
876
|
+
|
|
877
|
+
class ExampleService {
|
|
878
|
+
static async performAction(data) {
|
|
879
|
+
${config.features.includes('logging') ? "logger.info('Performing action with data:', data);" : "console.log('Performing action with data:', data);"}
|
|
880
|
+
// Add your business logic here
|
|
881
|
+
return { success: true, data };
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
module.exports = ExampleService;`;
|
|
886
|
+
|
|
887
|
+
await fs.writeFile(path.join(servicesPath, `example.service.${ext}`), exampleService);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
module.exports = { generateMVCStructure };
|