A comprehensive sample project demonstrating Test-Driven Development (TDD) principles for building REST APIs with Node.js and Express.js. This project is designed for teaching TDD concepts and showcases proper layered architecture with comprehensive test coverage.
This project demonstrates:
- Test-Driven Development (TDD) methodology
- Layered Architecture (Controller β Service β Data)
- Comprehensive Testing (Unit, Integration, and End-to-End)
- Mocking and Dependency Injection for isolated testing
- REST API Best Practices
- Error Handling and Validation
src/
βββ app.js # Main Express application
βββ controllers/ # HTTP request handlers
βββ services/ # Business logic layer
βββ data/ # Data access layer (mock database)
βββ routes/ # API route definitions
βββ utils/ # Utility functions
βββ middleware/ # Custom middleware
tests/
βββ controllers/ # Controller layer tests
βββ services/ # Service layer tests
βββ data/ # Data layer tests
βββ integration/ # End-to-end API tests
- Node.js (v14 or higher)
- npm or yarn
- Clone the repository:
git clone <repository-url>
cd tdd-nodejs-rest-api- Install dependencies:
npm install- Run the application:
npm start- Run in development mode with auto-reload:
npm run devThe API will be available at http://localhost:3000
npm testnpm run test:watchnpm run test:coverage- Unit Tests: Test individual components in isolation with mocked dependencies
- Integration Tests: Test the complete API flow using supertest
- Data Layer Tests: Test database operations with real mock database
- Service Layer Tests: Test business logic with mocked data layer
- Controller Tests: Test HTTP handling with mocked service layer
http://localhost:3000
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
API information and documentation |
| GET | /api/health |
Health check endpoint |
| GET | /api/users |
Get all users |
| GET | /api/users/:id |
Get user by ID |
| POST | /api/users |
Create new user |
| PUT | /api/users/:id |
Update user |
| DELETE | /api/users/:id |
Delete user |
| GET | /api/users/stats |
Get user statistics |
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}'curl http://localhost:3000/api/userscurl -X PUT http://localhost:3000/api/users/{user-id} \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Doe",
"age": 32
}'- Purpose: Handles data persistence and retrieval
- Testing: Tests database operations with actual mock database
- Key Concepts: CRUD operations, data validation, error handling
- Purpose: Contains business logic and validation
- Testing: Unit tests with mocked database dependencies
- Key Concepts: Business rules, validation, error handling
- Purpose: Handles HTTP requests and responses
- Testing: Unit tests with mocked service dependencies
- Key Concepts: HTTP status codes, request/response handling
- Red: Write a failing test
- Green: Write minimal code to make the test pass
- Refactor: Improve code while keeping tests green
describe('UserService', () => {
let userService;
let mockDatabase;
beforeEach(() => {
mockDatabase = {
findUserById: jest.fn(),
createUser: jest.fn()
};
userService = new UserService(mockDatabase);
});
test('should create user successfully', async () => {
const userData = { name: 'John', email: 'john@example.com', age: 30 };
mockDatabase.createUser.mockResolvedValue({ id: '1', ...userData });
const result = await userService.createUser(userData);
expect(result.success).toBe(true);
expect(mockDatabase.createUser).toHaveBeenCalledWith(userData);
});
});describe('API Integration', () => {
test('should create and retrieve user', async () => {
const newUser = { name: 'Test User', email: 'test@example.com', age: 25 };
// Create user
const createResponse = await request(server)
.post('/api/users')
.send(newUser)
.expect(201);
// Retrieve user
const userId = createResponse.body.data.id;
const getResponse = await request(server)
.get(`/api/users/${userId}`)
.expect(200);
expect(getResponse.body.data.name).toBe(newUser.name);
});
});- Services receive dependencies through constructor injection
- Tests use Jest mocks to isolate components
- Clear separation of concerns enables easy testing
- Comprehensive error handling at all layers
- Proper HTTP status codes
- Detailed error messages for debugging
- Input validation at service layer
- Email format validation
- Age range validation
- Required field validation
- In-memory mock database with realistic async operations
- Seed data for consistent testing
- Support for all CRUD operations
The project aims for high test coverage across all layers:
- Data Layer: 100% coverage of database operations
- Service Layer: 100% coverage of business logic
- Controller Layer: 100% coverage of HTTP handling
- Integration Tests: Complete API workflow coverage
- Start with a test: Write a failing test for the new feature
- Implement the feature: Write minimal code to pass the test
- Refactor: Improve the implementation while keeping tests green
- Add integration tests: Test the feature end-to-end
- Keep layers separated and focused
- Use dependency injection for testability
- Write tests first (TDD approach)
- Mock external dependencies
- Use meaningful test descriptions
This is an educational project. Feel free to:
- Add more test examples
- Improve documentation
- Add new features following TDD principles
- Fix bugs or improve code quality
MIT License - feel free to use this project for educational purposes.
After working with this project, you should understand:
- TDD Methodology: Red-Green-Refactor cycle
- Layered Architecture: Separation of concerns
- Unit Testing: Testing components in isolation
- Integration Testing: Testing complete workflows
- Mocking: Isolating dependencies for testing
- REST API Design: Best practices for API development
- Error Handling: Proper error management and HTTP status codes
- Validation: Input validation and data integrity
To extend this project, consider:
- Adding authentication and authorization
- Implementing pagination for user lists
- Adding more complex business rules
- Integrating with a real database
- Adding API documentation with Swagger
- Implementing rate limiting
- Adding logging and monitoring