Generate production-ready Node.js code with OOP architecture, Dependency Injection, and support for Knex/Objection (SQL) or Mongoose (MongoDB).
# 1. Install the CLI globally
npm install -g node-scaffold-cli
# 2. Generate a complete resource
scaffold g user
# 3. Install required dependencies (see Prerequisites below)
# For Knex/Objection (SQL):
npm install express objection knex pg tsyringe reflect-metadata
npm install -D @types/express @types/node typescript
# For Mongoose (MongoDB):
npm install express mongoose tsyringe reflect-metadata
# 4. Configure TypeScript (see Setup section)
# 5. Start coding!# Option 1: Install globally (recommended)
npm install -g node-scaffold-cli
# Option 2: Install as dev dependency in your project
npm install -D node-scaffold-cli
# Then use: npx scaffold g user
# Generate files matching your existing structure
scaffold g user --path ./src
# Or for nested structures:
scaffold g user --path ./src/modules
scaffold g product --path ./app/featuresKey Points:
- Use
--pathto match your existing project structure - Files are generated relative to the specified path
- CLI won't overwrite existing files without confirmation
- Works with any TypeScript/Node.js project structure
That's it! 🎉
Note: Make sure to install the required dependencies for your chosen ORM and configure TypeScript before using the generated code.
Before using the generated code, you'll need to install dependencies based on your setup:
| Setup | Required Packages |
|---|---|
| All Setups | express, @types/express, @types/node, typescript |
| Knex (SQL) | objection, knex, pg (or mysql2, sqlite3, etc.) |
| Mongoose (MongoDB) | mongoose |
| With DI | tsyringe, reflect-metadata |
Complete Setup (Knex + DI):
npm install express objection knex pg tsyringe reflect-metadata
npm install -D @types/express @types/node typescriptComplete Setup (Mongoose + DI):
npm install express mongoose tsyringe reflect-metadata
npm install -D @types/express @types/node typescriptWithout DI (Knex):
npm install express objection knex pg
npm install -D @types/express @types/node typescriptWithout DI (Mongoose):
npm install express mongoose
npm install -D @types/express @types/node typescriptYour tsconfig.json must include these options for DI to work:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Note: The CLI automatically adds
reflect-metadataimport when DI is enabled, but you still need to install the package.
See the Setup After Generation section for complete TypeScript configuration examples.
src/
├── models/
│ ├── user.model.ts
│ ├── product.model.ts
│ └── index.ts → Barrel exports (auto-generated)
├── repositories/
│ ├── user.repository.ts
│ ├── product.repository.ts
│ └── index.ts → Barrel exports (auto-generated)
├── services/
│ ├── user.service.ts
│ ├── product.service.ts
│ └── index.ts → Barrel exports (auto-generated)
├── controllers/
│ ├── user.controller.ts
│ ├── product.controller.ts
│ └── index.ts → Barrel exports (auto-generated)
└── routers/
├── user.router.ts
├── product.router.ts
└── index.ts → Barrel exports (auto-generated)
Barrel Exports: Each folder automatically gets an index.ts file that exports all components, enabling clean imports like import { UserModel, ProductModel } from './models'.
Model (Objection):
export class User extends Model {
static tableName = 'users';
static get jsonSchema() {
return {
type: 'object',
properties: {
id: { type: 'integer' },
// Add your fields here
created_at: { type: 'string', format: 'date-time' },
updated_at: { type: 'string', format: 'date-time' },
},
};
}
}Note: Models are generated empty by default. Add your fields manually after generation.
Repository:
export class UserRepository {
async findById(id: number): Promise<User | undefined> {}
async findAll(): Promise<User[]> {}
async create(data: Partial<User>): Promise<User> {}
// ... more methods
}Service:
export class UserService {
constructor(private userRepository: UserRepository) {}
async getById(id: number): Promise<User | null> {}
async create(data: Partial<User>): Promise<User> {}
// ... with validation
}Controller:
export class UserController {
getAll = async (req: Request, res: Response) => {};
getById = async (req: Request, res: Response) => {};
create = async (req: Request, res: Response) => {};
// ... all CRUD endpoints
}Router:
export class UserRouter {
public router: Router;
constructor(private userController: UserController) {
this.router = Router();
this.setupRoutes();
}
private setupRoutes(): void {
this.router.get('/', this.userController.getAll);
this.router.get('/:id', this.userController.getById);
this.router.post('/', this.userController.create);
this.router.put('/:id', this.userController.update);
this.router.delete('/:id', this.userController.delete);
}
}Generate all components at once (interactive prompts):
scaffold generate user
scaffold g product --orm mongooseWhen to use: Starting a new resource from scratch
Generate one component at a time:
scaffold add model user
scaffold add service product
scaffold a controller orderWhen to use: Adding to existing resources or granular control
| Option | Values | Default | Description |
|---|---|---|---|
--orm -o |
knex, mongoose |
knex |
ORM/ODM (knex=Objection ORM, mongoose=Mongoose ODM) |
--path -p |
any path | ./src |
Output directory |
--no-di |
- | DI enabled | Disable dependency injection |
The --path option is crucial for integrating with existing projects. It determines where files will be generated:
# Standard structure (default)
scaffold g user --path ./src
# Generates: src/models/, src/repositories/, etc.
# Nested module structure
scaffold g user --path ./src/modules/users
# Generates: src/modules/users/models/, src/modules/users/repositories/, etc.
# Feature-based structure
scaffold g product --path ./app/features/products
# Generates: app/features/products/models/, etc.
# Monorepo structure
scaffold g order --path ./packages/api/src
# Generates: packages/api/src/models/, etc.Tips:
- Always run the command from your project root
- Use relative paths (e.g.,
./src,./app) - The CLI creates the directory structure if it doesn't exist
- Generated files use relative imports based on the path structure
The CLI automatically checks if files already exist before creating them:
- If file exists: You'll be prompted to confirm overwrite
- If you skip: The file is preserved and marked as "skipped" in the summary
- Summary report: Shows which files were created, overwritten, or skipped
Example output:
✅ Created: src/models/user.model.ts
📦 Created models/index.ts with barrel export
✏️ Overwritten: src/repositories/user.repository.ts
📦 Updated repositories/index.ts with new export
⏭️ Skipped: src/services/user.service.ts
When generating with DI enabled (--di is default):
- Auto-detects entry point files (
index.ts,app.ts,main.ts,server.ts,config/container.ts) - Auto-adds
import 'reflect-metadata';if missing - Skips if already imported
- Shows message when import is added
Note: You still need to install: npm install reflect-metadata
# Step 1: Install CLI (if not already installed)
npm install -g node-scaffold-cli
# Step 2: Install project dependencies (see Prerequisites above)
npm install express objection knex pg tsyringe reflect-metadata
npm install -D @types/express @types/node typescript
# Step 3: Configure TypeScript (see Setup section)
# Add experimentalDecorators and emitDecoratorMetadata to tsconfig.json
# Step 4: Generate resource
scaffold g user --orm knex
# Step 5: You'll be prompted:
# - Select components: ✓ Model ✓ Repository ✓ Service ✓ Controller (Router optional)
# Step 6: If files already exist, you'll be asked to confirm overwrite
# Step 7: Done! Files created in src/models, src/repositories, etc.
# - Summary shows: created, overwritten, and skipped files
# - reflect-metadata import added automatically (if DI enabled)
# - index.ts barrel export files created/updated automatically
# Step 8: Set up database and routes (see Setup section)Models are generated empty by default. Add fields manually after generation:
// Example: Adding fields to a Knex/Objection model
export class User extends Model {
static tableName = 'users';
static get jsonSchema() {
return {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
age: { type: 'number' },
// Add your fields here
created_at: { type: 'string', format: 'date-time' },
updated_at: { type: 'string', format: 'date-time' },
},
};
}
}# Build piece by piece
scaffold add model user
scaffold add repository user
scaffold add service user
scaffold add controller user
scaffold add router user# Each resource in its own files
scaffold g user
scaffold g product
scaffold g order
# Result:
# src/models/user.model.ts, product.model.ts, order.model.ts
# src/repositories/user.repository.ts, product.repository.ts, ...
# Each folder has index.ts with barrel exportsThe CLI automatically creates index.ts files in each folder, enabling clean imports:
// Instead of individual imports:
import { UserModel } from './models/user.model';
import { ProductModel } from './models/product.model';
// Use barrel exports:
import { UserModel, ProductModel } from './models';
import { UserRepository, ProductRepository } from './repositories';
import { UserService, ProductService } from './services';
import { UserController, ProductController } from './controllers';
import { UserRouter, ProductRouter } from './routers';Note: Barrel exports are automatically created/updated when you generate files. No manual maintenance needed!
- Quick Reference ⭐ - Command cheatsheet and examples
- Add Command Guide - Detailed guide for
addcommand - Complete Examples - Full working examples with setup
- Architecture - Understanding the generated structure
- Development Guide - Extending and customizing
- Model - Data structure (Objection ORM for SQL, Mongoose ODM for MongoDB)
- Repository - Database queries
- Service - Business logic + validation
- Controller - HTTP handlers
- Router - Express route definitions
- Full type safety
- IntelliSense support
- Type-safe classes and methods
- Optional DI with tsyringe
- Easy testing with mocks
- Clean dependencies
- RESTful endpoints
- Error handling
- Validation placeholders
- CRUD operations
- Duplicate detection - Prompts before overwriting existing files
- Automatic setup - Adds
reflect-metadataimport when DI is enabled - Barrel exports - Automatically creates/updates
index.tsfiles in each folder for clean imports - Safe operations - Shows summary of created, overwritten, and skipped files
For Knex (SQL) Projects:
npm install express objection knex pg tsyringe reflect-metadata
npm install -D @types/express @types/node typescriptFor Mongoose (MongoDB) Projects:
npm install express mongoose tsyringe reflect-metadata
npm install -D @types/express @types/node typescriptWithout Dependency Injection:
# Knex
npm install express objection knex pg
npm install -D @types/express @types/node typescript
# MongoDB
npm install express mongoose
npm install -D @types/express @types/node typescriptCreate or update your tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Important:
experimentalDecoratorsandemitDecoratorMetadataare required for Dependency Injection to work.
For Knex (SQL):
# 1. Create database config (see EXAMPLES.md)
# 2. Create migration
npx knex migrate:make create_users_table
# 3. Run migrations
npx knex migrate:latestFor Mongoose (MongoDB):
# 1. Connect to MongoDB using Mongoose (see EXAMPLES.md)
# 2. No migrations needed!✨ Automatic Setup: When you generate files with DI enabled, the CLI automatically:
- Detects your entry point file (
index.ts,app.ts,main.ts,server.ts, orconfig/container.ts) - Adds
import 'reflect-metadata';at the top if it's missing - Skips adding if it's already imported
Manual Setup (if automatic detection fails):
// src/config/container.ts
import 'reflect-metadata'; // ← Added automatically by CLI
import { container } from 'tsyringe';
import { createDatabase } from './database';
import { UserRepository } from '../repositories/user.repository';
import { UserService } from '../services/user.service';
import { UserController } from '../controllers/user.controller';
// Register database connection
const db = createDatabase();
container.register('Database', { useValue: db });
// Register components
container.register(UserRepository, { useClass: UserRepository });
container.register(UserService, { useClass: UserService });
container.register(UserController, { useClass: UserController });Note: You still need to install:
npm install tsyringe reflect-metadata
With Router (Recommended):
// src/server.ts or src/app.ts
import express from 'express';
import { container } from 'tsyringe';
import { UserRouter } from './routers/user.router';
const app = express();
app.use(express.json());
// Resolve router from DI container
const userRouter = container.resolve(UserRouter);
// Register router
app.use('/api/users', userRouter.router);
app.listen(3000, () => {
console.log('Server running on port 3000');
});With Dependency Injection (Direct Controller):
// src/server.ts or src/app.ts
import express from 'express';
import { container } from 'tsyringe';
import { UserController } from './controllers/user.controller';
const app = express();
app.use(express.json());
// Resolve controller from DI container
const userController = container.resolve(UserController);
// Register routes
app.get('/api/users', userController.getAll);
app.get('/api/users/:id', userController.getById);
app.post('/api/users', userController.create);
app.put('/api/users/:id', userController.update);
app.delete('/api/users/:id', userController.delete);
app.listen(3000, () => {
console.log('Server running on port 3000');
});Without Dependency Injection:
// src/server.ts
import express from 'express';
import { UserRouter } from './routers/user.router';
import { UserController } from './controllers/user.controller';
import { UserService } from './services/user.service';
import { UserRepository } from './repositories/user.repository';
import { createDatabase } from './config/database';
const app = express();
app.use(express.json());
// Manual instantiation
const db = createDatabase();
const userRepository = new UserRepository(db);
const userService = new UserService(userRepository);
const userController = new UserController(userService);
const userRouter = new UserRouter(userController);
// Register router
app.use('/api/users', userRouter.router);
app.listen(3000, () => {
console.log('Server running on port 3000');
});| Feature | generate |
add |
|---|---|---|
| Use Case | New resource | Single component |
| Interactive | Select components | No prompts |
| Speed | Fast for full setup | Precise control |
| Output | Multiple files | One file |
Tip: Use generate to start, then add for additional components later!
scaffold g user
# Models are generated empty - add fields manually after generationscaffold g product --orm mongoose
# Models are generated empty - add fields manually after generation# You already have user resource
scaffold add service admin-user
# Customize for admin-specific logicscaffold g tenant --orm knex
scaffold g user --orm knex
scaffold g project --orm knex
# Each gets full CRUD structureScenario: You have an existing Express API with this structure:
my-api/
├── src/
│ ├── config/
│ ├── middleware/
│ └── routes/
└── package.json
Add a new resource:
# From project root
scaffold g user --path ./src
# Generates: src/models/, src/repositories/, src/services/, src/controllers/, src/routers/
# If you prefer a different structure
scaffold g product --path ./src/modules/products
# Generates: src/modules/products/models/, etc.Add to existing resource:
# You already have a user model, add other components
scaffold add repository user --path ./src
scaffold add service user --path ./src
scaffold add controller user --path ./src
scaffold add router user --path ./src# In a monorepo with packages/api/src structure
scaffold g user --path ./packages/api/src
# Files generated in packages/api/src/models/, etc.# Feature-based structure
scaffold g user --path ./src/features/users
scaffold g product --path ./src/features/products
# Each feature has its own models, repositories, etc.Scenario: You have an existing Express API with this structure:
my-api/
├── src/
│ ├── config/
│ ├── middleware/
│ └── routes/
└── package.json
Add a new resource:
# From project root
scaffold g user --path ./src
# Generates: src/models/, src/repositories/, src/services/, src/controllers/, src/routers/
# If you prefer a different structure
scaffold g product --path ./src/modules/products
# Generates: src/modules/products/models/, etc.Add to existing resource:
# You already have a user model, add other components
scaffold add repository user --path ./src
scaffold add service user --path ./src
scaffold add controller user --path ./src
scaffold add router user --path ./src# In a monorepo with packages/api/src structure
scaffold g user --path ./packages/api/src
# Files generated in packages/api/src/models/, etc.# Feature-based structure
scaffold g user --path ./src/features/users
scaffold g product --path ./src/features/products
# Each feature has its own models, repositories, etc."Cannot find module 'express'" or similar errors:
- Make sure you've installed all required dependencies (see Prerequisites)
- Run
npm installin your project directory
"Decorator metadata is not enabled" or DI not working:
- Ensure
experimentalDecorators: trueandemitDecoratorMetadata: trueare in yourtsconfig.json - Make sure
reflect-metadatais installed:npm install reflect-metadata - The CLI adds the import automatically, but you need to install the package
"File already exists" prompt:
- This is normal! The CLI protects you from accidental overwrites
- Choose "Yes" to overwrite, or "No" to skip and keep your existing file
TypeScript compilation errors:
- Make sure you have
@types/expressand@types/nodeinstalled - Check that your
tsconfig.jsonincludes the required compiler options
Database connection issues:
- For Knex/Objection: Ensure your database driver is installed (
pg,mysql2, etc.) - For Mongoose: Ensure
mongooseis installed - See EXAMPLES.md for complete database setup examples
- Quick Reference: Check QUICKREF.md for command examples
- Full Examples: See EXAMPLES.md for complete setup guides
- Issues: Report problems on GitHub
- Questions: Check documentation files first
- Issues: Report on GitHub
- Questions: Check QUICKREF.md first
- Examples: See EXAMPLES.md
MIT
- Start with
generatefor new resources - Use
addfor granular control - Check QUICKREF.md for quick commands
- Read EXAMPLES.md for full setup
- Customize generated code - it's starter code!
- Safe to re-run - CLI checks for duplicates and asks before overwriting
- DI setup is automatic -
reflect-metadataimport is handled for you
Need more details? → Check out QUICKREF.md for command reference and examples!