A RESTful API for an e-commerce platform built with Node.js, Express, and TypeScript following clean architecture principles.
This project is a demonstration of how to build a scalable and maintainable e-commerce API using clean architecture principles. It showcases:
- Clean Architecture implementation in TypeScript
- Domain-Driven Design (DDD) principles
- RESTful API design with Express
- Type safety with TypeScript
- Testing with Jest
This project follows the clean architecture principles, which separates the code into layers with clear dependencies:
Domain ← Application ← Interfaces ← Infrastructure
-
Domain Layer (
src/domain/)- Contains enterprise business rules
- Entities, value objects, and repository interfaces
- No dependencies on other layers or frameworks
-
Application Layer (
src/application/)- Contains application-specific business rules
- Use cases that orchestrate the flow of data and entities
- Depends only on the domain layer
-
Interfaces Layer (
src/interfaces/)- Contains adapters that convert data from the format most convenient for use cases and entities to the format most convenient for external agencies
- Controllers, presenters, and middleware
- Depends on the application layer
-
Infrastructure Layer (
src/infrastructure/)- Contains frameworks, drivers, and tools
- Database implementations, web frameworks, external services
- Depends on the interfaces layer
The fundamental rule of clean architecture is that dependencies can only point inward. Inner layers should not know anything about outer layers.
src/
├── domain/ # Enterprise business rules
│ ├── entities/ # Business entities
│ └── repositories/ # Repository interfaces
│
├── application/ # Application business rules
│ ├── interfaces/ # Ports for the interfaces layer
│ └── useCases/ # Application use cases
│
├── interfaces/ # Interface adapters
│ ├── controllers/ # Controllers
│ ├── middlewares/ # HTTP middlewares
│ └── presenters/ # Presenters for formatting responses
│
├── infrastructure/ # Frameworks and drivers
│ ├── database/ # Database implementations
│ ├── http/ # HTTP server and routes
│ └── services/ # External services
│
└── config/ # Application configuration
This project auto-discovers repository implementations to avoid manual updates to providers.ts and tokens.ts.
How it works:
- Place implementations under src/infrastructure/database/repositories.
- Each class should be @injectable() and declare a static ENGINE identifying the backend: e.g., static ENGINE = 'memory' | 'mongo' | 'maria'.
- The container scans that folder at startup, registers each implementation under a per-engine token (Symbol.for(
ProductRepository:<engine>)), and aliases the interface token to the selected engine based on DB_ENGINE. - Use cases keep injecting ProductRepositoryToken (stable).
Switch engine:
- Set environment variable DB_ENGINE=memory | mongo | maria. If the requested engine is not discovered, the container falls back to memory.
Example implementation:
export class InMemoryProductRepository implements ProductRepositoryInterface {
static ENGINE = 'memory';
// ...
}
- Node.js (v14 or higher)
- npm or yarn
- Clone the repository
- Install dependencies:
npm install - Copy
.env.exampleto.envand update the values - Build the project:
npm run build - Start the server:
npm start
npm run dev
npm test
GET /api/products- Get all productsGET /api/products/:id- Get a product by IDPOST /api/products- Create a new productPUT /api/products/:id- Update a productDELETE /api/products/:id- Delete a product
- Testability: Business rules can be tested without UI, database, web server, or any external element.
- Independence of frameworks: The architecture doesn't depend on the existence of some library or framework.
- Independence of UI: The UI can change easily, without changing the rest of the system.
- Independence of database: You can swap out databases without affecting business rules.
- Independence of any external agency: Business rules don't know anything about the outside world.
MIT