A Clean Project Architecture diagram for Node.js, Express, Socket.IO, and MongoDB

A Clean Project Architecture diagram for Node.js, Express, Socket.IO, and MongoDB

A project architecture diagram for a Node.js, Express, Socket.IO, and MongoDB project following Clean Architecture principles involves visually representing the different layers and their interactions. Below is an example diagram that outlines the main components and their relationships:

Project Architecture Diagram

|--------------------------------------------------|
|                   Presentation                   |
|--------------------------------------------------|
|     Express Controllers & Routes                 |
|--------------------------------------------------|
                    |      |
                    V      V
|--------------------------------------------------|
|                   Application                    |
|--------------------------------------------------|
|               Use Cases/Interactors              |
|--------------------------------------------------|
                    |      |
                    V      V
|--------------------------------------------------|
|                     Domain                       |
|--------------------------------------------------|
|     Entities        |       Repository Interfaces|
|--------------------------------------------------|
                     |      
                     V      
|--------------------------------------------------|
|                     Data                         |
|--------------------------------------------------|
|          Repository Implementations              |
|       (e.g., MongooseUserRepository)             |
|--------------------------------------------------|
                    |      |
                    V      V
|--------------------------------------------------|
|                 Infrastructure                   |
|--------------------------------------------------|
|        Database Config         WebSocket         |
|           (Mongoose)         (Socket.IO)         |
|--------------------------------------------------|

Diagram Description

  1. Presentation Layer: This layer handles HTTP requests and WebSocket connections.

    • Express Controllers & Routes: Define the endpoints and handle incoming HTTP requests, delegating the work to the use cases in the application layer.
  2. Application Layer: This layer contains the use cases or interactors, which orchestrate the flow of data between the domain and presentation layers.

    • Use Cases/Interactors: Implement the business logic and use the repository interfaces to interact with the data layer.
  3. Domain Layer: This layer contains the core business logic, entities, and repository interfaces.

    • Entities: Represent the core business objects.

    • Repository Interfaces: Define the contracts for data operations, which are implemented in the data layer.

  4. Data Layer: This layer contains the implementations of the repository interfaces defined in the domain layer.

    • Repository Implementations: Actual implementations of the repository interfaces, such as MongooseUserRepository, which interact with the database.
  5. Infrastructure Layer: This layer contains the technical details of the system's infrastructure, such as database configurations and WebSocket setup.

    • Database Config (Mongoose): Configuration and connection setup for MongoDB using Mongoose.

    • WebSocket (Socket.IO): Setup and management of WebSocket connections using Socket.IO.

Example Project Architecture Diagram (ASCII Art)

    +---------------------------------------------------+
    |                    Presentation                   |
    +--------------------------------------------------+
    |          Express Controllers & Routes             |
    +--------------------------------------------------+
                       |      |
                       V      V
    +--------------------------------------------------+
    |                   Application                    |
    +--------------------------------------------------+
    |               Use Cases/Interactors              |
    +--------------------------------------------------+
                     |      |
                     V      V
    +--------------------------------------------------+
    |                     Domain                       |
    +--------------------------------------------------+
    | Entities        |       Repository Interfaces    |
    +--------------------------------------------------+
                      |      
                      V      
    +--------------------------------------------------+
    |                     Data                         |
    +--------------------------------------------------+
    |          Repository Implementations              |
    |       (e.g., MongooseUserRepository)             |
    +--------------------------------------------------+
                     |      |
                     V      V
    +--------------------------------------------------+
    |                 Infrastructure                   |
    +--------------------------------------------------+
    | Database Config         WebSocket (Socket.IO)    |
    |   (Mongoose)                                      |
    +--------------------------------------------------+

Notes

  • Each layer has a distinct role and communicates with the layers directly above or below it.

  • The Presentation layer interacts with the Application layer by calling use cases.

  • The Application layer uses repository interfaces defined in the Domain layer to access data.

  • The Data layer implements the repository interfaces and interacts with the database or other data sources.

  • The Infrastructure layer contains the setup and configurations necessary for the technical operations of the system.

