UNPKG

@bodheesh/create-bodhi-node-app

Version:

Create a production-ready Node.js REST API with zero configuration

438 lines (376 loc) 13.5 kB
#!/usr/bin/env node const { Command } = require('commander'); const chalk = require('chalk'); const inquirer = require('inquirer'); const fs = require('fs-extra'); const path = require('path'); const ora = require('ora'); // ASCII art for Bodhi brand const bodhiAsciiArt = ` ╔══╗─────╔╗──╔╗╔═╗ ║╔╗║─────║║──║║║╔╝ ║╚╝╚╦══╦═╝╠══╣║║╚╗ ║╔═╗║╔╗║╔╗║║═╣║║╔╝ ║╚═╝║╚╝║╚╝║║═╣╚╣║ ╚═══╩══╩══╩══╩═╩╝ `; // Brand colors const brandColor = '#FF6B6B'; // You can change this to your brand color // Simple project name validation function validateProjectName(name) { if (!name) { return { valid: false, error: 'Project name cannot be empty' }; } if (!/^[a-zA-Z0-9-_]+$/.test(name)) { return { valid: false, error: 'Project name can only contain letters, numbers, hyphens and underscores' }; } return { valid: true }; } const program = new Command(); program .name('bodhi') .description(chalk.hex(brandColor)(bodhiAsciiArt) + '\nBodhi CLI - Create modern Node.js applications with best practices') .version('1.0.0'); program .command('create') .description('Create a new Bodhi-powered Node.js application') .argument('<project-name>', 'Project name') .option('-t, --template <template>', 'Project template (rest-api, graphql-api, mvc)', 'rest-api') .option('-d, --database <database>', 'Database to use (mongodb, postgres, mysql)', 'mongodb') .option('--typescript', 'Use TypeScript instead of JavaScript', false) .action(async (projectName, options) => { try { // Validate project name const nameValidation = validateProjectName(projectName); if (!nameValidation.valid) { console.error(chalk.red('Invalid project name:')); console.error(chalk.red(` * ${nameValidation.error}`)); process.exit(1); } // Create project directory (in current directory) const root = path.resolve(process.cwd(), projectName); if (fs.existsSync(root)) { console.error(chalk.red(`Directory ${projectName} already exists!`)); process.exit(1); } fs.ensureDirSync(root); console.log(); console.log(chalk.hex(brandColor)(bodhiAsciiArt)); console.log(chalk.blue(`Creating a new Bodhi-powered Node.js app in ${chalk.green(root)}`)); console.log(); // Prompt for additional options if not provided if (!options.template || !options.database) { const answers = await inquirer.prompt([ { type: 'list', name: 'template', message: 'Choose a template:', default: 'rest-api', choices: [ { name: 'REST API', value: 'rest-api' }, { name: 'GraphQL API', value: 'graphql-api' }, { name: 'MVC Application', value: 'mvc' } ] }, { type: 'list', name: 'database', message: 'Choose a database:', default: 'mongodb', choices: [ { name: 'MongoDB', value: 'mongodb' }, { name: 'PostgreSQL', value: 'postgres' }, { name: 'MySQL', value: 'mysql' } ] }, { type: 'confirm', name: 'typescript', message: 'Would you like to use TypeScript?', default: false } ]); Object.assign(options, answers); } // Create package.json const spinner = ora('Creating project files...').start(); const packageJson = { name: projectName, version: '0.1.0', private: true, description: 'A modern Node.js application powered by Bodhi', scripts: { start: 'node src/index.js', dev: 'nodemon src/index.js', test: 'jest' }, dependencies: { express: '^4.18.2', 'dotenv': '^16.3.1', cors: '^2.8.5', helmet: '^7.1.0', 'bodhi-form-validations': 'latest', bcryptjs: '^2.4.3', jsonwebtoken: '^9.0.2' }, devDependencies: { nodemon: '^3.0.2', jest: '^29.7.0' }, keywords: ['bodhi', 'node', 'api', 'rest', 'mongodb'], author: 'Created with Bodhi CLI' }; // Add database dependencies if (options.database === 'mongodb') { packageJson.dependencies.mongoose = '^8.0.3'; } else if (options.database === 'postgres') { packageJson.dependencies.pg = '^8.11.3'; packageJson.dependencies.sequelize = '^6.35.2'; } else if (options.database === 'mysql') { packageJson.dependencies.mysql2 = '^3.6.5'; packageJson.dependencies.sequelize = '^6.35.2'; } // Add TypeScript if selected if (options.typescript) { packageJson.devDependencies = { ...packageJson.devDependencies, typescript: '^5.3.3', '@types/node': '^20.10.6', '@types/express': '^4.17.21', 'ts-node': '^10.9.2', 'ts-jest': '^29.1.1' }; packageJson.scripts.start = 'ts-node src/index.ts'; packageJson.scripts.dev = 'nodemon --exec ts-node src/index.ts'; packageJson.scripts.build = 'tsc'; } // Write package.json fs.writeFileSync( path.join(root, 'package.json'), JSON.stringify(packageJson, null, 2) ); // Create src directory structure const srcDir = path.join(root, 'src'); fs.ensureDirSync(srcDir); // Create organized directory structure const dirs = [ 'controllers/auth', 'controllers/user', 'middleware/auth', 'middleware/validation', 'models', 'routes/auth', 'routes/user', 'utils', 'config' ]; dirs.forEach(dir => { fs.ensureDirSync(path.join(srcDir, dir)); }); // Copy template files with new structure const templateDir = path.join(__dirname, 'templates', options.template, 'src'); if (fs.existsSync(templateDir)) { // Copy main files fs.copyFileSync( path.join(templateDir, 'index.js'), path.join(srcDir, 'index.js') ); // Create config file const configContent = `module.exports = { jwt: { secret: process.env.JWT_SECRET || 'your-secret-key', expiresIn: process.env.JWT_EXPIRES_IN || '1d' }, db: { url: process.env.MONGODB_URI || 'mongodb://localhost:27017/${projectName}' }, api: { prefix: process.env.API_PREFIX || '/api/v1' } };`; fs.writeFileSync(path.join(srcDir, 'config/index.js'), configContent); // Create utils file const utilsContent = `const jwt = require('jsonwebtoken'); const config = require('../config'); exports.generateToken = (userId) => { return jwt.sign({ id: userId }, config.jwt.secret, { expiresIn: config.jwt.expiresIn }); }; exports.verifyToken = (token) => { return jwt.verify(token, config.jwt.secret); }; exports.catchAsync = (fn) => { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; };`; fs.writeFileSync(path.join(srcDir, 'utils/index.js'), utilsContent); } else { console.error(chalk.red(`Template directory not found: ${templateDir}`)); process.exit(1); } // Create .env file const envFile = path.join(root, '.env'); fs.writeFileSync(envFile, generateEnvFile(options, projectName)); // Create .gitignore const gitignore = path.join(root, '.gitignore'); fs.writeFileSync(gitignore, 'node_modules\n.env\ndist\ncoverage\n.DS_Store\n'); // Create README.md with Bodhi branding const readmeContent = `# ${projectName} ${bodhiAsciiArt} A modern Node.js application powered by Bodhi CLI. ## Project Structure \`\`\` ${projectName}/ ├── src/ │ ├── controllers/ # Route controllers │ │ ├── auth/ # Authentication controllers │ │ └── user/ # User management controllers │ ├── middleware/ # Custom middleware │ │ ├── auth/ # Authentication middleware │ │ └── validation/# Request validation middleware │ ├── models/ # Database models │ ├── routes/ # API routes │ │ ├── auth/ # Authentication routes │ │ └── user/ # User management routes │ ├── config/ # Configuration files │ ├── utils/ # Utility functions │ └── index.js # App entry point ├── .env # Environment variables ├── .gitignore # Git ignore file ├── package.json # Dependencies and scripts └── README.md # Project documentation \`\`\` ## Features - 🚀 Express.js with best practices - 🔒 JWT Authentication - ✨ Bodhi Form Validations - 🛡️ Security middleware (helmet, cors) - 📝 API Documentation - 🔄 Hot reload for development - ⚡️ Production-ready setup - 📁 Organized project structure - 🔍 Input validation - 🎯 Error handling ## Quick Start \`\`\`bash # Install dependencies npm install # Start development server npm run dev # Build for production npm run build # Start production server npm start \`\`\` ## API Endpoints ### Auth Routes - POST /api/v1/auth/register - Register a new user - POST /api/v1/auth/login - Login user - POST /api/v1/auth/refresh-token - Refresh JWT token - POST /api/v1/auth/logout - Logout user ### User Routes - GET /api/v1/users - Get all users - GET /api/v1/users/:id - Get user by ID - PUT /api/v1/users/:id - Update user - DELETE /api/v1/users/:id - Delete user ## Environment Variables Copy \`.env.example\` to \`.env\` and update the values: \`\`\`env PORT=3000 NODE_ENV=development MONGODB_URI=mongodb://localhost:27017/${projectName} JWT_SECRET=your-secret-key JWT_EXPIRES_IN=1d API_PREFIX=/api/v1 \`\`\` ## Built With Bodhi - [bodhi-form-validations](https://www.npmjs.com/package/bodhi-form-validations) - Form validation library - Express.js - Web framework - MongoDB - Database - JWT - Authentication - And many more... ## License This project is licensed under the MIT License. Created with ❤️ using [Bodhi CLI](https://github.com/yourusername/bodhi-cli) `; fs.writeFileSync(path.join(root, 'README.md'), readmeContent); spinner.succeed(chalk.green('Project files created successfully!')); console.log(); console.log('Project structure created:'); console.log(); console.log(chalk.cyan(`${projectName}/`)); console.log(chalk.cyan('├── src/')); console.log(chalk.cyan('│ ├── controllers/')); console.log(chalk.cyan('│ │ ├── auth/')); console.log(chalk.cyan('│ │ └── user/')); console.log(chalk.cyan('│ ├── middleware/')); console.log(chalk.cyan('│ │ ├── auth/')); console.log(chalk.cyan('│ │ └── validation/')); console.log(chalk.cyan('│ ├── models/')); console.log(chalk.cyan('│ ├── routes/')); console.log(chalk.cyan('│ │ ├── auth/')); console.log(chalk.cyan('│ │ └── user/')); console.log(chalk.cyan('│ ├── config/')); console.log(chalk.cyan('│ ├── utils/')); console.log(chalk.cyan('│ └── index.js')); console.log(chalk.cyan('├── .env')); console.log(chalk.cyan('├── .gitignore')); console.log(chalk.cyan('├── package.json')); console.log(chalk.cyan('└── README.md')); console.log(); console.log('Inside that directory, you can run several commands:'); console.log(); console.log(chalk.cyan(' npm install')); console.log(' Install project dependencies'); console.log(); console.log(chalk.cyan(' npm run dev')); console.log(' Starts the development server with hot reload'); console.log(); console.log(chalk.cyan(' npm start')); console.log(' Runs the server in production mode'); console.log(); console.log(chalk.cyan(' npm test')); console.log(' Runs the test suite'); console.log(); console.log('We suggest that you begin by typing:'); console.log(); console.log(chalk.cyan(' npm install')); console.log(chalk.cyan(' npm run dev')); console.log(); console.log(chalk.hex(brandColor)('Happy coding with Bodhi! 🚀')); } catch (error) { console.error(chalk.red('Error creating project:'), error); process.exit(1); } }); function generateEnvFile(options, projectName) { return `# Server Configuration PORT=3000 NODE_ENV=development # Database Configuration ${options.database === 'mongodb' ? `MONGODB_URI=mongodb://localhost:27017/${projectName}` : options.database === 'postgres' ? `DB_HOST=localhost DB_PORT=5432 DB_NAME=${projectName} DB_USER=postgres DB_PASSWORD=postgres` : `DB_HOST=localhost DB_PORT=3306 DB_NAME=${projectName} DB_USER=root DB_PASSWORD=root`} # JWT Configuration JWT_SECRET=your-secret-key JWT_EXPIRES_IN=1d # API Configuration API_PREFIX=/api/v1 CORS_ORIGIN=http://localhost:3000`; } program.parse();