express-launcher 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/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # Express Launcher
2
+
3
+ Use this detailed CLI tool to scaffold your Express.js applications with modern best practices, TypeScript support, and various integrations.
4
+
5
+ ## Features
6
+
7
+ - **Language Support:** JavaScript or TypeScript
8
+ - **Database Integration:** MongoDB, PostgreSQL, MySQL (with Prisma)
9
+ - **Middleware:** CORS, Helmet, Morgan, Rate Limiter
10
+ - **Error Handling:** Advanced global error handler, `AppError` class, and `asyncHandler` wrapper
11
+ - **Linting:** ESLint configuration (Flat Config, TypeScript support)
12
+ - **Structure:** MVC pattern or Modular structure (routes, controllers, utils, middlewares)
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install -g express-launcher
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Run the following command to start the interactive generator:
23
+
24
+ ```bash
25
+ express-launcher [project-name]
26
+ ```
27
+
28
+ Or just:
29
+
30
+ ```bash
31
+ express-launcher
32
+ ```
33
+
34
+ Follow the prompts to configure your project.
35
+
36
+ ## Generated Project Structure
37
+
38
+ ```
39
+ my-app/
40
+ ├── src/
41
+ │ ├── controllers/
42
+ │ ├── lib/ # Database connection / Prisma client
43
+ │ ├── middlewares/ # Error handling, rate limiting
44
+ │ ├── routes/
45
+ │ ├── utils/ # AppError, asyncHandler
46
+ │ └── server.js (or index.ts)
47
+ ├── prisma/ # If SQL DB selected
48
+ ├── .env
49
+ ├── package.json
50
+ └── README.md
51
+ ```
52
+
53
+ ## License
54
+
55
+ MIT © Narasimha
package/bin/index.js ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ const { Command } = require('commander');
5
+ const inquirer = require('inquirer');
6
+ const chalk = require('chalk');
7
+ const { generateProject } = require('../lib/generator');
8
+ const packageJson = require('../package.json');
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .version(packageJson.version)
14
+ .description('A CLI to generate Express.js projects')
15
+ .argument('[project-name]', 'Name of the project directory')
16
+ .action(async (projectName) => {
17
+ let answers = { name: projectName };
18
+
19
+ if (!projectName) {
20
+ const nameAnswer = await inquirer.prompt([
21
+ {
22
+ type: 'input',
23
+ name: 'name',
24
+ message: 'What is the name of your project?',
25
+ default: 'my-express-app',
26
+ validate: (input) => input.trim() !== '' || 'Project name cannot be empty.'
27
+ }
28
+ ]);
29
+ answers.name = nameAnswer.name;
30
+ }
31
+
32
+ const config = await inquirer.prompt([
33
+ {
34
+ type: 'confirm',
35
+ name: 'useSrc',
36
+ message: 'Would you like to organize your project inside a src directory?',
37
+ default: true
38
+ },
39
+ {
40
+ type: 'list',
41
+ name: 'language',
42
+ message: 'Which language would you like to use?',
43
+ choices: ['JavaScript', 'TypeScript'],
44
+ default: 'JavaScript'
45
+ },
46
+ {
47
+ type: 'confirm',
48
+ name: 'useAlias',
49
+ message: 'Would you like to configure "@/” as an alias for the src directory?',
50
+ default: true,
51
+ when: (answers) => answers.useSrc
52
+ },
53
+ {
54
+ type: 'list',
55
+ name: 'database',
56
+ message: 'Which database would you like to set up?',
57
+ choices: ['None', 'MongoDB', 'PostgreSQL', 'MySQL'],
58
+ default: 'None'
59
+ },
60
+ {
61
+ type: 'confirm',
62
+ name: 'cors',
63
+ message: 'Would you like to enable CORS support?',
64
+ default: true
65
+ },
66
+ {
67
+ type: 'confirm',
68
+ name: 'helmet',
69
+ message: 'Would you like to add Helmet for security best practices?',
70
+ default: true
71
+ },
72
+ {
73
+ type: 'confirm',
74
+ name: 'morgan',
75
+ message: 'Would you like to enable Morgan for request logging?',
76
+ default: true
77
+ },
78
+ {
79
+ type: 'confirm',
80
+ name: 'errorHandler',
81
+ message: 'Would you like to include a centralized error handler?',
82
+ default: true
83
+ },
84
+ {
85
+ type: 'confirm',
86
+ name: 'rateLimiter',
87
+ message: 'Would you like to add rate limiter?',
88
+ default: true
89
+ },
90
+ {
91
+ type: 'confirm',
92
+ name: 'eslint',
93
+ message: 'Would you like to set up ESLint for linting?',
94
+ default: true
95
+ }
96
+ ]);
97
+
98
+
99
+ const finalConfig = { ...answers, ...config };
100
+
101
+ console.log(chalk.blue(`\nGenerating project in ./${finalConfig.name}...\n`));
102
+
103
+ try {
104
+ await generateProject(finalConfig);
105
+ console.log(chalk.green('\nSuccess! Your project is ready.'));
106
+ console.log(chalk.white(`\ncd ${finalConfig.name}`));
107
+ console.log(chalk.white('npm install'));
108
+ console.log(chalk.white(finalConfig.language === 'TypeScript' ? 'npm run dev' : 'npm start\n'));
109
+ } catch (error) {
110
+ console.error(chalk.red('Error generating project:'), error.message);
111
+ process.exit(1);
112
+ }
113
+ });
114
+
115
+ program.parse(process.argv);
@@ -0,0 +1,444 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ async function generateProject(config) {
6
+ const { name, useSrc, language, useAlias, database, cors, helmet, morgan, errorHandler, rateLimiter, eslint } = config;
7
+ const isTs = language === 'TypeScript';
8
+ const rootDir = path.resolve(process.cwd(), name);
9
+ const srcDir = useSrc ? path.join(rootDir, 'src') : rootDir;
10
+
11
+ // Folders to create
12
+ const folders = ['routes', 'controllers', 'utils', 'lib', 'middlewares', '../tests'];
13
+ if (database === 'PostgreSQL' || database === 'MySQL') {
14
+ folders.push('../prisma');
15
+ }
16
+
17
+ if (database !== 'PostgreSQL' || database !== 'MySQL') {
18
+ folders.push('models');
19
+ }
20
+
21
+ if (fs.existsSync(rootDir)) {
22
+ throw new Error(`Directory ${name} already exists.`);
23
+ }
24
+
25
+ console.log(`Creating directory ${name}...`);
26
+ await fs.ensureDir(rootDir);
27
+ if (useSrc) await fs.ensureDir(srcDir);
28
+
29
+ for (const folder of folders) {
30
+ if (folder.startsWith('../')) {
31
+ await fs.ensureDir(path.join(rootDir, folder.replace('../', '')));
32
+ } else {
33
+ await fs.ensureDir(path.join(srcDir, folder));
34
+ }
35
+ }
36
+
37
+ // 1. Generate package.json
38
+ const packageJson = {
39
+ name: name,
40
+ version: '1.0.0',
41
+ description: '',
42
+ main: isTs ? (useSrc ? 'dist/index.js' : 'dist/server.js') : (useSrc ? 'src/server.js' : 'server.js'),
43
+ type: "module",
44
+ scripts: {
45
+ start: isTs ? 'node dist/index.js' : 'node src/server.js',
46
+ dev: isTs ? 'tsx watch src/index.ts' : 'node --watch src/server.js',
47
+ build: isTs ? 'tsc' : undefined,
48
+ ...(eslint ? { "lint": "eslint src", "lint:fix": "eslint src --fix" } : {}),
49
+ ...(isTs ? { "watch": "tsc -w" } : {})
50
+ },
51
+ dependencies: {
52
+ express: '^4.19.2',
53
+ dotenv: '^16.4.5'
54
+ },
55
+ devDependencies: {
56
+ // nodemon replaced by native watch or tsx
57
+ }
58
+ };
59
+
60
+ if (cors) packageJson.dependencies.cors = '^2.8.5';
61
+ if (helmet) packageJson.dependencies.helmet = '^7.1.0';
62
+ if (morgan) packageJson.dependencies.morgan = '^1.10.0';
63
+ if (rateLimiter) packageJson.dependencies['express-rate-limit'] = '^7.2.0';
64
+
65
+ if (database === 'MongoDB') {
66
+ packageJson.dependencies.mongoose = '^8.2.0';
67
+ } else if (database === 'PostgreSQL' || database === 'MySQL') {
68
+ packageJson.dependencies['@prisma/client'] = '^7.3.0';
69
+ packageJson.devDependencies.prisma = '^7.3.0';
70
+
71
+ // Add driver adapters based on database choice
72
+ if (database === 'PostgreSQL') {
73
+ packageJson.dependencies['@prisma/adapter-pg'] = '^7.3.0';
74
+ packageJson.dependencies['pg'] = '^8.11.3';
75
+ if (isTs) packageJson.devDependencies['@types/pg'] = '^8.11.0';
76
+ } else if (database === 'MySQL') {
77
+ packageJson.dependencies['@prisma/adapter-mariadb'] = '^7.3.0';
78
+ }
79
+ }
80
+
81
+ if (isTs) {
82
+ packageJson.devDependencies.typescript = '^5.3.3';
83
+ packageJson.devDependencies.tsx = '^4.7.1';
84
+ packageJson.devDependencies['@types/node'] = '^20.11.19';
85
+ packageJson.devDependencies['@types/express'] = '^4.17.21';
86
+ if (cors) packageJson.devDependencies['@types/cors'] = '^2.8.17';
87
+ if (morgan) packageJson.devDependencies['@types/morgan'] = '^1.9.9';
88
+ }
89
+
90
+ if (eslint) {
91
+ if (isTs) {
92
+ packageJson.devDependencies = {
93
+ ...packageJson.devDependencies,
94
+ "eslint": "9.39.2",
95
+ "globals": "17.1.0",
96
+ "typescript-eslint": "8.53.1",
97
+ "eslint-plugin-import": "2.32.0"
98
+ };
99
+ } else {
100
+ packageJson.devDependencies = {
101
+ ...packageJson.devDependencies,
102
+ "eslint": "9.39.2",
103
+ "@eslint/js": "9.39.2",
104
+ "globals": "17.1.0"
105
+ };
106
+ }
107
+ }
108
+
109
+ await fs.writeJson(path.join(rootDir, 'package.json'), packageJson, { spaces: 2 });
110
+
111
+ // 2. Generate tsconfig.json (if TS)
112
+ if (isTs) {
113
+ const tsConfig = {
114
+ compilerOptions: {
115
+ target: 'ES2022',
116
+ module: 'NodeNext',
117
+ moduleResolution: 'NodeNext',
118
+ outDir: './dist',
119
+ strict: true,
120
+ esModuleInterop: true,
121
+ skipLibCheck: true,
122
+ forceConsistentCasingInFileNames: true
123
+ },
124
+ exclude: ["prisma.config.ts"] // Exclude config file from build to satisfy rootDir: ./src
125
+ };
126
+ if (useSrc) tsConfig.compilerOptions.rootDir = './src';
127
+ // if (useSrc) tsConfig.include = ["src/**/*"]; // Optional: be explicit
128
+ if (useAlias) {
129
+ tsConfig.compilerOptions.baseUrl = '.';
130
+ tsConfig.compilerOptions.paths = { "@/*": ["src/*"] };
131
+ }
132
+ await fs.writeJson(path.join(rootDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
133
+ }
134
+
135
+ // 3. Generate ESLint config
136
+ if (eslint) {
137
+
138
+
139
+ const eslintConfigContent = isTs
140
+ ? `import globals from "globals";
141
+ import pluginJs from "@eslint/js";
142
+ import tseslint from "typescript-eslint";
143
+
144
+ /** @type {import('eslint').Linter.Config[]} */
145
+ export default [
146
+ {files: ["**/*.{js,mjs,cjs,ts}"]},
147
+ {languageOptions: { globals: globals.node }},
148
+ pluginJs.configs.recommended,
149
+ ...tseslint.configs.recommended,
150
+ ];`
151
+ : `import globals from "globals";
152
+ import pluginJs from "@eslint/js";
153
+
154
+ /** @type {import('eslint').Linter.Config[]} */
155
+ export default [
156
+ {files: ["**/*.{js,mjs,cjs}"]},
157
+ {languageOptions: { globals: globals.node }},
158
+ pluginJs.configs.recommended,
159
+ ];`;
160
+
161
+ await fs.writeFile(path.join(rootDir, 'eslint.config.mjs'), eslintConfigContent);
162
+
163
+ // Remove old logic if any (the logic above replaced the old devDependencies block part,
164
+ // but we need to ensure we don't duplicate or leave old file writes steps if they were separate.
165
+ // The original code had separate steps for package.json deps (lines 90-98) and config write (127-139).
166
+ // I am replacing the config write block here (127-139) but I also need to handle the deps which were at 90-98.
167
+ // ideally I should have targeted the deps block too.
168
+ // Let's do a better replace plan.
169
+ }
170
+
171
+ // 4. Generate Database Config
172
+ if (database === 'MongoDB') {
173
+ const dbFile = isTs ? 'db.ts' : 'db.js';
174
+ let dbContent = '';
175
+ if (isTs) {
176
+ dbContent = `import mongoose from 'mongoose';\n\nconst connectDB = async () => {\n try {\n await mongoose.connect(process.env.MONGO_URI as string);\n console.log('MongoDB Connected');\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n};\n\nexport default connectDB;`;
177
+ } else {
178
+ dbContent = `import mongoose from 'mongoose';\n\nconst connectDB = async () => {\n try {\n await mongoose.connect(process.env.MONGO_URI);\n console.log('MongoDB Connected');\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n};\n\nexport default connectDB;`;
179
+ }
180
+ await fs.writeFile(path.join(srcDir, 'lib', dbFile), dbContent);
181
+ } else if (database === 'PostgreSQL' || database === 'MySQL') {
182
+ // Prisma client initialization with driver adapters
183
+ const prismaClientFile = isTs ? 'prisma.ts' : 'prisma.js';
184
+ let prismaContent = '';
185
+
186
+ if (database === 'PostgreSQL') {
187
+ if (isTs) {
188
+ prismaContent = `import 'dotenv/config';\nimport { Pool } from 'pg';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { PrismaClient } from '@prisma/client';\n\nconst connectionString = process.env.DATABASE_URL;\n\n// Using Pool is standard for @prisma/adapter-pg to allow connection management\nconst pool = new Pool({ connectionString });\nconst adapter = new PrismaPg(pool);\nconst prisma = new PrismaClient({ adapter });\n\nexport default prisma;`;
189
+ } else {
190
+ prismaContent = `import 'dotenv/config';\nimport pg from 'pg';\nimport { PrismaPg } from '@prisma/adapter-pg';\nimport { PrismaClient } from '@prisma/client';\n\nconst { Pool } = pg;\nconst connectionString = process.env.DATABASE_URL;\n\nconst pool = new Pool({ connectionString });\nconst adapter = new PrismaPg(pool);\nconst prisma = new PrismaClient({ adapter });\n\nexport default prisma;`;
191
+ }
192
+ } else if (database === 'MySQL') {
193
+ if (isTs) {
194
+ prismaContent = `import 'dotenv/config';\nimport { PrismaMariaDb } from '@prisma/adapter-mariadb';\nimport { PrismaClient } from '@prisma/client';\n\nconst adapter = new PrismaMariaDb({\n host: process.env.DATABASE_HOST || 'localhost',\n user: process.env.DATABASE_USER || 'root',\n password: process.env.DATABASE_PASSWORD || '',\n database: process.env.DATABASE_NAME || '${name}',\n connectionLimit: 5\n});\n\nconst prisma = new PrismaClient({ adapter });\n\nexport default prisma;`;
195
+ } else {
196
+ prismaContent = `import 'dotenv/config';\nimport { PrismaMariaDb } from '@prisma/adapter-mariadb';\nimport { PrismaClient } from '@prisma/client';\n\nconst adapter = new PrismaMariaDb({\n host: process.env.DATABASE_HOST || 'localhost',\n user: process.env.DATABASE_USER || 'root',\n password: process.env.DATABASE_PASSWORD || '',\n database: process.env.DATABASE_NAME || '${name}',\n connectionLimit: 5\n});\n\nconst prisma = new PrismaClient({ adapter });\n\nexport default prisma;`;
197
+ }
198
+ }
199
+
200
+ await fs.writeFile(path.join(srcDir, 'lib', prismaClientFile), prismaContent);
201
+
202
+ // Generate prisma.config.ts/js
203
+ if (isTs) {
204
+ const prismaConfig = `import 'dotenv/config'\nimport { defineConfig, env } from 'prisma/config'\n\nexport default defineConfig({\n schema: 'prisma/schema.prisma',\n migrations: {\n path: 'prisma/migrations',\n },\n datasource: {\n url: env('DATABASE_URL'),\n },\n})\n`;
205
+ await fs.writeFile(path.join(rootDir, 'prisma.config.ts'), prismaConfig);
206
+ } else {
207
+ const prismaConfig = `import 'dotenv/config';\nimport { defineConfig, env } from 'prisma/config';\n\nexport default defineConfig({\n schema: 'prisma/schema.prisma',\n migrations: {\n path: 'prisma/migrations',\n },\n datasource: {\n url: env('DATABASE_URL'),\n },\n});\n`;
208
+ await fs.writeFile(path.join(rootDir, 'prisma.config.js'), prismaConfig);
209
+ }
210
+
211
+ // Generate schema.prisma (no url)
212
+ const provider = database === 'PostgreSQL' ? 'postgresql' : 'mysql';
213
+ const schema = `datasource db {\n provider = "${provider}"\n}\n\ngenerator client {\n provider = "prisma-client-js"\n }\n\n// Add your models here\n// Example:\n// model User {\n// id Int @id @default(autoincrement())\n// email String @unique\n// name String?\n// }\n`;
214
+ await fs.writeFile(path.join(rootDir, 'prisma', 'schema.prisma'), schema);
215
+ }
216
+
217
+ // 4.5 Generate Error Handling Files
218
+ if (errorHandler) {
219
+ // AppError
220
+ const appErrorFile = isTs ? 'AppError.ts' : 'AppError.js';
221
+ const appErrorContent = isTs
222
+ ? `export class AppError extends Error {
223
+ public statusCode: number;
224
+ public status: string;
225
+ public isOperational: boolean;
226
+
227
+ constructor(message: string, statusCode: number) {
228
+ super(message);
229
+ this.statusCode = statusCode;
230
+ this.status = \`\${statusCode}\`.startsWith('4') ? 'fail' : 'error';
231
+ this.isOperational = true;
232
+
233
+ Error.captureStackTrace(this, this.constructor);
234
+ }
235
+ }`
236
+ : `export class AppError extends Error {
237
+ constructor(message, statusCode) {
238
+ super(message);
239
+ this.statusCode = statusCode;
240
+ this.status = \`\${statusCode}\`.startsWith('4') ? 'fail' : 'error';
241
+ this.isOperational = true;
242
+
243
+ Error.captureStackTrace(this, this.constructor);
244
+ }
245
+ }`;
246
+ await fs.writeFile(path.join(srcDir, 'utils', appErrorFile), appErrorContent);
247
+
248
+ // AsyncHandler
249
+ const asyncHandlerFile = isTs ? 'asyncHandler.ts' : 'asyncHandler.js';
250
+ const asyncHandlerContent = isTs
251
+ ? `import { Request, Response, NextFunction } from 'express';
252
+
253
+ export const asyncHandler = (fn: Function) => {
254
+ return (req: Request, res: Response, next: NextFunction) => {
255
+ fn(req, res, next).catch(next);
256
+ };
257
+ };`
258
+ : `export const asyncHandler = (fn) => {
259
+ return (req, res, next) => {
260
+ fn(req, res, next).catch(next);
261
+ };
262
+ };`;
263
+ await fs.writeFile(path.join(srcDir, 'utils', asyncHandlerFile), asyncHandlerContent);
264
+
265
+ // Global Error Middleware
266
+ const errorMiddlewareFile = isTs ? 'error.ts' : 'error.js';
267
+ const errorMiddlewareContent = isTs
268
+ ? `import { Request, Response, NextFunction } from 'express';
269
+ import { AppError } from '../utils/AppError${useAlias ? '' : '.js'}';
270
+
271
+ export const globalErrorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
272
+ err.statusCode = err.statusCode || 500;
273
+ err.status = err.status || 'error';
274
+
275
+ if (process.env.NODE_ENV === 'development') {
276
+ res.status(err.statusCode).json({
277
+ status: err.status,
278
+ error: err,
279
+ message: err.message,
280
+ stack: err.stack,
281
+ });
282
+ } else {
283
+ // Production
284
+ if (err.isOperational) {
285
+ res.status(err.statusCode).json({
286
+ status: err.status,
287
+ message: err.message,
288
+ });
289
+ } else {
290
+ // Programming or other unknown error
291
+ console.error('ERROR 💥', err);
292
+ res.status(500).json({
293
+ status: 'error',
294
+ message: 'Something went very wrong!',
295
+ });
296
+ }
297
+ }
298
+ };`
299
+ : `import { AppError } from '../utils/AppError.js';
300
+
301
+ export const globalErrorHandler = (err, req, res, next) => {
302
+ err.statusCode = err.statusCode || 500;
303
+ err.status = err.status || 'error';
304
+
305
+ if (process.env.NODE_ENV === 'development') {
306
+ res.status(err.statusCode).json({
307
+ status: err.status,
308
+ error: err,
309
+ message: err.message,
310
+ stack: err.stack,
311
+ });
312
+ } else {
313
+ // Production
314
+ if (err.isOperational) {
315
+ res.status(err.statusCode).json({
316
+ status: err.status,
317
+ message: err.message,
318
+ });
319
+ } else {
320
+ // Programming or other unknown error
321
+ console.error('ERROR 💥', err);
322
+ res.status(500).json({
323
+ status: 'error',
324
+ message: 'Something went very wrong!',
325
+ });
326
+ }
327
+ }
328
+ };`;
329
+ await fs.writeFile(path.join(srcDir, 'middlewares', errorMiddlewareFile), errorMiddlewareContent);
330
+ }
331
+
332
+ // 5. Generate App Entry
333
+ let appContent = ``;
334
+
335
+ if (isTs) {
336
+ appContent += `import express, { Express, Request, Response, NextFunction } from 'express';\n`;
337
+ appContent += `import dotenv from 'dotenv';\n`;
338
+ if (cors) appContent += `import cors from 'cors';\n`;
339
+ if (helmet) appContent += `import helmet from 'helmet';\n`;
340
+ if (morgan) appContent += `import morgan from 'morgan';\n`;
341
+ if (rateLimiter) appContent += `import rateLimit from 'express-rate-limit';\n`;
342
+ if (database === 'MongoDB') appContent += `import connectDB from './lib/db.js';\n`;
343
+ if (errorHandler) {
344
+ appContent += `import { globalErrorHandler } from './middlewares/error${useAlias ? '' : '.js'}';\n`;
345
+ appContent += `import { AppError } from './utils/AppError${useAlias ? '' : '.js'}';\n`;
346
+ }
347
+ } else {
348
+ appContent += `import express from 'express';\n`;
349
+ appContent += `import dotenv from 'dotenv';\n`;
350
+ if (cors) appContent += `import cors from 'cors';\n`;
351
+ if (helmet) appContent += `import helmet from 'helmet';\n`;
352
+ if (morgan) appContent += `import morgan from 'morgan';\n`;
353
+ if (rateLimiter) appContent += `import rateLimit from 'express-rate-limit';\n`;
354
+ if (database === 'MongoDB') appContent += `import connectDB from './lib/db.js';\n`;
355
+ if (errorHandler) {
356
+ appContent += `import { globalErrorHandler } from './middlewares/error.js';\n`;
357
+ appContent += `import { AppError } from './utils/AppError.js';\n`;
358
+ }
359
+ }
360
+
361
+ appContent += `\ndotenv.config();\n\n`;
362
+ if (database === 'MongoDB') appContent += `connectDB();\n\n`;
363
+
364
+ appContent += isTs ? `const app: Express = express();\n` : `const app = express();\n`;
365
+ appContent += `const port = process.env.PORT || 3000;\n\n`;
366
+
367
+ appContent += `app.use(express.json());\n`;
368
+ appContent += `app.use(express.urlencoded({ extended: true }));\n`;
369
+ if (cors) appContent += `app.use(cors());\n`;
370
+ if (helmet) appContent += `app.use(helmet());\n`;
371
+ if (morgan) appContent += `app.use(morgan('dev'));\n`;
372
+
373
+ if (rateLimiter) {
374
+ appContent += `\nconst limiter = rateLimit({\n windowMs: 15 * 60 * 1000,\n max: 100\n});\n`;
375
+ appContent += `app.use(limiter);\n`;
376
+ }
377
+
378
+ appContent += `\n// Your routes will go here\n`;
379
+ appContent += `// Example: app.use('/api', routes);\n\n`;
380
+
381
+ appContent += `app.get('/', (req${isTs ? ': Request' : ''}, res${isTs ? ': Response' : ''}) => {\n`;
382
+ appContent += ` res.json({ message: 'Welcome to ${name} API' });\n`;
383
+ appContent += `});\n`;
384
+
385
+ appContent += `\napp.all('*', (req, res, next) => {\n`;
386
+ if (errorHandler) {
387
+ appContent += ` throw new AppError(\`Can't find \${req.originalUrl} on this server!\`, 404);\n`;
388
+ } else {
389
+ appContent += ` res.status(404).json({ status: 'fail', message: \`Can't find \${req.originalUrl} on this server!\` });\n`;
390
+ }
391
+ appContent += `});\n`;
392
+
393
+ if (errorHandler) {
394
+ appContent += `\napp.use(globalErrorHandler);\n`;
395
+ }
396
+
397
+ appContent += `\napp.listen(port, () => {\n console.log(\`[server]: Server is running at http://localhost:\${port}\`);\n});\n`;
398
+
399
+ const entryFile = isTs ? 'index.ts' : 'server.js';
400
+ await fs.writeFile(path.join(srcDir, entryFile), appContent);
401
+
402
+ // 6. Create .env
403
+ let envContent = `PORT=3000\nNODE_ENV=development\n`;
404
+ if (database === 'MongoDB') {
405
+ envContent += `MONGO_URI=mongodb://localhost:27017/${name}\n`;
406
+ } else if (database === 'PostgreSQL') {
407
+ envContent += `DATABASE_URL="postgresql://user:password@localhost:5432/${name}?schema=public"\n`;
408
+ } else if (database === 'MySQL') {
409
+ envContent += `DATABASE_URL="mysql://user:password@localhost:3306/${name}"\n`;
410
+ envContent += `DATABASE_HOST="localhost"\n`;
411
+ envContent += `DATABASE_USER="root"\n`;
412
+ envContent += `DATABASE_PASSWORD=""\n`;
413
+ envContent += `DATABASE_NAME="${name}"\n`;
414
+ }
415
+
416
+ await fs.writeFile(path.join(rootDir, '.env'), envContent);
417
+
418
+ // 7. Create .gitignore
419
+ const gitIgnore = `node_modules\ndist\n.env\n`;
420
+ await fs.writeFile(path.join(rootDir, '.gitignore'), gitIgnore);
421
+
422
+ // 8. Create README
423
+ let readme = `# ${name}\n\n`;
424
+ readme += `## Setup\n\n\`\`\`bash\nnpm install\n\`\`\`\n\n`;
425
+
426
+ if (database === 'PostgreSQL' || database === 'MySQL') {
427
+ readme += `## Database Setup\n\n`;
428
+ readme += `1. Update the \`.env\` file with your database credentials\n`;
429
+ readme += `2. Run Prisma migrations:\n\n\`\`\`bash\nnpx prisma migrate dev --name init\n\`\`\`\n\n`;
430
+ readme += `3. Generate Prisma Client:\n\n\`\`\`bash\nnpx prisma generate\n\`\`\`\n\n`;
431
+ }
432
+
433
+ readme += `## Development\n\n\`\`\`bash\nnpm run dev\n\`\`\`\n\n`;
434
+
435
+ if (isTs) {
436
+ readme += `## Build\n\n\`\`\`bash\nnpm run build\n\`\`\`\n\n`;
437
+ }
438
+
439
+ readme += `## Start\n\n\`\`\`bash\nnpm start\n\`\`\`\n`;
440
+
441
+ await fs.writeFile(path.join(rootDir, 'README.md'), readme);
442
+ }
443
+
444
+ module.exports = { generateProject };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "express-launcher",
3
+ "version": "1.0.0",
4
+ "description": "A Simple CLI to generate Express.js projects",
5
+ "main": "bin/index.js",
6
+ "bin": {
7
+ "express-launcher": "./bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "cli",
14
+ "express",
15
+ "generator",
16
+ "scaffold",
17
+ "boilerplate",
18
+ "starter",
19
+ "typescript",
20
+ "javascript",
21
+ "prisma",
22
+ "mongodb",
23
+ "postgresql",
24
+ "mysql",
25
+ "express-generator",
26
+ "create-express-app",
27
+ "backend"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/NarasimhaVaddala/express-launcher.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/NarasimhaVaddala/express-launcher/issues"
35
+ },
36
+ "homepage": "https://github.com/NarasimhaVaddala/express-launcher#readme",
37
+ "author": "Narasimha Vaddala",
38
+ "license": "ISC",
39
+ "dependencies": {
40
+ "chalk": "^4.1.2",
41
+ "commander": "^14.0.2",
42
+ "fs-extra": "^11.3.3",
43
+ "inquirer": "^8.2.7"
44
+ }
45
+ }