By following this architecture, you can ensure a clean separation of concerns, making your application easier to maintain, test, and extend.

Sure! Here's a detailed project structure with a tree view and the main content for each file to guide you in setting up your Node.js, Express, Socket.IO, and MongoDB project with Clean Architecture principles.

Project Structure

Clean-architecture-nodejs
│
├── src
│   ├── application
│   │   └── use-cases
│   │       ├── createUser.js
│   │       └── getUser.js
│   │
│   ├── domain
│   │   ├── entities
│   │   │   └── user.js
│   │   └── repositories
│   │       ├── userRepository.js
│   │       └── userRepositoryImpl.js
│   │
│   ├── infrastructure
│   │   ├── db
│   │   │   ├── mongooseConfig.js
│   │   │   └── models
│   │   │       └── userModel.js
│   │   ├── repositories
│   │   │   └── mongooseUserRepository.js
│   │   └── websocket
│   │       └── socketServer.js
│   │
│   ├── interfaces
│   │   ├── controllers
│   │   │   └── userController.js
│   │   ├── routes
│   │   │   └── userRoutes.js
│   │   └── web
│   │       └── server.js
│   │
│   ├── app.js
│   └── server.js
│
├── .env
├── .gitignore
├── package.json
└── README.md

Detailed File Contents

1. Domain Layer

  • Entities (src/domain/entities/user.js)

      class User {
        constructor(id, name, email) {
          this.id = id;
          this.name = name;
          this.email = email;
        }
      }
    
      module.exports = User;
    
  • Repositories Interface (src/domain/repositories/userRepository.js)

      class UserRepository {
        async createUser(user) {
          throw new Error('Not implemented');
        }
    
        async getUserById(id) {
          throw new Error('Not implemented');
        }
      }
    
      module.exports = UserRepository;
    
  • Repositories Implementation (src/domain/repositories/userRepositoryImpl.js)

      const UserRepository = require('./userRepository');
    
      class UserRepositoryImpl extends UserRepository {
        constructor(userModel) {
          super();
          this.userModel = userModel;
        }
    
        async createUser(user) {
          const newUser = new this.userModel(user);
          return await newUser.save();
        }
    
        async getUserById(id) {
          return await this.userModel.findById(id);
        }
      }
    
      module.exports = UserRepositoryImpl;
    

2. Application Layer

  • Use Cases (src/application/use-cases/createUser.js)

      module.exports = ({ userRepository }) => {
        return async ({ name, email }) => {
          const User = require('../../domain/entities/user');
          const user = new User(null, name, email);
          return await userRepository.createUser(user);
        };
      };
    
  • Use Cases (src/application/use-cases/getUser.js)

      module.exports = ({ userRepository }) => {
        return async (id) => {
          return await userRepository.getUserById(id);
        };
      };
    

3. Infrastructure Layer

  • Mongoose Configuration (src/infrastructure/db/mongooseConfig.js)

      const mongoose = require('mongoose');
    
      module.exports = {
        connect: () => {
          return mongoose.connect(process.env.MONGODB_URI, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
          });
        }
      };
    
  • User Model (src/infrastructure/db/models/userModel.js)

      const mongoose = require('mongoose');
    
      const userSchema = new mongoose.Schema({
        name: { type: String, required: true },
        email: { type: String, required: true },
      });
    
      module.exports = mongoose.model('User', userSchema);
    
  • Mongoose User Repository (src/infrastructure/repositories/mongooseUserRepository.js)

      const UserModel = require('../db/models/userModel');
      const UserRepositoryImpl = require('../../domain/repositories/userRepositoryImpl');
    
      class MongooseUserRepository extends UserRepositoryImpl {
        constructor() {
          super(UserModel);
        }
      }
    
      module.exports = MongooseUserRepository;
    
  • Socket Server (src/infrastructure/websocket/socketServer.js)

      const socketIo = require('socket.io');
    
      module.exports = (server) => {
        const io = socketIo(server);
    
        io.on('connection', (socket) => {
          console.log('New client connected');
    
          socket.on('disconnect', () => {
            console.log('Client disconnected');
          });
    
          // Define other socket events here
        });
    
        return io;
      };
    

