Aller au contenu principal

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 documents
  • findById(id) - Find by ID
  • create(data) - Create new document
  • update(query, data) - Update document(s)
  • delete(query) - Delete document(s)
  • count(query) - Count documents
  • paginate(query, options) - Paginated results
  • findByIdCached(id, query, model, ttl) - Cached find by ID
  • cacheGet(key) - Get from cache
  • cacheSet(key, value, ttl) - Set cache
  • cacheDelete(key) - Delete cache
  • cacheGetOrSet(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 interfaces
  • bin/script/generate-model.js - Mongoose models
  • bin/script/generate-service.js - Service classes
  • bin/script/generate-controller.js - Controllers
  • bin/script/generate-route.js - Routes
  • bin/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​


Questions? Check the GitHub Discussions or open an issue.