Code Generator CLI
Express Base API includes a powerful CLI tool to generate complete resources (models, services, controllers, routes, validations) with a single command.
Featuresβ
- π¨ One Command - Generate entire resource in seconds
- π¦ Complete Structure - Models, services, controllers, routes, validations
- π― Best Practices - Generated code follows project conventions
- π§ Customizable - Templates can be modified
- β‘ Fast Development - 10x faster than manual coding
- π TypeScript - Full type safety out of the box
- ποΈ MVC Pattern - Proper separation of concerns
Quick Startβ
Generate a Resourceβ
npm run g resource product
This creates:
β
src/types/product.types.ts # TypeScript interfaces
β
src/models/product.model.ts # Mongoose model
β
src/services/app/product.service.ts # Service layer
β
src/controllers/app/product.controller.ts # Controller
β
routes/app/product.route.ts # REST routes
β
utils/validations/product.validation.ts # Joi validation
Register the Routeβ
Add to routes.ts:
{
path: '/product',
middlewares: [
Middleware.xApiKey,
Middleware.isLogin,
],
}
Start Usingβ
npm run dev
# Test the endpoints
curl -X GET http://localhost:3000/product
curl -X POST http://localhost:3000/product -d '{"name":"Test"}'
Generated Filesβ
1. TypeScript Typesβ
File: src/types/product.types.ts
import { BaseDocument } from './service.types';
/**
* Product interface
*/
export interface IProduct extends BaseDocument {
// Add your fields here
// Example:
// name: string;
// price: number;
// stock: number;
}
What to do: Add your interface fields
2. Mongoose Modelβ
File: src/models/product.model.ts
import { Model } from 'mongoose';
import { BaseSchema } from '../../../configs/app.config';
import { IProduct } from '../../types';
/**
* Product schema definition
*/
const productSchema = {
// Add your schema fields here
// Example:
// name: {
// type: String,
// required: true,
// index: true,
// },
// price: {
// type: Number,
// required: true,
// min: 0,
// },
};
/**
* Product model
*/
const Product: Model<IProduct> = BaseSchema<IProduct>(
'products',
productSchema
);
export default Product;
What to do: Define schema fields, indexes, validation
3. Service Layerβ
File: src/services/app/product.service.ts
import BaseService from '../../../utils/bases/base.service';
import { IProduct } from '../../types';
import Product from '../../models/product.model';
/**
* Product service
* Handles business logic for products
*/
class ProductService extends BaseService<IProduct> {
constructor() {
super(Product);
}
// Add custom business logic methods here
// Example:
// async getByCategory(category: string) {
// return await this.find({ category } as any);
// }
}
export default ProductService.getInstance();
What to do: Add custom business logic methods
Available BaseService Methods:
find(query, options?)- Find multiple documentsfindById(id)- Find by IDcreate(data)- Create new documentupdate(query, data)- Update document(s)delete(query)- Delete document(s)count(query)- Count documentspaginate(query, options)- Paginated resultsfindByIdCached(id, query, model, ttl)- Cached find by IDcacheGet(key)- Get from cachecacheSet(key, value, ttl)- Set cachecacheDelete(key)- Delete cachecacheGetOrSet(key, fetcher, ttl)- Cache-aside pattern
4. Controller Layerβ
File: src/controllers/app/product.controller.ts
import BaseController from '../../../utils/bases/base.controller';
import productService from '../../services/app/product.service';
/**
* Product controller
* Handles HTTP requests for products
*/
class ProductController extends BaseController {
constructor() {
super(productService);
}
// Add custom endpoints here
// Example:
// async getByCategory(req, res) {
// const { category } = req.params;
// const result = await productService.getByCategory(category);
// return this.handleResponse(res, result);
// }
}
export default ProductController;
What to do: Add custom endpoints beyond CRUD
Available BaseController Methods:
index(req, res)- GET / (list all)show(req, res)- GET /:id (get one)store(req, res)- POST / (create)update(req, res)- PUT /:id (update)destroy(req, res)- DELETE /:id (delete)
5. Routesβ
File: routes/app/product.route.ts
import express from 'express';
import ProductController from '../../src/controllers/app/product.controller';
import productValidation from '../../utils/validations/product.validation';
const router = express.Router();
const productController = new ProductController();
/**
* Product routes
*/
// GET /product - List all products
router.get('/', productController.index);
// GET /product/:id - Get single product
router.get('/:id', productController.show);
// POST /product - Create product
router.post('/', productValidation.store, productController.store);
// PUT /product/:id - Update product
router.put('/:id', productValidation.update, productController.update);
// DELETE /product/:id - Delete product
router.delete('/:id', productValidation.destroy, productController.destroy);
// Add custom routes here
// Example:
// router.get('/category/:category', productController.getByCategory);
export default router;
What to do: Add custom routes if needed
6. Validationβ
File: utils/validations/product.validation.ts
import { validation } from '../../configs/app.config';
import Joi from 'joi';
/**
* Product validation schemas
*/
const productValidation = {
/**
* Validation for creating product
*/
store: validation({
body: Joi.object({
// Add your validation rules here
// Example:
// name: Joi.string().min(3).max(100).required(),
// price: Joi.number().min(0).required(),
// stock: Joi.number().integer().min(0).required(),
}),
}),
/**
* Validation for updating product
*/
update: validation({
body: Joi.object({
// Add your validation rules here (all optional for update)
// Example:
// name: Joi.string().min(3).max(100).optional(),
// price: Joi.number().min(0).optional(),
// stock: Joi.number().integer().min(0).optional(),
}),
params: Joi.object({
id: Joi.string().hex().length(24).required(),
}),
}),
/**
* Validation for deleting product
*/
destroy: validation({
params: Joi.object({
id: Joi.string().hex().length(24).required(),
}),
}),
};
export default productValidation;
What to do: Define validation rules for your fields
Complete Exampleβ
Let's create a complete Product resource:
Step 1: Generateβ
npm run g resource product
Step 2: Define Typesβ
Edit src/types/product.types.ts:
import { BaseDocument } from './service.types';
export interface IProduct extends BaseDocument {
name: string;
description?: string;
price: number;
stock: number;
category: string;
featured: boolean;
}
Step 3: Define Schemaβ
Edit src/models/product.model.ts:
const productSchema = {
name: {
type: String,
required: true,
index: true,
trim: true,
},
description: {
type: String,
required: false,
},
price: {
type: Number,
required: true,
min: 0,
},
stock: {
type: Number,
required: true,
default: 0,
min: 0,
},
category: {
type: String,
required: true,
enum: ['Electronics', 'Clothing', 'Food', 'Other'],
},
featured: {
type: Boolean,
default: false,
},
};
// Add index for category
Product.schema.index({ category: 1 });
Step 4: Add Business Logicβ
Edit src/services/app/product.service.ts:
class ProductService extends BaseService<IProduct> {
/**
* Get featured products with caching
*/
async getFeaturedProducts() {
return await this.cacheGetOrSet(
'products:featured',
async () => {
return await this.find(
{ featured: true } as any,
{ sort: { created_at: -1 }, limit: 10 }
);
},
3600 // Cache for 1 hour
);
}
/**
* Get products by category
*/
async getByCategory(category: string) {
return await this.find(
{ category } as any,
{ sort: { name: 1 } }
);
}
/**
* Update stock after purchase
*/
async updateStock(productId: string, quantity: number) {
const product = await this.findById(productId);
if (product.error || !product.data) {
return { error: true, message: 'Product not found' };
}
if (product.data.stock < quantity) {
return { error: true, message: 'Insufficient stock' };
}
const result = await this.update(
{ _id: productId } as any,
{ stock: product.data.stock - quantity } as any
);
// Invalidate cache
await this.invalidateCache(productId);
return result;
}
/**
* Get low stock products
*/
async getLowStockProducts(threshold: number = 5) {
return await this.find(
{ stock: { $lte: threshold } } as any,
{ sort: { stock: 1 } }
);
}
}
Step 5: Add Custom Endpointsβ
Edit src/controllers/app/product.controller.ts:
import { response } from '../../../configs/app.config';
class ProductController extends BaseController {
/**
* GET /product/featured
*/
async getFeatured(req, res) {
try {
const result = await productService.getFeaturedProducts();
if (result.error) {
return response.error(res, result.message, 400);
}
return response.success(res, result.data);
} catch (error) {
return response.error(res, error.message, 500);
}
}
/**
* GET /product/category/:category
*/
async getByCategory(req, res) {
try {
const { category } = req.params;
const result = await productService.getByCategory(category);
if (result.error) {
return response.error(res, result.message, 400);
}
return response.success(res, result.data);
} catch (error) {
return response.error(res, error.message, 500);
}
}
/**
* GET /product/low-stock
*/
async getLowStock(req, res) {
try {
const threshold = parseInt(req.query.threshold as string) || 5;
const result = await productService.getLowStockProducts(threshold);
if (result.error) {
return response.error(res, result.message, 400);
}
return response.success(res, result.data);
} catch (error) {
return response.error(res, error.message, 500);
}
}
}
Step 6: Add Custom Routesβ
Edit routes/app/product.route.ts:
// Custom routes (add before parameterized routes)
router.get('/featured', productController.getFeatured);
router.get('/low-stock', productController.getLowStock);
router.get('/category/:category', productController.getByCategory);
// Standard CRUD routes
router.get('/', productController.index);
router.get('/:id', productController.show);
router.post('/', productValidation.store, productController.store);
router.put('/:id', productValidation.update, productController.update);
router.delete('/:id', productValidation.destroy, productController.destroy);
Step 7: Add Validationβ
Edit utils/validations/product.validation.ts:
const productValidation = {
store: validation({
body: Joi.object({
name: Joi.string().min(3).max(100).required(),
description: Joi.string().max(500).optional(),
price: Joi.number().min(0).required(),
stock: Joi.number().integer().min(0).required(),
category: Joi.string()
.valid('Electronics', 'Clothing', 'Food', 'Other')
.required(),
featured: Joi.boolean().optional(),
}),
}),
update: validation({
body: Joi.object({
name: Joi.string().min(3).max(100).optional(),
description: Joi.string().max(500).optional(),
price: Joi.number().min(0).optional(),
stock: Joi.number().integer().min(0).optional(),
category: Joi.string()
.valid('Electronics', 'Clothing', 'Food', 'Other')
.optional(),
featured: Joi.boolean().optional(),
}),
params: Joi.object({
id: Joi.string().hex().length(24).required(),
}),
}),
destroy: validation({
params: Joi.object({
id: Joi.string().hex().length(24).required(),
}),
}),
};
Step 8: Register Routeβ
Edit routes.ts:
const routes = [
// ... existing routes
{
path: '/product',
middlewares: [
Middleware.xApiKey,
Middleware.isLogin,
],
},
];
Step 9: Testβ
# Start server
npm run dev
# Create product
curl -X POST http://localhost:3000/product \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_KEY" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"name": "Laptop",
"description": "High-performance laptop",
"price": 999.99,
"stock": 10,
"category": "Electronics",
"featured": true
}'
# Get all products
curl http://localhost:3000/product \
-H "x-api-key: YOUR_KEY" \
-H "Authorization: Bearer YOUR_TOKEN"
# Get featured products
curl http://localhost:3000/product/featured \
-H "x-api-key: YOUR_KEY" \
-H "Authorization: Bearer YOUR_TOKEN"
CLI Command Referenceβ
Generate Resourceβ
npm run g resource <name>
Arguments:
<name>- Resource name (singular, lowercase)
Examples:
npm run g resource product
npm run g resource order
npm run g resource customer
Customizing Templatesβ
Templates are located in bin/script/ and can be modified to suit your needs.
Template Filesβ
bin/script/generate-types.js- TypeScript interfacesbin/script/generate-model.js- Mongoose modelsbin/script/generate-service.js- Service classesbin/script/generate-controller.js- Controllersbin/script/generate-route.js- Routesbin/script/generate-validation.js- Joi validation
Modify Templatesβ
Edit the template strings in these files to change generated code:
// Example: bin/script/generate-model.js
const template = `import { Model } from 'mongoose';
import { BaseSchema } from '../../../configs/app.config';
import { I${capitalize(name)} } from '../../types';
/**
* ${capitalize(name)} schema definition
*/
const ${name}Schema = {
// Your custom default fields here
status: {
type: String,
enum: ['active', 'inactive'],
default: 'active',
},
};
const ${capitalize(name)}: Model<I${capitalize(name)}> = BaseSchema<I${capitalize(name)}>(
'${name}s',
${name}Schema
);
export default ${capitalize(name)};
`;
Best Practicesβ
1. Naming Conventionsβ
- Use singular, lowercase resource names
- Good:
product,order,user - Bad:
Product,products,PRODUCT
2. Structureβ
Keep generated files organized:
src/
βββ types/
β βββ product.types.ts # Interfaces
βββ models/
β βββ product.model.ts # Schema
βββ services/
β βββ app/
β βββ product.service.ts # Logic
βββ controllers/
βββ app/
βββ product.controller.ts # HTTP
3. Customizationβ
Only customize what you need:
- Always customize: Types, schema, validation
- Customize if needed: Service methods, controller endpoints
- Rarely customize: Routes (unless custom endpoints)
4. Don't Modify Base Classesβ
Never modify generated base class usage - extend instead:
// β
Good - Extend base service
class ProductService extends BaseService<IProduct> {
async customMethod() {
// Custom logic
}
}
// β Bad - Don't modify base class
class ProductService {
// Missing base class methods
}
Next Stepsβ
- Quick Start Guide - Complete walkthrough
- Creating Resources - Detailed guide
- Custom Services - Advanced service methods
- Validation - Custom validation rules
Questions? Check the GitHub Discussions or open an issue.