Build an ExpressJS Application With Clean Architecture

One that actually adheres to Clean Architecture

ยท

8 min read

Motivation

Similarly to my Front-End implementation, I'm not satisfied with the available resources related to Clean Architecture. That's right, even on the Back-End!

What is Clean Architecture

If you stumbled upon this article, you probably know about Clean Architecture, but are looking for more information.

In the rare event of a beginner reading this article, I highly recommend getting started with the necessary theory.

What are we going to build

While I wrote this article, my goal was to test if Clean Architecture was as flexible as it promised. That's the reason I re-used the same entities and use cases as my Front-End implementation, and tried to move the infrastructure part to the Back-End with Express.

That means, the application is the same: a Tic-Tac-Toe game (in TypeScript). You will be able to send requests to a Back-End and play a complete game.

If you want to follow along with me, you can clone my starter project.

Behaviors

You can find the Front-End Tic-Tac-Toe on CodeSandbox to understand what the application is doing in practice. There are two main functionalities:

  • Obviously, you can play a game of Tic-Tac-Toe.

  • You can go back to a previous move (history) and play again.

Instead of being a Front-End application, we will provide endpoints with the same behavior.

File Structure

The original application already follows the idea of Screaming Architecture, with three directories:

  • common/ includes all the shared source code.

  • core/ defines the entry point of our application:

    • app.ts contains the ExpressJS server.

    • bootstrap.ts uses the code inside app.ts to build a server and listen to it.

  • tic-tac-toe/ contains everything related to the game itself.

Files

While you can have a look at the entire project by yourself, please note there are three main files:

App

createApp is responsible for building the ExpressJS server and uses the Tic-Tac-Toe module (which is responsible for routing but is not the focus of this article).

Tic-Tac-Toe Controller

TicTacToeController is responsible for handling requests. It's the one that contains the business logic and the core part of the original implementation.

Board Service

BoardService takes the role of a data access object, which is an in-memory implementation for simplicity.

Define a Game Model

Similarly to my Front-End implementation, I like to start by defining a clear Domain Model. Its goal is to define the elements that make up our system. Please, keep in mind this is not strictly a part of Clean Architecture but Domain-Driven-Design.

For our game of Tic-Tac-Toe, we need:

  • A board composed of 9 squares.

  • Players and winners.

  • At one point in time, the state of our game:

    • Is there a winner?

    • The history of moves (so we can navigate among them).

    • Who is playing next?

    • Potentially, the index of the last game played.

I'll create this file under tic-tac-toe/, using a namespace to make it clear:

Create the base structure for Clean Architecture

We will create an entity and use case in a minute. We can create the base classes for them under common/:

Keep in mind:

There are multiple ways to define those.

For Entity, I like to store properties inside a _data object and make it protected. It prevents developers from mutating it directly, which is a common source of issues. Depending on our needs, this class will definitely change.

There are multiple practices when it comes to use cases, but a very common one is to provide an execute method, which returns a Promise most of the time.

Create the Board Entity

Most of the logic of the Tic-Tac-Toe game seems related to the state of the board. Let's create an entity for it, inside a new entities subfolder, under tic-tac-toe:

Here:

  • We store squares, based on the types from our GameDomainModel.

    • We add a getter to easily access the property.
  • We already have the formula to calculate if player X plays next.

    • We copy it from the TicTacToe file inside the starter project.

    • We can calculate the current step based on the filled squares.

  • The entity is also a great place to calculate if there is a winner.

We can also copy the formula from the starter TicTacToeController file.

In "Clean Architecture", Uncle Bob says:

An Entity is an object within our computer system that embodies a small set of critical business rules operating on Critical Business Data.

In our context, our critical business rules are the rules of the game of Tic-Tac-Toe.

Starting with our first use case

Now, we have everything we need to create an empty use case. We can get started with an obvious one, playing a move.

Obviously, it will take the square we play on. On the other hand, we also need to define the step we play on, as we can play on a previous move. Our use case will probably return an object similar to the game state:

