Skip to content

Commit 67fe196

Browse files
committed
docs: update README and Swagger UI docs for input/output models and improved API documentation
1 parent 954fa7d commit 67fe196

20 files changed

Lines changed: 1173 additions & 301 deletions

File tree

.husky/pre-push

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/usr/bin/env sh
22
. "$(dirname -- "$0")/_/husky.sh"
33

4-
yarn lint && yarn format
4+
yarn lint && yarn format && yarn test

README.md

Lines changed: 46 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,57 @@
1-
![Clean Architecture Diagram](public/clean-architecture.png)
21

3-
# Digital Market Place API
2+
# Clean Architecture Node.js REST API Example
43

5-
A Node.js REST API for a digital marketplace, structured according to Uncle Bob's Clean Architecture principles. This project demonstrates separation of concerns, testability, and scalability by organizing code into distinct layers: Enterprise Business Rules, Application Business Rules, Interface Adapters, and Frameworks & Drivers.
4+
<div style="width:100%; text-align:center">
5+
<img src="public/images/clean-code_arch.jpeg" width="600">
6+
</div>
67

7-
## Table of Contents
8+
**Objective:**
9+
> This project demonstrates how to apply Uncle Bob's Clean Architecture principles in a Node.js REST API. It is designed as an educational resource to help developers structure their projects for maximum testability, maintainability, and scalability. The codebase shows how to keep business logic independent from frameworks, databases, and delivery mechanisms.
810
9-
- [Introduction](#introduction)
10-
- [Architecture Overview](#architecture-overview)
11-
- [Features](#features)
12-
- [Getting Started](#getting-started)
13-
- [Project Structure](#project-structure)
14-
- [API Endpoints](#api-endpoints)
15-
- [Testing](#testing)
16-
- [Linting & Formatting](#linting--formatting)
17-
- [Docker & Docker Compose](#docker--docker-compose)
18-
- [CI/CD Workflow](#cicd-workflow)
19-
- [Troubleshooting](#troubleshooting)
20-
- [License](#license)
11+
## Stack
12+
- **Node.js** (Express.js) for the REST API
13+
- **MongoDB** (native driver) for persistence
14+
- **Jest** & **Supertest** for unit and integration testing
15+
- **ESLint** & **Prettier** for linting and formatting
16+
- **Docker** & **Docker Compose** for containerization
17+
- **GitHub Actions** for CI/CD
2118

22-
## Introduction
19+
## Why Clean Architecture?
20+
- **Separation of Concerns:** Each layer has a single responsibility and is independent from others.
21+
- **Dependency Rule:** Data and control flow from outer layers (e.g., routes/controllers) to inner layers (use cases, domain), never the reverse. Lower layers are unaware of upper layers.
22+
- **Testability:** Business logic can be tested in isolation by injecting dependencies (e.g., mock DB handlers) from above. No real database is needed for unit tests.
23+
- **Security & Flexibility:** Infrastructure (DB, frameworks) can be swapped without touching business logic.
2324

24-
This backend API allows users to register, authenticate, and interact with products, blogs, and ratings. It is designed for maintainability and extensibility, following Clean Architecture best practices.
25-
26-
## Architecture Overview
27-
28-
The project is organized into the following layers:
29-
30-
- **Enterprise Business Rules**: Core business logic and domain models (`enterprise-business-rules/`).
31-
- **Application Business Rules**: Use cases and application-specific logic (`application-business-rules/`).
32-
- **Interface Adapters**: Controllers, database access, adapters, and middlewares (`interface-adapters/`).
33-
- **Frameworks & Drivers**: Express.js, MongoDB, and other external libraries.
25+
## How Testing Works
26+
- **Unit tests** inject mocks for all dependencies (DB, loggers, etc.) into use cases and controllers. This means you can test all business logic without a real database or server.
27+
- **Integration tests** can use a real or in-memory database, but the architecture allows you to swap these easily.
28+
- **Example:**
29+
- The product use case receives a `createProductDbHandler` as a parameter. In production, this is the real DB handler; in tests, it's a mock function.
30+
- Lower layers (domain, use cases) never import or reference Express, MongoDB, or any framework code.
3431

32+
## Project Structure
3533
```
3634
enterprise-business-rules/
3735
entities/ # Domain models (User, Product, Rating, Blog)
3836
validate-models/ # Validation logic for domain models
3937
application-business-rules/
40-
use-cases/ # Application use cases (products, user)
38+
use-cases/ # Application use cases (products, user, blog)
4139
interface-adapters/
42-
controllers/ # Route controllers for products, users
40+
controllers/ # Route controllers for products, users, blogs
4341
database-access/ # DB connection and data access logic
4442
adapter/ # Adapters (e.g., request/response)
4543
middlewares/ # Auth, logging, error handling
4644
routes/ # Express route definitions
4745
public/ # Static files and HTML views
4846
```
4947

50-
## Features
51-
52-
- User registration and authentication (JWT)
53-
- Product CRUD operations
54-
- Blog and rating management
55-
- Role-based access control (admin, blocked users)
56-
- Input validation and error handling
57-
- Modular, testable codebase
58-
5948
## Getting Started
6049

6150
### Prerequisites
62-
6351
- Node.js (v18+ recommended)
6452
- MongoDB instance (local or cloud)
6553

6654
### Installation
67-
6855
1. Clone the repository:
6956
```bash
7057
git clone <repo-url>
@@ -74,10 +61,10 @@ public/ # Static files and HTML views
7461
```bash
7562
yarn install
7663
```
77-
3. Create a `.env` file in the root with your environment variables (see `.env.example` if available):
64+
3. Create a `.env` file in the root with your environment variables:
7865
```env
7966
PORT=5000
80-
MONGODB_URI=mongodb://localhost:27017/your-db
67+
MONGO_URI=mongodb://localhost:27017/your-db
8168
JWT_SECRET=your_jwt_secret
8269
```
8370
4. Start the server:
@@ -87,52 +74,34 @@ public/ # Static files and HTML views
8774
yarn start
8875
```
8976

90-
The server will run at [http://localhost:5000](http://localhost:5000).
91-
92-
## Project Structure
93-
94-
- `index.js` - Main entry point, sets up Express, routes, and middleware
95-
- `routes/` - Express route definitions for products, users, blogs
96-
- `interface-adapters/` - Controllers, DB access, adapters, and middleware
97-
- `application-business-rules/` - Use cases for products and users
98-
- `enterprise-business-rules/` - Domain models and validation logic
99-
- `public/` - Static HTML views (landing page, 404)
100-
10177
## API Endpoints
102-
103-
### Products
104-
78+
See the `routes/` directory for all endpoints. Example:
10579
- `POST /products/` - Create a new product
10680
- `GET /products/` - Get all products
107-
- `GET /products/:productId` - Get a product by ID
108-
- `PUT /products/:productId` - Update a product
109-
- `DELETE /products/:productId` - Delete a product
110-
- `POST /products/:productId/:userId/rating` - Rate a product
111-
112-
### Users & Auth
113-
11481
- `POST /users/register` - Register a new user
11582
- `POST /users/login` - User login
116-
- `GET /users/profile` - Get user profile (auth required)
117-
118-
### Blogs
119-
12083
- `GET /blogs/` - Get all blogs
121-
- `POST /blogs/` - Create a new blog
12284

123-
> More endpoints and details can be found in the route files under `routes/`.
85+
## API Documentation & Models (Swagger UI)
86+
- Interactive API docs are available at `/api-docs` when the server is running.
87+
- All endpoints are documented with request/response schemas using Swagger/OpenAPI.
88+
- **Models:**
89+
- Each resource (User, Product, Blog) has two main schemas:
90+
- **Input Model** (e.g., `UserInput`, `ProductInput`, `BlogInput`): What the client sends when creating or updating a resource. Only includes fields the client can set (e.g., no `_id`, no server-generated fields).
91+
- **Output Model** (e.g., `User`, `Product`, `Blog`): What the API returns. Includes all fields, including those generated by the server (e.g., `_id`, `role`, etc.).
92+
- This separation improves security, clarity, and validation.
93+
- You can view and try all models in the "Schemas" section of Swagger UI.
12494

12595
## Testing
126-
127-
- Tests are written using [Jest](https://jestjs.io/) and [Supertest](https://github.com/visionmedia/supertest).
96+
- **Unit tests** (Jest): Test business logic in isolation by injecting mocks for all dependencies. No real DB required.
97+
- **Integration tests** (Supertest): Test the full stack, optionally with a real or in-memory DB.
12898
- To run all tests:
12999
```bash
130100
yarn test
131101
```
132-
- Test files are located in the `tests/` directory.
102+
- Test files are in the `tests/` directory.
133103

134104
## Linting & Formatting
135-
136105
- Lint your code:
137106
```bash
138107
yarn lint
@@ -144,33 +113,23 @@ The server will run at [http://localhost:5000](http://localhost:5000).
144113
- Prettier and ESLint are enforced on pre-push via Husky and lint-staged.
145114

146115
## Docker & Docker Compose
147-
148116
- Build and run the app with MongoDB using Docker Compose:
149117
```bash
150118
docker-compose up --build
151119
```
152120
- The app will be available at [http://localhost:5000](http://localhost:5000).
153-
- The MongoDB service runs at `mongodb://localhost:27017/cleanarchdb`.
121+
- The MongoDB service runs at `mongodb://mongo:27017/cleanarchdb` (inside Docker) or `localhost:27017` (locally).
154122
- To stop and remove containers, networks, and volumes:
155123
```bash
156124
docker-compose down -v
157125
```
158126

159127
## CI/CD Workflow
160-
161128
- GitHub Actions workflow is set up in `.github/workflows/ci-cd.yml`.
162-
- On push to `main`, the workflow:
163-
- Installs dependencies
164-
- Lints and formats code
165-
- Runs tests
166-
- Builds a Docker image
167-
- Pushes the image to Docker Hub (update credentials and repo in workflow and GitHub secrets)
129+
- On push to `main`, the workflow lints, tests, builds, and pushes a Docker image.
168130

169131
## Troubleshooting
170-
171-
- Common issues and solutions are documented in [troubleshooting.md](./troubleshooting.md).
172-
- Please add new issues and solutions as you encounter them.
132+
- See [troubleshooting.md](./troubleshooting.md) for common issues and solutions.
173133

174134
## License
175-
176-
This project is licensed under the ISC License. See the [LICENSE](LICENSE) file for details.
135+
ISC License. See [LICENSE](LICENSE).
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
const blogValidation = require('../validate-models/blog-validation');
22

33
module.exports = {
4-
makeBlogModel: ({ blogValidation, logEvents }) => {
5-
return async function makeBlog({ blogData }) {
6-
try {
7-
const validatedBlog = await blogValidation.blogPostValidation({
8-
blogPostData: blogData,
9-
errorHandlers: blogValidation,
10-
});
11-
// Add normalization or additional logic if needed
12-
return Object.freeze(validatedBlog);
13-
} catch (error) {
14-
logEvents && logEvents(`${error.message}`, 'blog-model.log');
15-
throw error;
16-
}
17-
};
18-
},
4+
makeBlogModel: ({ blogValidation, logEvents }) => {
5+
return async function makeBlog({ blogData }) {
6+
try {
7+
const validatedBlog = await blogValidation.blogPostValidation({
8+
blogPostData: blogData,
9+
errorHandlers: blogValidation,
10+
});
11+
// Add normalization or additional logic if needed
12+
return Object.freeze(validatedBlog);
13+
} catch (error) {
14+
logEvents && logEvents(`${error.message}`, 'blog-model.log');
15+
throw error;
16+
}
17+
};
18+
},
1919
};

index.js

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,50 @@ const { dbconnection } = require('./interface-adapters/database-access/db-connec
77
const errorHandler = require('./interface-adapters/middlewares/loggers/errorHandler.js');
88
const { logger } = require('./interface-adapters/middlewares/loggers/logger.js');
99
const createIndexFn = require('./interface-adapters/database-access/db-indexes.js');
10+
const swaggerUi = require('swagger-ui-express');
11+
const swaggerJSDoc = require('swagger-jsdoc');
12+
13+
const PORT = process.env.PORT || 5000;
14+
15+
const swaggerDefinition = {
16+
openapi: '3.0.0',
17+
info: {
18+
title: 'Clean Architecture REST API',
19+
version: '1.0.0',
20+
description: 'API documentation for the Clean Architecture Node.js REST API',
21+
contact: {
22+
name: 'Avom Brice',
23+
email: 'bricefrkc@gmail.com',
24+
},
25+
},
26+
servers: [
27+
{
28+
url: `http://localhost:${PORT}`,
29+
description: 'Local server API documentation',
30+
},
31+
],
32+
components: {
33+
securitySchemes: {
34+
bearerAuth: {
35+
type: 'http',
36+
scheme: 'bearer',
37+
bearerFormat: 'JWT',
38+
},
39+
},
40+
},
41+
security: [{ bearerAuth: [] }],
42+
};
43+
44+
const options = {
45+
swaggerDefinition,
46+
apis: [
47+
'./routes/*.js',
48+
],
49+
};
50+
const swaggerSpec = swaggerJSDoc(options);
1051

1152
const app = express();
1253

13-
const PORT = process.env.PORT || 5000;
1454
var cookieParser = require('cookie-parser');
1555
const corsOptions = require('./interface-adapters/middlewares/config/corsOptions.Js');
1656

@@ -26,14 +66,20 @@ app.use(express.json());
2666
app.use(cookieParser());
2767
app.use(express.urlencoded({ extended: false }));
2868

69+
// Register Swagger UI BEFORE any static or catch-all routes
70+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
71+
2972
// Use the new single entry point for all routes
3073
const mainRouter = require('./routes');
31-
app.use('/', mainRouter);
3274

33-
app.use('/', (_, res) => {
75+
// Only serve index.html for the root path
76+
app.get('/', (_, res) => {
3477
res.sendFile(path.join(__dirname, 'public', 'views', 'index.html'));
3578
});
3679

80+
app.use('/', mainRouter);
81+
82+
3783
//for no specified endpoint that is not found. this must after all the middlewares
3884
app.all('*', (req, res) => {
3985
res.status(404);

interface-adapters/controllers/products/index.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
1-
const { dbProductHandler } = require('../../database-access');
2-
31
const {
42
createProductController,
5-
deleteProductController,
6-
updateProductController,
73
findAllProductController,
84
findOneProductController,
5+
updateProductController,
6+
deleteProductController,
97
rateProductController,
108
// findBestUserRaterController
11-
} = require('./product-controller')();
9+
} = require('./product-controller');
1210

1311
const {
1412
createProductUseCaseHandler,
15-
updateProductUseCaseHandler,
16-
deleteProductUseCaseHandler,
1713
findAllProductUseCaseHandler,
1814
findOneProductUseCaseHandler,
15+
updateProductUseCaseHandler,
16+
deleteProductUseCaseHandler,
1917
rateProductUseCaseHandler,
20-
// findBestUserRaterUseCaseHandler
2118
} = require('../../../application-business-rules/use-cases/products');
2219
const { makeHttpError } = require('../../validators-errors/http-error');
2320

2421
const errorHandlers = require('../../validators-errors/errors');
2522
const { logEvents } = require('../../middlewares/loggers/logger');
23+
const { dbProductHandler } = require('../../database-access');
2624

2725
const createProductControllerHandler = createProductController({
2826
createProductUseCaseHandler,
@@ -68,11 +66,9 @@ const rateProductControllerHandler = rateProductController({
6866

6967
module.exports = {
7068
createProductControllerHandler,
71-
72-
updateProductControllerHandler,
73-
deleteProductControllerHandler,
7469
findAllProductControllerHandler,
7570
findOneProductControllerHandler,
71+
updateProductControllerHandler,
72+
deleteProductControllerHandler,
7673
rateProductControllerHandler,
77-
// findBestUserRaterControllerHandler
7874
};

0 commit comments

Comments
 (0)