UNPKG

express-ignite-cli

Version:

Powerful Express.js CLI to generate boilerplate projects with auth, swagger, testing, and more!

419 lines (357 loc) • 11.1 kB
#!/usr/bin/env node import inquirer from "inquirer"; import fs from "fs"; import path from "path"; import chalk from "chalk"; import { execSync } from "child_process"; import { fileURLToPath } from "url"; import { dirname } from "path"; import { generateAuthModule } from "./generateAuthModule.js"; import { imp, exp } from './helper.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const basePackages = [ "express", "morgan", "multer", "cors", "bcrypt", "jsonwebtoken", "dotenv", "express-rate-limit", "nodemon", "swagger-ui-express", "swagger-jsdoc", "express-validator", "jest", "supertest" ]; function write(filePath, content) { fs.writeFileSync(filePath, content.trim()); console.log(chalk.green("āœ” Created:"), filePath); } function generateSwaggerFile(projectPath, ext, isESM) { const filePath = path.join(projectPath, `configs/swagger.${ext}`); const content = ` ${imp("swaggerJsdoc", "swagger-jsdoc", isESM)} ${imp("swaggerUi", "swagger-ui-express", isESM)} const options = { definition: { openapi: '3.0.0', info: { title: 'Express API with Swagger', version: '1.0.0', }, }, apis: ['./routes/*.${ext}'], }; const specs = swaggerJsdoc(options); const setupSwagger = (app) => { app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); }; ${exp("setupSwagger", isESM)} `; write(filePath, content); } async function main() { console.log(chalk.cyan("\nšŸš€ Welcome to Express App Generator\n")); const { projectName, language, moduleSystem, db, orm } = await inquirer.prompt([ { name: "projectName", message: "šŸ“ Project name:", validate: (val) => !!val || "Project name is required", }, { type: "list", name: "language", // message: "šŸ“¦ Choose language:", message: "šŸ”§ Currently we have an issue with TypeScript, so we are working on it. Please choose JavaScript for now.", choices: ["JavaScript", "TypeScript"], }, { type: "list", name: "moduleSystem", message: "šŸ› ļø Choose module system:", choices: [ { name: "ECMAScript Modules (import/export)", value: "esm" }, { name: "CommonJS (require/module.exports)", value: "cjs" }, ], }, { type: "list", name: "db", message: "šŸ—ƒļø Choose a database:", choices: ["MongoDB", "MySQL", "PostgreSQL"], }, { type: "list", name: "orm", message: "šŸ”§ Choose ORM for SQL DB:", when: (answers) => answers.db !== "MongoDB", choices: ["Sequelize", "Prisma", "TypeORM"], }, ]); const isESM = moduleSystem === "esm"; const isTS = language === "TypeScript"; const ext = isTS ? "ts" : "js"; const projectPath = path.join(process.cwd(), projectName); console.log(chalk.green(`\nšŸ“ Creating project: ${projectName}\n`)); fs.mkdirSync(projectPath); process.chdir(projectPath); execSync("npm init -y", { stdio: "inherit" }); // Base packages execSync(`npm install ${basePackages.join(" ")}`, { stdio: "inherit" }); // DB Packages if (db === "MongoDB") { execSync("npm install mongoose", { stdio: "inherit" }); } else { if (orm === "Sequelize") { const driver = db === "MySQL" ? "mysql2" : "pg pg-hstore"; execSync(`npm install sequelize ${driver}`, { stdio: "inherit" }); } else if (orm === "Prisma") { execSync("npm install prisma @prisma/client", { stdio: "inherit" }); execSync("npx prisma init", { stdio: "inherit" }); } else if (orm === "TypeORM") { const driver = db === "MySQL" ? "mysql2" : "pg"; execSync(`npm install typeorm reflect-metadata ${driver}`, { stdio: "inherit", }); } } if (isTS) { // execSync( // "npm install -D typescript @types/express @types/node ts-node-dev", // { // stdio: "inherit", // } // ); execSync( "npm install -D typescript ts-node-dev @types/node @types/express @types/morgan @types/multer @types/cors @types/bcrypt @types/jsonwebtoken @types/express-rate-limit @types/express-validator @types/jest @types/supertest", { stdio: "inherit" } ); execSync("npx tsc --init", { stdio: "inherit" }); const tsConfigPath = path.join(projectPath, "tsconfig.json"); fs.writeFileSync( tsConfigPath, JSON.stringify( { compilerOptions: { target: "ES2020", module: isESM ? "ESNext" : "CommonJS", moduleResolution: isESM ? "NodeNext" : "Node", esModuleInterop: true, forceConsistentCasingInFileNames: true, strict: true, skipLibCheck: true, outDir: "dist", resolveJsonModule: true, }, include: ["**/*.ts"], }, null, 2 ) ); } // Folders const folders = [ "controllers", "services", "routes", "middlewares", "utils", "validations", "configs", "constants", "models", ]; folders.forEach((folder) => fs.mkdirSync(path.join(projectPath, folder))); // .env let dbUri = "DB_URI=your_db_uri"; if (db === "MongoDB") { dbUri = "DB_URI=mongodb://localhost:27017/your_db_name"; } else if (db === "MySQL") { dbUri = "DB_URI=mysql://username:password@localhost:3306/your_db_name"; } else if (db === "PostgreSQL") { dbUri = "DB_URI=postgresql://username:password@localhost:5432/your_db_name"; } fs.writeFileSync(path.join(projectPath, ".env"), `PORT=5000\nJWT_SECRET=your_secret\n${dbUri}`); // app.(js|ts) fs.writeFileSync( path.join(projectPath, `app.${ext}`), ` ${imp("express", "express", isESM)} ${imp("dotenv", "dotenv", isESM)} ${imp("cors", "cors", isESM)} ${imp("rateLimit", "express-rate-limit", isESM)} ${imp("errorHandler", `./middlewares/errorHandler.${ext}`, isESM)} ${imp("setupSwagger", `./configs/swagger.${ext}`, isESM, true)} ${imp("authRoutes", `./routes/authRoutes.${ext}`, isESM)} dotenv.config(); const app = express(); app.use(express.json()); app.use(cors()); app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 })); // uncomment this line if you want to use db after adding the key in .env // connectDb(); setupSwagger(app); app.get('/', (req, res) => res.send('API is running...')); app.use("/api/auth", authRoutes); app.use(errorHandler); const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(\`šŸš€ Server running on port \${PORT}\`)); `.trim() ); // authMiddleware fs.writeFileSync( path.join(projectPath, `middlewares/authMiddleware.${ext}`), ` const protect = (req, res, next) => { // Add your JWT auth logic here next(); }; ${exp("protect", isESM)} `.trim() ); // generateToken fs.writeFileSync( path.join(projectPath, `utils/generateToken.${ext}`), ` ${imp("jwt", "jsonwebtoken", isESM)} const generateToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d', }); }; ${exp("generateToken", isESM)} `.trim() ); // passwords fs.writeFileSync( path.join(projectPath, `utils/passwords.${ext}`), ` ${imp("bcrypt", "bcrypt", isESM)} const hashPassword = async (password) => await bcrypt.hash(password, 10); const comparePasswords = async (plain, hashed) => await bcrypt.compare(plain, hashed); ${exp(["hashPassword","comparePasswords"], isESM)} `.trim() ); // connectDb let connectDbCode = ""; if (db === "MongoDB") { connectDbCode = ` ${imp("mongoose", "mongoose", isESM)} const connectDb = async () => { try { await mongoose.connect(process.env.DB_URI); console.log('MongoDB Connected'); } catch (err) { console.error(err); process.exit(1); } }; ${exp("connectDb", isESM)} `.trim(); } else if (orm === "Sequelize") { connectDbCode = ` ${imp("Sequelize", "sequelize", isESM)} ${imp("dotenv", "dotenv", isESM)} dotenv.config(); const sequelize = new Sequelize(process.env.DB_URI); ${exp("sequelize", isESM)} `.trim(); } else if (orm === "Prisma") { connectDbCode = ` ${imp("PrismaClient", "@prisma/client", isESM, true)} const prisma = new PrismaClient(); ${exp("prisma", isESM)} `.trim(); } else if (orm === "TypeORM") { connectDbCode = ` ${imp("DataSource", "typeorm", isESM)} ${imp("dotenv", "dotenv", isESM)} dotenv.config(); const AppDataSource = new DataSource({ type: '${db.toLowerCase()}', url: process.env.DB_URI, entities: [], synchronize: true, }); ${exp("AppDataSource", isESM)} `.trim(); } fs.writeFileSync( path.join(projectPath, `configs/connectDb.${ext}`), connectDbCode ); // āœ… Generate Swagger File generateSwaggerFile(projectPath, ext, isESM); console.log(chalk.green("\nāœ… Express app created successfully!\n")); console.log(chalk.blue(`šŸ‘‰ cd ${projectName} && npm run dev (or start)`)); console.log(chalk.yellow("\n🧠 Initializing Git...")); execSync("git init", { stdio: "inherit" }); console.log(chalk.yellow("šŸ“„ Creating .gitignore...")); fs.writeFileSync( path.join(projectPath, ".gitignore"), ` node_modules .env dist build .DS_Store`.trim() ); console.log(chalk.yellow("šŸ“„ Creating README.md...")); fs.writeFileSync( path.join(projectPath, "README.md"), ` # ${projectName} Generated with \`create-express-cli\` šŸš€ ## šŸ“¦ Stack - Language: ${language} - Database: ${db} - ${db !== "MongoDB" ? `ORM: ${orm}` : "ODM: Mongoose"} ## šŸ“ Project Structure \`\`\` . ā”œā”€ā”€ app.${ext} ā”œā”€ā”€ controllers/ ā”œā”€ā”€ services/ ā”œā”€ā”€ routes/ ā”œā”€ā”€ middlewares/ ā”œā”€ā”€ utils/ ā”œā”€ā”€ configs/ ā”œā”€ā”€ validations/ ā”œā”€ā”€ constants/ ā”œā”€ā”€ models/ └── .env \`\`\` ## šŸš€ Scripts \`\`\`bash npm run dev # Start dev server npm run build # TypeScript build (TS only) npm start # Start production server \`\`\` ## šŸ“– Docs Visit: http://localhost:5000/api-docs `.trim() ); console.log(chalk.yellow("šŸ”§ Adding scripts to package.json...")); const pkgPath = path.join(projectPath, "package.json"); const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); pkg.scripts = { dev: isTS ? isESM ? "ts-node-dev --respawn --loader ts-node/esm app.ts" : "ts-node-dev --respawn app.ts" : "nodemon app.js", ...(isTS ? { build: "tsc" } : {}), start: isTS ? "node dist/app.js" : "node app.js", test: "jest", }; pkg.type = isESM ? "module" : "commonjs"; // if (isESM) pkg.type = "module"; fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); console.log(chalk.yellow("\nšŸ”‘ Generating Auth Module...")); generateAuthModule(projectPath, isTS, db, orm, isESM); } main();