We could probably use GameDomainModel.GameState as Output here. On the other hand, those objects may only seem familiar, and change for different reasons.

To be honest, the current code inside TicTacToeController is very similar to the future content of the Play use case. It might be because I created this project as another implementation, but the original project is still far from using Clean Architecture.

If you remember well, a use case shouldn't depend on a data access gateway from the Interface Adapters layer.

Add the board repository

Instead of the current BoardService, I'll define an interface as a port and its implementation will be an adapter. I'll also use the definition of Repository instead of Service for consistency.

Let's start with the interface by creating a sub-directory ports/ under tic-tac-toe/, with a file called board-repository.port.ts:

Then, we can create an adapters/ sub-directory under tic-tac-toe/. Also, the previous BoardService shouldn't be called BoardRepository.

It's not the implementation of IBoardRepository, it's one among others. To be more precise, it's an in-memory implementation.

Let's create in-memory-board.repository.ts:

Before we go back to our use case, we need one more thing: exceptions.

Exception management

If you look at the original TicTacToeController, you can see we throw exceptions when:

  • The step we are supposed to play on doesn't exist

  • We play on a square that's already taken

With Clean Architecture, we cannot throw exceptions that belong to the infrastructure layer (Interface Adapters & Framework and Drivers), from the use case layer.

Instead, we need to throw a Domain Exception, that is handled by the controller.

Let's define two exceptions, stored inside an exceptions/ sub-directory (itself inside tic-tac-toe/):

Complete our use case

Now that we have our entity, repository, and exceptions, we can complete our use case. Most of the logic can be transferred from TicTacToeController, in handlePlay.

There are a few changes necessary:

  • Business rules are now inside the Board entity.

  • We rely on the IBoardRepository.

  • We throw a Domain Exception

This use case was added under tic-tac-toe/ in its own sub-directory use-cases/.

Adding missing use cases

The process is similar for the two other use cases. We can retrieve most of the logic from the TicTacToeController but use our brand new Board entity and IBoardRepository.

Let's start with jump-to.use-case.ts:

Then, we can add initialize.use-case.ts, which is responsible for starting a new game:

Binding use cases to a controller

The last part is definitely way easier in a Back-End environment. We simply need to:

  • Execute the right use case

  • If there is a Domain (or unknown) Exception, throw the related Application Exception

  • Otherwise, return the result with the right status code.

Dependency Injection

Same thing here, using dependency injection is definitely easy. Depending on the Back-End framework you're using, you will have different solutions (or you can setup your own with InversifyJS).

In this example, I manually instantiate and inject dependencies from app.ts:

Using a presenter

If you look closely at our current controller, it's doing more than orchestration. It's also formatting data for moves, status, and squares, which is not really its role.

A better architecture would use a presenter instead. There are multiple ways to define a presenter. A common practice is to define a class with a format method.

Let's create presenter.ts inside common/:

Now, we need to define our actual presenter. It's supposed to present our game and return the related squares, moves, and status. Based on our current code, it needs the current history, winner, xIsNext, and step.

A controller shouldn't depend directly on a presenter but on its interface.

Let's create a game-presenter.port.ts inside tic-tac-toe/ports/:

Now, we can add the implementation, as game.presenter.ts inside tic-tac-toe/adapters/:

Completing controller

Now, we can expect our IGamePresenter inside the TicTacController constructor (which we instantiate and inject from app.ts). Then, we can remove the method related to formatting and instead use the presenter:

Conclusion

Your Back-End Tic-Tac-Toe game is now fully functional, and adheres to Clean Architecture! The domain (logic) is completely isolated from the infrastructure.

Feel free to look at the end result.

You would have no problem moving away from ExpressJS and using another Back-End framework like Fastify or NestJS. If you did, you wouldn't need to modify any code that's part of the domain layer.

Not only could you use another Back-End framework, but you could even make this game a Front-End application!


Are looking for more in-depth resources about Clean Architecture? Let me know here.

ย