UNPKG

aj-rest-api-generator

Version:

🔥 A powerful and interactive CLI tool to scaffold REST API boilerplates in seconds.

1,831 lines (1,644 loc) • 48.8 kB
const fs = require('fs'); const path = require('path'); const folders = [ 'src/config', 'src/controllers', 'src/models', 'src/routes', 'src/middlewares', 'src/services', 'src/utils', 'src/validations', 'src/constants', 'docs', 'logs', 'public', 'uploads', 'tests', ]; const files = { 'package.json': `{ "name": "rest-api", "version": "1.0.0", "main": "index.js", "scripts": { "start": "node src/index.js", "dev": "nodemon src/index.js", "test": "jest --coverage", "lint": "eslint .", "format": "prettier --write .", "docs": "apidoc -i routes/ -o docs/" }, "dependencies": { "express": "^4.18.2", "dotenv": "^16.3.1", "mongoose": "^8.0.3", "cors": "^2.8.5", "helmet": "^7.1.0", "morgan": "^1.10.0", "winston": "^3.11.0", "bcryptjs": "^2.4.3", "jsonwebtoken": "^9.0.2", "express-rate-limit": "^6.7.0", "express-validator": "^7.0.1", "swagger-ui-express": "^5.0.0", "yamljs": "^0.3.0", "prom-client": "^15.1.3" }, "devDependencies": { "nodemon": "^3.0.1", "jest": "^29.7.0", "supertest": "^6.3.3", "eslint": "^8.56.0", "prettier": "^3.1.1", "apidoc": "^0.53.1" } }`, '.gitignore': `node_modules .env *.log .DS_Store coverage uploads/* !uploads/.gitkeep `, '.env': `# App Configuration PORT=5000 NODE_ENV=development # Database Configuration DB_URI=mongodb://localhost:27017/restapi DB_TEST_URI=mongodb://localhost:27017/restapi_test # JWT Configuration JWT_SECRET=your_jwt_secret_key JWT_EXPIRE=30d JWT_COOKIE_EXPIRE=30 # Rate Limiting RATE_LIMIT_WINDOW_MS=15*60*1000 # 15 minutes RATE_LIMIT_MAX=100 # Email Configuration (TODO: Configure for production) SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_EMAIL=your_email@example.com SMTP_PASSWORD=your_email_password FROM_EMAIL=noreply@example.com FROM_NAME=REST API `, '.env.example': `# App Configuration PORT=5000 NODE_ENV=development # Database Configuration DB_URI=mongodb://localhost:27017/restapi DB_TEST_URI=mongodb://localhost:27017/restapi_test # JWT Configuration JWT_SECRET=your_jwt_secret_key JWT_EXPIRE=30d JWT_COOKIE_EXPIRE=30 # Rate Limiting RATE_LIMIT_WINDOW_MS=15*60*1000 # 15 minutes RATE_LIMIT_MAX=100 # Email Configuration (TODO: Configure for production) SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_EMAIL=your_email@example.com SMTP_PASSWORD=your_email_password FROM_EMAIL=noreply@example.com FROM_NAME=REST API `, '.eslintrc.json': `{ "extends": ["eslint:recommended"], "env": { "node": true, "es2021": true }, "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "rules": { "indent": ["error", 2], "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "always"] } }`, '.prettierrc': `{ "semi": true, "singleQuote": true, "printWidth": 100, "tabWidth": 2, "trailingComma": "es5" }`, 'src/index.js': `const express = require('express'); const cors = require('cors'); const config = require('./config'); const helmet = require('helmet'); const path = require('path'); const morgan = require('morgan'); const rateLimit = require('express-rate-limit'); const swaggerUi = require('swagger-ui-express'); const YAML = require('yamljs'); const logger = require('./utils/logger'); const db = require('./config/database'); const { errorResponse } = require('./utils/responseHandler'); // Create Express app const app = express(); // Database connection db(); // Middlewares app.use(cors()); app.use(helmet()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(morgan('dev')); // Rate limiting const limiter = rateLimit({ windowMs: process.env.RATE_LIMIT_WINDOW_MS || 15 * 60 * 1000, // 15 minutes max: process.env.RATE_LIMIT_MAX || 100, // limit each IP to 100 requests per windowMs }); app.use(limiter); // Static files app.use('/public', express.static(path.join(__dirname, '../public'))); // Swagger documentation const swaggerDocument = YAML.load(path.join(__dirname, '../docs/swagger.yaml')); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // Routes app.use('/api', require('./routes')); // 404 handler app.use((req, res) => { res.status(404).json({ success: false, message: 'Resource not found' }); }); // Error handler app.use((err, req, res,) => { logger.error(\`Error: \${err.message}\`, config.env === 'development' ? err : undefined); errorResponse(res, err); }); // Start server const PORT = process.env.PORT || 5000; app.listen(PORT, () => { logger.info(\`Server running in \${config.env} mode on port \${PORT}\`); }); module.exports = app; // for testing`, 'src/config/index.js': `const dotenv = require('dotenv'); dotenv.config(); module.exports = { env: process.env.NODE_ENV, port: process.env.PORT, mongoose: { url: process.env.DB_URI + (process.env.NODE_ENV === 'test' ? '_test' : ''), options: { useNewUrlParser: true, useUnifiedTopology: true, }, }, jwt: { secret: process.env.JWT_SECRET, expiresIn: process.env.JWT_EXPIRE, cookieExpires: process.env.JWT_COOKIE_EXPIRE, }, rateLimit: { windowMs: process.env.RATE_LIMIT_WINDOW_MS, max: process.env.RATE_LIMIT_MAX, }, email: { smtp: { host: process.env.SMTP_HOST, port: process.env.SMTP_PORT, auth: { user: process.env.SMTP_EMAIL, pass: process.env.SMTP_PASSWORD, }, }, from: process.env.FROM_EMAIL, fromName: process.env.FROM_NAME, }, };`, 'src/config/database.js': `const mongoose = require('mongoose'); const config = require('.'); const logger = require('../utils/logger'); // TODO: Configure database connection pooling // TODO: Add database transaction support // TODO: Implement database migration system const connectDB = async () => { try { await mongoose.connect(config.mongoose.url, config.mongoose.options); logger.info('Connected to MongoDB'); } catch (err) { logger.error('MongoDB connection error:', err); process.exit(1); } }; module.exports = connectDB;`, 'src/utils/logger.js': `const winston = require('winston'); const path = require('path'); const config = require('../config'); // TODO: Configure logging levels based on environment // TODO: Implement log rotation for production // TODO: Add error tracking service integration (Sentry, etc.) const transports = []; if (config.env === 'production') { transports.push( new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }), new winston.transports.File({ filename: path.join(__dirname, '../../logs/error.log'), level: 'error', }), new winston.transports.File({ filename: path.join(__dirname, '../../logs/combined.log'), }) ); } else { transports.push( new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }) ); } const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports, }); module.exports = logger; `, 'src/utils/apiError.js': `class ApiError extends Error { constructor(statusCode, message, isOperational = true, stack = '') { super(message); this.statusCode = statusCode; this.isOperational = isOperational; if (stack) { this.stack = stack; } else { Error.captureStackTrace(this, this.constructor); } } } module.exports = ApiError;`, 'src/utils/responseHandler.js': `const logger = require('../utils/logger'); const config = require('../config'); /** * Success response handler * @param {Object} res - Express response object * @param {*} data - Response data * @param {number} [statusCode=200] - HTTP status code * @param {string} [message='Success'] - Response message */ const successResponse = (res, data, statusCode = 200, message = 'Success') => { logger.info(\`Response: \${message}\`, config.env === 'development' ? data : undefined); res.status(statusCode).json({ success: true, message, data, }); }; /** * Error response handler * @param {Object} res - Express response object * @param {Error} error - Error object */ const errorResponse = (res, error) => { const statusCode = error.statusCode || 500; const message = error.message || 'Internal Server Error'; res.status(statusCode).json({ success: false, message, error: config.env === 'development' ? error.stack : undefined, }); }; module.exports = { successResponse, errorResponse, };`, 'src/middlewares/auth.js': `const jwt = require('jsonwebtoken'); const ApiError = require('../utils/apiError'); const config = require('../config'); // TODO: Implement role-based access control (RBAC) // TODO: Add token blacklisting for logout functionality // TODO: Implement refresh token mechanism const auth = () => { return async (req, res, next) => { try { // 1) Get token from header or cookies let token; if ( req.headers.authorization && req.headers.authorization.startsWith('Bearer') ) { token = req.headers.authorization.split(' ')[1]; } else if (req.cookies?.token) { token = req.cookies.token; } if (!token) { throw new ApiError(401, 'Please authenticate to access this resource'); } // 2) Verify token const decoded = jwt.verify(token, config.jwt.secret); // 3) Attach user to request object req.user = decoded; next(); } catch (err) { next(err); } }; }; module.exports = auth;`, 'src/middlewares/error.js': `const ApiError = require('../utils/apiError'); const logger = require('../utils/logger'); const config = require('../config'); // TODO: Add more specific error handling for different error types // TODO: Implement error tracking integration const errorConverter = (err, req, res, next) => { let error = err; if (!(error instanceof ApiError)) { const statusCode = error.statusCode || 500; const message = error.message || 'Internal Server Error'; error = new ApiError(statusCode, message, false, err.stack); } next(error); }; const errorHandler = (err, req, res, next) => { const { statusCode, message } = err; logger.error(\`Error \${statusCode}: \${message}\`); res.locals.errorMessage = message; res.status(statusCode).json({ success: false, message, ...(config.env === 'development' && { stack: err.stack }), }); }; module.exports = { errorConverter, errorHandler, };`, 'src/middlewares/validate.js': `const { validationResult } = require('express-validator'); // TODO: Add more validation rules as needed // TODO: Implement custom validation messages const validate = (validations) => { return async (req, res, next) => { await Promise.all(validations.map((validation) => validation.run(req))); const errors = validationResult(req); if (errors.isEmpty()) { return next(); } const extractedErrors = []; errors.array().map((err) => extractedErrors.push({ [err.param]: err.msg })); return res.status(422).json({ success: false, errors: extractedErrors, }); }; }; module.exports = validate;`, 'src/routes/index.js': ` const express = require('express'); const router = express.Router(); // Mount your routes router.use('/users', require('./user.routes')); router.use('/health', require('./health.routes')); router.use('/metrics', require('./metrics.routes')); router.use('/docs', require('./docs.routes')); router.use('/auth', require('./auth.routes')); // Must export the Router module.exports = router;`, 'src/routes/health.routes.js': `const express = require('express'); const router = express.Router(); const mongoose = require('mongoose'); router.get('/', async (req, res) => { const healthcheck = { uptime: process.uptime(), message: 'OK', timestamp: Date.now(), database: mongoose.connection.readyState === 1 ? 'connected' : 'disconnected' }; res.status(200).json(healthcheck); }); module.exports = router;`, 'src/routes/metrics.routes.js': `const client = require('prom-client'); const collectDefaultMetrics = client.collectDefaultMetrics; const express = require('express'); const router = express.Router(); collectDefaultMetrics(); router.get('/', async (req, res) => { res.set('Content-Type', client.register.contentType); res.end(await client.register.metrics()); }); module.exports = router;`, 'src/routes/auth.routes.js': `const express = require('express'); const router = express.Router(); const authController = require('../controllers/auth.controller'); const validate = require('../middlewares/validate'); const authValidation = require('../validations/auth.validation'); // TODO: Add rate limiting for auth routes // TODO: Implement password reset routes router.post( '/register', validate(authValidation.register), authController.register ); router.post('/login', validate(authValidation.login), authController.login); router.post('/logout', authController.logout); router.post( '/refresh-token', validate(authValidation.refreshToken), authController.refreshToken ); module.exports = router;`, 'src/routes/user.routes.js': `const express = require('express'); const router = express.Router(); const auth = require('../middlewares/auth'); const userController = require('../controllers/user.controller'); const validate = require('../middlewares/validate'); const userValidation = require('../validations/user.validation'); // TODO: Add more user-related routes as needed // TODO: Implement user profile picture upload router.use(auth()); router .route('/') .get(userController.getUsers) .post(validate(userValidation.createUser), userController.createUser); router .route('/:userId') .get(validate(userValidation.getUser), userController.getUser) .patch(validate(userValidation.updateUser), userController.updateUser) .delete(validate(userValidation.deleteUser), userController.deleteUser); module.exports = router;`, 'src/routes/docs.routes.js': `const express = require('express'); const router = express.Router(); const path = require('path'); // TODO: Add API documentation authentication in production // TODO: Implement versioned documentation router.get('/', (req, res) => { res.sendFile(path.join(__dirname, '../../docs/index.html')); }); module.exports = router;`, 'src/controllers/auth.controller.js': `const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const ApiError = require('../utils/apiError'); const { successResponse, errorResponse } = require('../utils/responseHandler'); const config = require('../config'); const logger = require('../utils/logger'); const User = require('../models/user.model'); // TODO: Implement email verification // TODO: Add password reset functionality // TODO: Implement 2FA (Two-Factor Authentication) const register = async (req, res, next) => { try { const { name, email, password, role } = req.body; // Check if user already exists const existingUser = await User.findOne({ email }); if (existingUser) { throw new ApiError(400, 'Email already in use'); } // Hash password const hashedPassword = await bcrypt.hash(password, 10); // Create user const user = await User.create({ name, email, password: hashedPassword, role: role || 'user', }); // Generate JWT token const token = generateToken(user); successResponse(res, { user, token }, 201, 'User registered successfully'); } catch (err) { logger.error(\`Register error: \${err.message}\`); errorResponse(res, err); } }; const login = async (req, res, next) => { try { const { email, password } = req.body; // Check if user exists const user = await User.findOne({ email }).select('+password'); if (!user) { throw new ApiError(401, 'Invalid credentials'); } // Check password const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { throw new ApiError(401, 'Invalid credentials'); } // Generate JWT token const token = generateToken(user); // Set cookie res.cookie('token', token, { expires: new Date( Date.now() + config.jwt.cookieExpires * 24 * 60 * 60 * 1000 ), httpOnly: true, secure: process.env.NODE_ENV === 'production', }); successResponse(res, { user, token }, 200, 'Login successful'); } catch (err) { logger.error(\`Login error: \${err.message}\`); errorResponse(res, err); } }; const logout = (req, res, next) => { try { res.cookie('token', 'none', { expires: new Date(Date.now() + 10 * 1000), httpOnly: true, }); successResponse(res, null, 200, 'User logged out successfully'); } catch (err) { logger.error(\`Logout error: \${err.message}\`); errorResponse(res, err); } }; const refreshToken = async (req, res, next) => { try { // TODO: Implement refresh token logic throw new ApiError(501, 'Refresh token functionality not implemented yet'); } catch (err) { logger.error(\`Refresh token error: \${err.message}\`); errorResponse(res, err); } }; const generateToken = (user) => { return jwt.sign( { id: user._id, role: user.role, }, config.jwt.secret, { expiresIn: config.jwt.expiresIn, } ); }; module.exports = { register, login, logout, refreshToken, };`, 'src/controllers/user.controller.js': `const ApiError = require('../utils/apiError'); const { successResponse, errorResponse } = require('../utils/responseHandler'); const User = require('../models/user.model'); const logger = require('../utils/logger'); // TODO: Implement user profile picture upload // TODO: Add user activity tracking // TODO: Implement user search and filtering const getUsers = async (req, res, next) => { try { // Pagination const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 10; const skip = (page - 1) * limit; // Query const users = await User.find() .skip(skip) .limit(limit) .select('-password'); const count = await User.countDocuments(); successResponse(res, { users, pagination: { total: count, pages: Math.ceil(count / limit), currentPage: page, }, }); } catch (err) { logger.error(\`Get users error: \${err.message}\`); errorResponse(res, err); } }; const getUser = async (req, res, next) => { try { const user = await User.findById(req.params.userId).select('-password'); if (!user) { throw new ApiError(404, 'User not found'); } successResponse(res, user); } catch (err) { logger.error(\`Get user error: \${err.message}\`); errorResponse(res, err); } }; const createUser = async (req, res, next) => { try { const user = await User.create(req.body); successResponse(res, user, 201, 'User created successfully'); } catch (err) { logger.error(\`Create user error: \${err.message}\`); errorResponse(res, err); } }; const updateUser = async (req, res, next) => { try { const user = await User.findByIdAndUpdate(req.params.userId, req.body, { new: true, runValidators: true, }).select('-password'); if (!user) { throw new ApiError(404, 'User not found'); } successResponse(res, user, 200, 'User updated successfully'); } catch (err) { logger.error(\`Update user error: \${err.message}\`); errorResponse(res, err); } }; const deleteUser = async (req, res, next) => { try { const user = await User.findByIdAndDelete(req.params.userId); if (!user) { throw new ApiError(404, 'User not found'); } successResponse(res, null, 200, 'User deleted successfully'); } catch (err) { logger.error(\`Delete user error: \${err.message}\`); errorResponse(res, err); } }; module.exports = { getUsers, getUser, createUser, updateUser, deleteUser, };`, 'src/models/user.model.js': `const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const validator = require('validator'); // TODO: Add more user fields as needed // TODO: Implement user soft delete // TODO: Add user activity tracking const userSchema = new mongoose.Schema( { name: { type: String, required: [true, 'Please provide your name'], trim: true, maxlength: [50, 'Name cannot be more than 50 characters'], }, email: { type: String, required: [true, 'Please provide your email'], unique: true, lowercase: true, validate: [validator.isEmail, 'Please provide a valid email'], }, password: { type: String, required: [true, 'Please provide a password'], minlength: [8, 'Password must be at least 8 characters'], select: false, }, role: { type: String, enum: ['user', 'admin'], default: 'user', }, isActive: { type: Boolean, default: true, }, lastLogin: { type: Date, }, }, { timestamps: true, toJSON: { virtuals: true, transform: function (doc, ret) { delete ret.password; delete ret.__v; return ret; }, }, toObject: { virtuals: true, transform: function (doc, ret) { delete ret.password; delete ret.__v; return ret; }, }, } ); // Hash password before saving userSchema.pre('save', async function (next) { if (!this.isModified('password')) return next(); this.password = await bcrypt.hash(this.password, 12); next(); }); // Method to compare passwords userSchema.methods.comparePassword = async function ( candidatePassword, userPassword ) { return await bcrypt.compare(candidatePassword, userPassword); }; const User = mongoose.model('User', userSchema); module.exports = User;`, 'src/services/email.service.js': `const nodemailer = require('nodemailer'); const config = require('../config'); const logger = require('../utils/logger'); // TODO: Implement email templates // TODO: Add email queue for production // TODO: Implement email retry logic const transporter = nodemailer.createTransport({ host: config.email.smtp.host, port: config.email.smtp.port, auth: { user: config.email.smtp.auth.user, pass: config.email.smtp.auth.pass, }, }); if (process.env.NODE_ENV !== 'production') { transporter.verify((error) => { if (error) { logger.error('Error with email configuration:', error); } else { logger.info('Email server is ready to take our messages'); } }); } const sendEmail = async (to, subject, text) => { try { const mailOptions = { from: \`"\${config.email.fromName}" <\${config.email.from}>\`, to, subject, text, // html: TODO: Add HTML template }; await transporter.sendMail(mailOptions); } catch (err) { logger.error('Email sending error:', err); throw err; } }; const sendVerificationEmail = async (user, verificationToken) => { try { const verificationUrl = \`\${process.env.FRONTEND_URL}/verify-email?token=\${verificationToken}\`; const subject = 'Email Verification'; const text = \`Hi \${user.name},\\n\\nPlease verify your email by clicking on the following link: \${verificationUrl}\\n\\nIf you did not create an account, please ignore this email.\\n\`; await sendEmail(user.email, subject, text); } catch (err) { logger.error('Verification email error:', err); throw err; } }; module.exports = { sendEmail, sendVerificationEmail, };`, 'src/validations/auth.validation.js': `const { body } = require('express-validator'); // TODO: Add more validation rules as needed // TODO: Implement custom validation messages const register = [ body('name') .notEmpty() .withMessage('Name is required') .isLength({ max: 50 }) .withMessage('Name cannot be more than 50 characters'), body('email') .notEmpty() .withMessage('Email is required') .isEmail() .withMessage('Please provide a valid email'), body('password') .notEmpty() .withMessage('Password is required') .isLength({ min: 8 }) .withMessage('Password must be at least 8 characters'), body('role') .optional() .isIn(['user', 'admin']) .withMessage('Role must be either user or admin'), ]; const login = [ body('email') .notEmpty() .withMessage('Email is required') .isEmail() .withMessage('Please provide a valid email'), body('password').notEmpty().withMessage('Password is required'), ]; const refreshToken = [ body('refreshToken').notEmpty().withMessage('Refresh token is required'), ]; module.exports = { register, login, refreshToken, };`, 'src/validations/user.validation.js': `const { body, param } = require('express-validator'); // TODO: Add more validation rules as needed // TODO: Implement custom validation messages const createUser = [ body('name') .notEmpty() .withMessage('Name is required') .isLength({ max: 50 }) .withMessage('Name cannot be more than 50 characters'), body('email') .notEmpty() .withMessage('Email is required') .isEmail() .withMessage('Please provide a valid email'), body('password') .notEmpty() .withMessage('Password is required') .isLength({ min: 8 }) .withMessage('Password must be at least 8 characters'), body('role') .optional() .isIn(['user', 'admin']) .withMessage('Role must be either user or admin'), ]; const getUser = [ param('userId').isMongoId().withMessage('Invalid user ID format'), ]; const updateUser = [ param('userId').isMongoId().withMessage('Invalid user ID format'), body('name') .optional() .isLength({ max: 50 }) .withMessage('Name cannot be more than 50 characters'), body('email').optional().isEmail().withMessage('Please provide a valid email'), body('role') .optional() .isIn(['user', 'admin']) .withMessage('Role must be either user or admin'), ]; const deleteUser = [ param('userId').isMongoId().withMessage('Invalid user ID format'), ]; module.exports = { createUser, getUser, updateUser, deleteUser, };`, 'src/constants/httpStatusCodes.js': `module.exports = { // Success OK: 200, CREATED: 201, ACCEPTED: 202, NO_CONTENT: 204, // Client errors BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, NOT_FOUND: 404, METHOD_NOT_ALLOWED: 405, CONFLICT: 409, VALIDATION_ERROR: 422, // Server errors INTERNAL_SERVER_ERROR: 500, NOT_IMPLEMENTED: 501, BAD_GATEWAY: 502, SERVICE_UNAVAILABLE: 503, GATEWAY_TIMEOUT: 504, };`, 'src/constants/errorMessages.js': `module.exports = { // Authentication errors INVALID_CREDENTIALS: 'Invalid credentials', UNAUTHORIZED_ACCESS: 'Unauthorized access', INVALID_TOKEN: 'Invalid token', TOKEN_EXPIRED: 'Token expired', EMAIL_IN_USE: 'Email already in use', // User errors USER_NOT_FOUND: 'User not found', USER_DELETED: 'User deleted', USER_INACTIVE: 'User account is inactive', // Validation errors VALIDATION_ERROR: 'Validation error', INVALID_EMAIL: 'Invalid email', INVALID_PASSWORD: 'Invalid password', INVALID_ID: 'Invalid ID format', // Server errors INTERNAL_ERROR: 'Internal server error', NOT_IMPLEMENTED: 'Not implemented', DATABASE_ERROR: 'Database error', // Success messages LOGIN_SUCCESS: 'Login successful', LOGOUT_SUCCESS: 'Logout successful', REGISTER_SUCCESS: 'Registration successful', UPDATE_SUCCESS: 'Update successful', DELETE_SUCCESS: 'Delete successful', };`, 'tests/auth.test.js': `const request = require('supertest'); const app = require('../index'); const mongoose = require('mongoose'); const User = require('../models/user.model'); const { MongoMemoryServer } = require('mongodb-memory-server'); let mongoServer; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); const uri = mongoServer.getUri(); await mongoose.connect(uri); }); afterAll(async () => { await mongoose.disconnect(); await mongoServer.stop(); }); afterEach(async () => { await User.deleteMany(); }); describe('Auth API', () => { describe('POST /api/auth/register', () => { it('should register a new user', async () => { const res = await request(app) .post('/api/auth/register') .send({ name: 'Test User', email: 'test@example.com', password: 'password123', }); expect(res.statusCode).toEqual(201); expect(res.body).toHaveProperty('success', true); expect(res.body).toHaveProperty('message', 'User registered successfully'); expect(res.body.data).toHaveProperty('user'); expect(res.body.data).toHaveProperty('token'); }); it('should not register with duplicate email', async () => { await User.create({ name: 'Existing User', email: 'test@example.com', password: 'password123', }); const res = await request(app) .post('/api/auth/register') .send({ name: 'Test User', email: 'test@example.com', password: 'password123', }); expect(res.statusCode).toEqual(400); expect(res.body).toHaveProperty('success', false); expect(res.body).toHaveProperty('message', 'Email already in use'); }); }); describe('POST /api/auth/login', () => { beforeEach(async () => { await User.create({ name: 'Test User', email: 'test@example.com', password: 'password123', }); }); it('should login with valid credentials', async () => { const res = await request(app) .post('/api/auth/login') .send({ email: 'test@example.com', password: 'password123', }); expect(res.statusCode).toEqual(200); expect(res.body).toHaveProperty('success', true); expect(res.body).toHaveProperty('message', 'Login successful'); expect(res.body.data).toHaveProperty('user'); expect(res.body.data).toHaveProperty('token'); }); it('should not login with invalid password', async () => { const res = await request(app) .post('/api/auth/login') .send({ email: 'test@example.com', password: 'wrongpassword', }); expect(res.statusCode).toEqual(401); expect(res.body).toHaveProperty('success', false); expect(res.body).toHaveProperty('message', 'Invalid credentials'); }); }); });`, 'tests/user.test.js': `const request = require('supertest'); const app = require('../index'); const mongoose = require('mongoose'); const User = require('../models/user.model'); const { MongoMemoryServer } = require('mongodb-memory-server'); const jwt = require('jsonwebtoken'); const config = require('../config'); let mongoServer; let authToken; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); const uri = mongoServer.getUri(); await mongoose.connect(uri); // Create a test user and generate token const user = await User.create({ name: 'Admin User', email: 'admin@example.com', password: 'password123', role: 'admin', }); authToken = jwt.sign( { id: user._id, role: user.role }, config.jwt.secret, { expiresIn: config.jwt.expiresIn } ); }); afterAll(async () => { await mongoose.disconnect(); await mongoServer.stop(); }); afterEach(async () => { await User.deleteMany({ email: { $ne: 'admin@example.com' } }); }); describe('User API', () => { describe('GET /api/users', () => { it('should get all users', async () => { await User.create([ { name: 'User 1', email: 'user1@example.com', password: 'password123', }, { name: 'User 2', email: 'user2@example.com', password: 'password123', }, ]); const res = await request(app) .get('/api/users') .set('Authorization', \`Bearer \${authToken}\`); expect(res.statusCode).toEqual(200); expect(res.body).toHaveProperty('success', true); expect(res.body.data).toHaveProperty('users'); expect(res.body.data.users.length).toBe(3); // Including admin user expect(res.body.data).toHaveProperty('pagination'); }); }); describe('POST /api/users', () => { it('should create a new user', async () => { const res = await request(app) .post('/api/users') .set('Authorization', \`Bearer \${authToken}\`) .send({ name: 'New User', email: 'newuser@example.com', password: 'password123', }); expect(res.statusCode).toEqual(201); expect(res.body).toHaveProperty('success', true); expect(res.body).toHaveProperty('message', 'User created successfully'); expect(res.body.data).toHaveProperty('name', 'New User'); expect(res.body.data).toHaveProperty('email', 'newuser@example.com'); }); }); });`, 'docs/swagger.yaml': `openapi: 3.0.0 info: title: REST API Documentation version: 1.0.0 description: Documentation for the REST API servers: - url: http://localhost:5000/api description: Development server tags: - name: Auth description: Authentication operations - name: Users description: User management operations paths: /auth/register: post: tags: [Auth] summary: Register a new user requestBody: required: true content: application/json: schema: type: object required: - name - email - password properties: name: type: string example: John Doe email: type: string format: email example: john@example.com password: type: string format: password example: password123 role: type: string enum: [user, admin] default: user responses: '201': description: User registered successfully content: application/json: schema: type: object properties: success: type: boolean example: true message: type: string example: User registered successfully data: type: object properties: user: $ref: '#/components/schemas/User' token: type: string example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '400': description: Bad request (validation errors) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /auth/login: post: tags: [Auth] summary: Authenticate user requestBody: required: true content: application/json: schema: type: object required: - email - password properties: email: type: string format: email example: john@example.com password: type: string format: password example: password123 responses: '200': description: Login successful content: application/json: schema: type: object properties: success: type: boolean example: true message: type: string example: Login successful data: type: object properties: user: $ref: '#/components/schemas/User' token: type: string example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '401': description: Invalid credentials content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /auth/logout: post: tags: [Auth] summary: Logout user security: - bearerAuth: [] responses: '200': description: Logout successful content: application/json: schema: type: object properties: success: type: boolean example: true message: type: string example: Logout successful '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /auth/refresh-token: post: tags: [Auth] summary: Refresh access token requestBody: required: true content: application/json: schema: type: object required: - refreshToken properties: refreshToken: type: string example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... responses: '200': description: Token refreshed content: application/json: schema: type: object properties: success: type: boolean example: true data: type: object properties: token: type: string example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '401': description: Invalid refresh token content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /users: get: tags: [Users] summary: Get all users security: - bearerAuth: [] parameters: - in: query name: page schema: type: integer default: 1 description: Page number - in: query name: limit schema: type: integer default: 10 description: Items per page - in: query name: role schema: type: string enum: [user, admin] description: Filter by role responses: '200': description: List of users content: application/json: schema: type: object properties: success: type: boolean example: true data: type: object properties: users: type: array items: $ref: '#/components/schemas/User' pagination: type: object properties: total: type: integer example: 1 pages: type: integer example: 1 currentPage: type: integer example: 1 '403': description: Forbidden (admin access required) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' post: tags: [Users] summary: Create new user (admin only) security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateUserInput' responses: '201': description: User created content: application/json: schema: type: object properties: success: type: boolean example: true message: type: string example: User created successfully data: $ref: '#/components/schemas/User' '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /users/{userId}: get: tags: [Users] summary: Get user by ID security: - bearerAuth: [] parameters: - in: path name: userId required: true schema: type: string description: User ID responses: '200': description: User details content: application/json: schema: type: object properties: success: type: boolean example: true data: $ref: '#/components/schemas/User' '404': description: User not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' patch: tags: [Users] summary: Update user security: - bearerAuth: [] parameters: - in: path name: userId required: true schema: type: string description: User ID requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateUserInput' responses: '200': description: User updated content: application/json: schema: type: object properties: success: type: boolean example: true message: type: string example: User updated successfully data: $ref: '#/components/schemas/User' '403': description: Forbidden (users can only update their own profile) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' delete: tags: [Users] summary: Delete user (admin only) security: - bearerAuth: [] parameters: - in: path name: userId required: true schema: type: string description: User ID responses: '200': description: User deleted content: application/json: schema: type: object properties: success: type: boolean example: true message: type: string example: User deleted successfully '403': description: Forbidden (admin access required) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: User: type: object properties: id: type: string example: 5f8d0d55b54764421b7156c3 name: type: string example: John Doe email: type: string format: email example: john@example.com role: type: string enum: [user, admin] example: user isActive: type: boolean example: true createdAt: type: string format: date-time updatedAt: type: string format: date-time CreateUserInput: type: object required: - name - email - password properties: name: type: string example: Jane Doe email: type: string format: email example: jane@example.com password: type: string format: password example: password123 role: type: string enum: [user, admin] default: user UpdateUserInput: type: object properties: name: type: string example: Updated Name email: type: string format: email example: updated@example.com role: type: string enum: [user, admin] isActive: type: boolean ErrorResponse: type: object properties: success: type: boolean example: false message: type: string example: Error message errors: type: array items: type: object properties: field: type: string example: email message: type: string example: must be a valid email`, 'logs/.gitkeep': '', 'uploads/.gitkeep': '', 'public/.gitkeep': '', 'README.md': `# REST API Project ## Project Structure \`\`\` project-root/ |── src/ | ├── config/ # Configuration files | ├── controllers/ # Route controllers | ├── models/ # Database models | ├── routes/ # Route definitions | ├── middlewares/ # Custom express middlewares | ├── services/ # Business logic services | ├── utils/ # Utility classes and functions | ├── validations/ # Request validation schemas | ├── constants/ # Constants definitions | ├── tests/ # Test cases | ├── docs/ # Documentation files | ├── logs/ # Log files | ├── public/ # Public assets | └── uploads/ # File uploads |── tests/ # Test files |── docs/ # API documentation files |── logs/ # Log files |── public/ # Public assets |── uploads/ # File uploads |── .env.example # Example environment variables ├── .env # Environment variables ├── .gitignore # Git ignore file ├── .eslintrc.js # ESLint configuration ├── .prettierrc # Prettier configuration \`\`\` ## Environment Variables ## Getting Started ### Prerequisites - Node.js (v16 or higher) - MongoDB - npm (comes with Node.js) ### Installation 1. Clone the repository 2. Install dependencies: \`\`\`bash npm install \`\`\` 3. Set up environment variables: \`\`\`bash cp .env.example .env \`\`\` 4. Start the development server: \`\`\`bash npm run dev \`\`\` ## Available Scripts - \`npm start\`: Start production server - \`npm run dev\`: Start development server with nodemon - \`npm test\`: Run tests - \`npm run lint\`: Run ESLint - \`npm run format\`: Format code with Prettier - \`npm run docs\`: Generate API documentation ## API Documentation After starting the server, API documentation will be available at \`/api-docs\` ## Deployment TODO: Add deployment instructions ## Contributing TODO: Add contribution guidelines ## License TODO: Add license information `, };