4. Interface Adapters Layer

  • Controllers (src/interfaces/controllers/userController.js)

      module.exports = ({ createUser, getUser }) => {
        return {
          createUser: async (req, res, next) => {
            try {
              const user = await createUser(req.body);
              res.status(201).json(user);
            } catch (error) {
              next(error);
            }
          },
    
          getUser: async (req, res, next) => {
            try {
              const user = await getUser(req.params.id);
              if (user) {
                res.status(200).json(user);
              } else {
                res.status(404).send('User not found');
              }
            } catch (error) {
              next(error);
            }
          },
        };
      };
    
  • Routes (src/interfaces/routes/userRoutes.js)

      const express = require('express');
      const router = express.Router();
    
      module.exports = ({ userController }) => {
        router.post('/users', userController.createUser);
        router.get('/users/:id', userController.getUser);
    
        return router;
      };
    

5. App and Server Initialization

  • App Setup (src/app.js)

      const express = require('express');
      const bodyParser = require('body-parser');
      require('dotenv').config();
    
      const createUser = require('./application/use-cases/createUser');
      const getUser = require('./application/use-cases/getUser');
      const MongooseUserRepository = require('./infrastructure/repositories/mongooseUserRepository');
    
      const userController = require('./interfaces/controllers/userController')({
        createUser: createUser({ userRepository: new MongooseUserRepository() }),
        getUser: getUser({ userRepository: new MongooseUserRepository() }),
      });
    
      const userRoutes = require('./interfaces/routes/userRoutes')({
        userController,
      });
    
      const app = express();
      app.use(bodyParser.json());
      app.use('/api', userRoutes);
    
      module.exports = app;
    
  • Server Setup (src/server.js)

      const http = require('http');
      const app = require('./app');
      const socketServer = require('./infrastructure/websocket/socketServer');
      const mongooseConfig = require('./infrastructure/db/mongooseConfig');
    
      const server = http.createServer(app);
      const io = socketServer(server);
    
      mongooseConfig.connect().then(() => {
        server.listen(3000, () => {
          console.log('Server is running on port 3000');
        });
      }).catch(error => {
        console.error('Database connection failed', error);
      });
    

6. Root Files

  • Environment Configuration (.env)

      MONGODB_URI=mongodb://localhost:27017/yourdbname
    
  • Git Ignore (.gitignore)

      /node_modules
      .env
    
  • Package.json (package.json)

      {
        "name": "clean-architecture-nodejs",
        "version": "1.0.0",
        "main": "src/server.js",
        "scripts": {
          "start": "node src/server.js",
          "dev": "nodemon src/server.js"
        },
        "dependencies": {
          "express": "^4.17.1",
          "mongoose": "^5.12.3",
          "socket.io": "^4.0.0",
          "dotenv": "^8.2.0",
          "body-parser": "^1.19.0"
        },
        "devDependencies": {
          "nodemon": "^2.0.7"
        }
      }
    
  • Readme (README.md)

      markdownCopy code# Clean Architecture Node.js Project
    
      This project is a Node.js application structured following Clean Architecture principles, using Express, Socket.IO, and MongoDB.
    
      ## Setup
    
      1. Clone the repository.
      2. Install dependencies: `npm install`
      3. Set up environment variables in a `.env` file.
      4. Start the application: `npm run dev`
    
      ## Project Structure
    
      - `src/application`: Contains use cases or interactors.
      - `src/domain`: Contains entities and repository interfaces.
      - `src/infrastructure`: Contains external service integrations, such as database configuration and WebSocket setup.
      - `src/interfaces`: Contains controllers and routes.
      - `src/app.js`: Initializes the app.
      - `src/server.js`: Configures and starts the server.
    

With this structure, you can develop your project in a modular way, ensuring the separation of concerns and making it easier to maintain and extend.