A modern, robust TypeScript backend boilerplate built with SOLID principles, Dependency Injection, and database abstraction to support both SQL and NoSQL databases seamlessly.
This boilerplate implements industry best practices for backend development:
- TypeScript with strict type checking
- SOLID Principles for maintainable code architecture
- Dependency Injection using InversifyJS
- Repository Pattern for data access abstraction
- Service Layer for business logic encapsulation
- Database Abstraction supporting both SQL (PostgreSQL) and NoSQL (MongoDB)
- Unit Testing with Jest
- REST API using Express
The architecture follows a clean, layered approach:
- Controllers Layer: Handles HTTP requests/responses, input validation, and routing
- Services Layer: Implements business logic and orchestrates operations
- Repositories Layer: Abstracts data access operations
- Data Access Layer: Provides database-specific implementations
- Models Layer: Defines domain entities and DTOs
The application uses InversifyJS for dependency injection, which enables:
- Loose coupling between components
- Easier testing through dependency mocking
- Better separation of concerns
- Runtime dependency resolution
One of the key features of this boilerplate is its database agnosticism:
- SQL support using TypeORM (PostgreSQL)
- NoSQL support using Mongoose (MongoDB)
- Repository pattern to abstract data access
- Easy switching between databases via configuration
src/
├── config/ # Configuration files
│ ├── env.config.ts # Environment variables
│ ├── inversify.config.ts # DI container setup
│ └── types.ts # DI types/symbols
├── controllers/ # Express route controllers
│ └── user.controller.ts # User endpoints
├── database/ # Database connections
│ ├── database.interface.ts # DB connection interface
│ ├── database.factory.ts # Factory for DB connections
│ ├── postgres.connection.ts # PostgreSQL implementation
│ └── mongodb.connection.ts # MongoDB implementation
├── interfaces/ # Interfaces
│ ├── base.repository.interface.ts # Base repository interface
│ ├── base.service.interface.ts # Base service interface
│ ├── DbInterfaces.ts # Database related interfaces
│ └── user.service.interfaces.ts # User service interfaces
├── middlewares/ # Express middlewares
│ ├── auth.middleware.ts # Authentication middleware
│ ├── error.middleware.ts # Error handling middleware
│ ├── socket.middleware.ts # Socket.IO middleware
│ ├── validation.middleware.ts # Input validation middleware
│ └── tests/ # Middleware tests
├── models/ # Data models
│ ├── dto/ # Data Transfer Objects
│ │ └── user.dto.ts # User DTOs
│ ├── mongo/ # MongoDB schemas
│ │ └── user.model.ts # User MongoDB model
│ └── sql/ # SQL entities
│ └── user.entity.ts # User TypeORM entity
├── repositories/ # Data access layer
│ ├── mongo/ # MongoDB repositories
│ │ ├── base.repository.ts # Base MongoDB repository
│ │ └── user.repository.ts # User MongoDB repository
│ └── sql/ # SQL repositories
│ ├── base.repository.ts # Base SQL repository
│ └── user.repository.ts # User SQL repository
├── services/ # Business logic layer
│ ├── base.service.ts # Base service implementation
│ ├── user.service.ts # User service
│ ├── socket.service.ts # Socket.IO service
│ └── tests/ # Service tests
├── tests/ # Test setup and utilities
│ ├── integration/ # Integration tests
│ │ └── user.controller.test.ts # User API tests
│ └── setup.ts # Test setup utilities
├── utils/ # Utility classes and helpers
│ ├── response.util.ts # HTTP response utilities
│ └── tests/ # Utilities tests
└── index.ts # Application entry point
- Node.js (v16 or higher recommended)
- npm or yarn
- MongoDB or PostgreSQL (depending on your configuration)
This repository has two main branches:
- main: Contains the core boilerplate code with essential features and structure
- dev: Contains additional implementation examples including product API and authentication. If you're looking for more comprehensive examples, check out the dev branch.
-
Clone the repository:
git clone https://github.com/Shahriar701/Boilerplate-NodeBackend.git cd Boilerplate-NodeBackend
-
Install dependencies:
npm install
-
Set up environment variables:
cp .env.example .env
Edit the
.env
file to match your database configuration:# Server Configuration NODE_ENV=development PORT=3000 API_PREFIX=/api # Database Configuration - Choose ONE set # For SQL (PostgreSQL) DB_TYPE=postgres DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres DB_PASSWORD=postgres DB_NAME=app_db # For MongoDB # DB_TYPE=mongodb # Uncomment this line to use MongoDB MONGO_URI=mongodb://localhost:27017/app_db
-
Start development server:
npm run dev
-
Run tests:
npm test
npm run build
- Compiles TypeScript to JavaScriptnpm run start
- Runs the compiled JavaScript in productionnpm run dev
- Runs the application in development mode with hot-reloadnpm run lint
- Runs ESLint to check code qualitynpm run lint:fix
- Automatically fixes ESLint issues when possiblenpm test
- Runs Jest testsnpm run test:watch
- Runs Jest in watch modenpm run test:coverage
- Generates test coverage report
- The application loads environment variables from
.env
- The inversify container is configured and dependencies are registered
- Database connection is established based on the DB_TYPE environment variable
- Express server is set up with middleware and controllers
- The server starts listening on the configured port
The boilerplate includes a sample User resource with the following endpoints:
- GET /api/users - Get all users
- GET /api/users/:id - Get user by ID
- POST /api/users - Create a new user
- PUT /api/users/:id - Update an existing user
- DELETE /api/users/:id - Delete a user
Follow these steps to add a new entity to your application:
-
Create Models
- For SQL: Create an entity in
src/models/sql/
- For MongoDB: Create a schema in
src/models/mongo/
- Create DTOs in
src/models/dto/
- For SQL: Create an entity in
-
Create Repository
- Implement the repository interface for the new entity
- Create SQL and/or MongoDB implementations
-
Create Service
- Implement business logic in a service class
-
Create Controller
- Define routes and HTTP handlers for the entity
-
Register Dependencies
- Add types to
src/config/types.ts
- Register in the container in
src/config/inversify.config.ts
- Add types to
-
Import Controller
- Import the controller in
src/index.ts
- Import the controller in
-
Create models:
src/models/sql/product.entity.ts
src/models/mongo/product.model.ts
src/models/dto/product.dto.ts
-
Create repositories:
src/repositories/sql/product.repository.ts
src/repositories/mongo/product.repository.ts
-
Create service:
src/services/product.service.ts
-
Create controller:
src/controllers/product.controller.ts
-
Update
src/config/types.ts
:export const TYPES = { // ... existing types IProductRepository: Symbol.for('IProductRepository'), IProductService: Symbol.for('IProductService'), };
-
Update
src/config/inversify.config.ts
to register new dependencies. -
Import the controller in
src/index.ts
.
The boilerplate comes with Jest configured for testing:
- Unit tests: Test individual components in isolation
- Integration tests: Test interactions between components
-
Run all tests:
npm test
-
Run specific test files:
npm test -- src/services/user.service.test.ts
-
Run tests with a specific pattern:
npm test -- -t "should return all users"
-
Run integration tests only:
npm test -- src/tests/integration
-
Run with coverage report:
npm run test:coverage
This boilerplate uses a two-tiered testing approach:
-
Unit Tests: These focus on testing individual components (like services) in isolation. Dependencies are mocked to ensure we're only testing the unit's behavior. Example:
src/services/user.service.test.ts
-
Integration Tests: These test how components work together. For example, the UserController integration tests verify that the controller correctly interacts with the service layer and properly handles HTTP requests/responses. Example:
src/tests/integration/user.controller.test.ts
When writing tests:
- For unit tests, focus on business logic and edge cases
- For integration tests, focus on the contract between components
- Use mocks and stubs to isolate the code being tested
- Write tests before implementing features (TDD) when possible
Example test files:
src/services/user.service.test.ts
- Tests for UserServicesrc/tests/integration/user.controller.test.ts
- Integration tests for UserController
This boilerplate follows these best practices:
-
SOLID Principles:
- Single Responsibility: Each class has one responsibility
- Open/Closed: Entities are open for extension, closed for modification
- Liskov Substitution: Subtypes can be substituted for their base types
- Interface Segregation: Specific interfaces instead of general ones
- Dependency Inversion: Depend on abstractions, not concrete implementations
-
Repository Pattern: Abstracts data access from business logic
-
Service Layer: Encapsulates business logic
-
Dependency Injection: Manages dependencies and promotes loosely coupled code
-
Error Handling: Consistent error handling throughout the application
-
Environment Configuration: Using environment variables for configuration
- Check that your database is running
- Verify the connection details in your .env file
- Check for any firewall or network issues
- Try running
npm install
with the--legacy-peer-deps
flag - Update dependencies to compatible versions
This boilerplate has specific version dependencies to ensure compatibility:
- Express: Using version 4.18.x (not 5.x) to ensure compatibility with inversify-express-utils
- Inversify: Using version 6.x to ensure compatibility with inversify-express-utils
If you see errors related to these dependencies, ensure you're using the correct versions:
# Fix dependency issues
npm uninstall express inversify inversify-express-utils
npm install express@4.18.2 inversify@6.0.3 inversify-express-utils@6.5.0 --save --legacy-peer-deps
- Run
npm run build
to see detailed errors - Check for type inconsistencies in your code
The project includes several middleware components to help with common functionality:
Located in src/middlewares/auth.middleware.ts
, this middleware provides:
- JWT-based authentication
- Role-based access control
- Token generation helpers
Example usage:
// In controller or route handler
import { createAuthMiddleware, hasRoles } from '@middlewares/auth.middleware';
// Apply middleware to a route
router.get('/protected',
createAuthMiddleware(container),
(req, res) => {
res.json({ user: req.user });
}
);
// Role-based access control
router.get('/admin',
createAuthMiddleware(container),
hasRoles(['admin']),
(req, res) => {
res.json({ message: 'Admin access granted' });
}
);
Located in src/middlewares/validation.middleware.ts
, this middleware provides:
- Request body validation
- Custom validation rules
- Error messaging
Example usage:
import { validateBody, Validators } from '@middlewares/validation.middleware';
// Define validation middleware for user creation
const validateUserCreate = validateBody<CreateUserDTO>(
{
email: Validators.email,
name: Validators.string(2, 100),
password: Validators.string(8, 100)
},
{
email: 'Please provide a valid email address',
name: 'Name must be between 2 and 100 characters',
password: 'Password must be at least 8 characters long'
}
);
// Apply middleware to a route
router.post('/users', validateUserCreate, createUserController);
Located in src/middlewares/error.middleware.ts
, this middleware provides:
- Global error handling
- Custom ApiError class for HTTP errors
- Standardized error responses
Example usage:
// Throw an error from a controller
if (!user) {
throw ApiError.notFound('User not found');
}
// Throw a validation error
if (invalidData) {
throw ApiError.badRequest('Invalid data', { field: 'Error message' });
}
The project includes Socket.IO integration for real-time applications:
Located in src/services/socket.service.ts
, this service provides:
- WebSocket server initialization
- Event management
- Room-based messaging
Example usage:
import { SocketService, SocketEvents } from '@services/socket.service';
import { TYPES } from '@config/types';
import { injectable, inject } from 'inversify';
@injectable()
export class NotificationService {
constructor(@inject(TYPES.SocketService) private socketService: SocketService) {}
notifyUser(userId: string, message: string): void {
this.socketService.emitToRoom(
`user:${userId}`,
SocketEvents.NOTIFICATION_CREATED,
{ message }
);
}
}
Socket.IO connections can be authenticated using the same JWT tokens as the REST API:
// Client-side connection with auth token
const socket = io('http://localhost:3000', {
auth: {
token: 'your-jwt-token'
}
});
// Socket events
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('notification:created', (data) => {
console.log('New notification:', data);
});
socket.on('connect_error', (err) => {
console.error('Connection error:', err.message);
});
This project is licensed under the ISC License.