Clean Architecture Implementation in Javascript

Clean Architecture Implementation in Javascript

When most start out as software engineers, they focus on shipping software as fast as they can with little emphasis on quality and maintainability. But as the saying goes: “Wisdom comes from experience. Experience is often a result of lack of wisdom.” ― Terry Pratchett. When you grow as a software engineer, you realise the importance of building high-quality and maintainable software.

One way to achieve the aforementioned quality is the adoption of software design philosophies like clean architecture. Clean architecture is a software design philosophy that emphasizes the separation of concerns and independence of components. The goal of clean architecture is to make software more maintainable, flexible, and testable. This is done by ensuring that the different parts of the system are loosely coupled and can be easily changed or replaced without affecting the rest of the system.

In this blog post, we'll explore how to implement clean architecture in JavaScript. We'll start by looking at the principles of clean architecture, and then we'll walk through an example of how to implement it in a JavaScript Todo application (The gold standard of tutorial applications).

The principles of clean architecture include:

  • Independent of Frameworks: The architecture of the system should be independent of any specific framework, library, or technology. This means that the core business logic of the system should be separate from any specific implementation details, such as the user interface or the database.

  • Independent of UI: The core business logic of the system should not depend on any specific user interface, such as a web page or a mobile app.

  • Independent of Database: The core business logic of the system should not depend on any specific database technology or schema.

A common theme that can be realised from the above principles is independence! From the diagram beneath, you can see how each layer is well-defined and abstracts away the implementation details of the inner layers as well as independent of the outer layers.

Implementation in Javascript (NodeJS)

Prerequisites:

  • You have good knowledge of writing Javascript and are familiar with Node.js/ Express

  • Create a new Node.js project and install ExpressJS:

      mkdir todo-app
      cd todo-app
      npm init -y
      npm install express
    

We would be taking a step-by-step approach to implementing the different layers of the clean architecture starting from the innermost layer.

To implement the clean architecture in JavaScript, we can use the following structure:

Entities Layer

Entities are the core business objects of the system. They represent the key concepts and data that the system is designed to manage. In a todo app, for example, the Todo class might be an entity. Entities are independent of any specific framework or technology, and they contain the properties of the data they are designed to manage.

// entities/todo.js

class Todo {
  constructor(text) {
    this.text = text;
    this._isComplete = false;
  }

  markComplete() {
    this._isComplete = true;
  }

  getStatus() {
    return this._isComplete ? 'Complete' : 'Incomplete';
  }
}

module.exports = Todo;

Some exposed properties and methods visible in the Todo entity above include text , markComplete and getStatus . The two methods markComplete and getStatus controls and retrieves the state for this entity.

Business Logic Layer

Business logic refers to the code that implements the business rules and processes of the system. This includes the use cases or business actions that the system is designed to support, as well as the entities that represent the key concepts of the system. Business logic should be independent of the framework or technology being used to implement the system.

// logic/todo-actions.js

const Todo = require('../entities/todo');

class TodoActions {
  constructor() {
    this.todos = [];
  }

  createTodo(text) {
    const todo = new Todo(text);
    this.todos.push(todo);
    return this.todos.length;
  }

  markTodoComplete(id) {
    const todo = this.todos[id]
    todo.markComplete();
  }

  getAllTodos() {
    return this.todos;
  }
}

module.exports = TodoActions;

Controllers Layer

Controllers are responsible for handling requests from the user interface (UI) and delegating tasks to the appropriate use cases or entities. Using our current application as an example, the controller will handle an HTTP request, call the TodoActions business logic to handle the request, and then return a response to the client. Controllers are part of the infrastructure layer of the system, and they are dependent on the framework or technology being used.

// controllers/todo-controllers.js

const TodoActions = require('../logic/todo-actions');

const todoActions = new TodoActions();

const createTodo = (req, res) => {
    try {
        const text = req.body.text;

        todoActions.createTodo(text);

        return res.send({
            message: 'Todo created successfully.'
        });
    catch (error) {
        return res.status(400).send({message: error.message});
    }
}

const getTodos = (req, res) => {
    try {
        const todos = todoActions.getAllTodos();

        return res.send({ todos });
    catch (error) {
        return res.status(400).send({message: error.message});
    }
}

const completeTodo = (req, res) => {
    try {
        const id = req.params.id;

        todoActions.markTodoComplete(id);

        return res.send({
            message: 'Todo completed successfully.'
        });
    catch (error) {
        return res.status(400).send({message: error.message});
    }
}

module.exports = {
    createTodo,
    getTodos,
    completeTodo
}

Frameworks Layer

A framework is a set of libraries or tools that provide a standard way of building a software system. In the context of clean architecture, frameworks are part of the infrastructure layer of the system and are responsible for tasks such as handling HTTP requests and responses, connecting to databases, and rendering UI templates. Examples of frameworks include ExpressJS for building web applications and Angular for building single-page applications. In our context, ExpressJS is our framework. We would setup ExpressJS below to handle incoming requests to create, retrieve and mark Todos as complete.

// index.js

const express = require('express');

const todoControllers = require('./controllers/todo-controllers.js');

const app = express();
const port = 3000;

app.use(express.json());

app.post('/todos', todoControllers.createTodo);
app.get('/todos', todoControllers.getTodos);
app.put('/todos/:id', todoControllers.completeTodo);

app.listen(port);

This code sets up an ExpressJS server and defines routes for creating, completing, and fetching todos.

To start the server, you can run npm start from the command line. The server will listen on port 3000, and you can make HTTP requests to the routes defined in the code to create, complete, and fetch todos.

Clean architecture is a powerful software design philosophy that can help developers create scalable and maintainable systems. By following the principles of clean architecture, developers can build software that is easy to understand, extend, and maintain, even as the complexity of the projects grows. Whether you're just starting out with clean architecture or are an experienced developer looking to improve your skills, I hope that this blog has been a helpful resource on your journey towards clean, well-designed code in JavaScript.

A recent app I have created using the clean architecture pattern is @summarisethis. This app provides an easy way to summarise long Twitter threads. Simply mention @summarisethis in reply to the thread you want to summarise, and it will provide a concise summary in just a few seconds. @summarisethis is the perfect tool for busy social media users who want to stay informed without spending hours scrolling through long threads. Try it out today and make your Twitter experience more efficient and enjoyable! https://summarisethis.app

Did you find this article valuable?

Support Chinaza Egbo by becoming a sponsor. Any amount is appreciated!