@bodheesh/create-bodhi-node-app
Version:
Create a production-ready Node.js REST API with zero configuration
438 lines (376 loc) • 13.5 kB
JavaScript
#!/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();