In this post I present an implementation of clean architecture’d REST Api using NestJS. This post does not intends to teach about clean architecture, and for that you will need to follow the references at the bottom of this page.
The stack for that project is:
- PostgresSQL
- TypeOrm
- NodeJS
- NestJS, a framework for building efficient, scalable Node.js server-side applications.
- Nrwl – monorepo
- Typescript
For the codebase I used the following project structure, projects & library packages:
- Generate apps
- api – NestJs
- client – Angular
- Generate libraries
- business-logic, Typescript Library
- entities, Typescript Library
- repository, Typescript Library
- inteface-adapters, Typescript Library
- ui, Angular Library
AccountController
Let’s have look at the main process of activating the API using clean architecture
Translating the code we have:
- a NestJs route for GET /accounts included by creating a new NestJS controller named ‘accounts.controller.ts’
- an ‘Interactor’ which is initialized with matching validators & repository for the upcoming input.
- The DB connection from NestJS is injected into the repository
- The Express Request is injected into NestView
- a Presenter to generate the view model and wrap it as output
- a Nest View to handle the actual HTTP Response of the view model with the help of the NestJs framework
- a controller to initiate and orchestrate the request/response pipe, which we’ll be shown soon.
Notes:
- Using clean architecture we have full control over the details of the application. Validations, repositories, connections, views, even the NestJS framework is isolated and can be easily plugged-out, plugged-in & replaced.
- GetAll request have no input parameters therefore validation is not required for that API, we will see later GetById which will be validated using UUID validator
AccountsLogicController
- That piece of code is the main process of the request/response flow.
- The Interactor handles the request – validation, processing & persistency and generate the result for presenation.
- The presenter handle the response from the interactor and prepare it for view
- The view is the driver that talks to the NestJS/Express Response which delivers the output to the HTTP.
Notes:
- NetsJS’s “ApplicationController” name collided with clean architecture name of the same object so I named it AccountsLoginController. How would you call it?
GetAllAccountsInteractor
The interactor (AKA usecase) implements IRequestHandler which dictates that a handler function is required. The interactor is responsible for request validation till response message is ready:
- input validation
- use case flow
- working with the repositories
- creating the response message which package both the validationResult and the data for the ViewModel
IRequestHandler
Interactor’s common interface, requires to implement a handle function with IRequest parameter as its input & IResponse parameter as its output
GetAllAccountsResponsePresenter
The Presenter implements IPresenter which dictates that a handler function is required. The presenter is responsible for the response validation till response message is ready
- conditional handling of presentation due to success or failure of use case
- prepare view model for each case, success or failure
Notes:
- GetAllAccountsOutput is a wrapper for error & success for the View to be able to differentiate if the feedback to the action should be displayed in the positive flow (success behaviour) or negative flow (failure behaviour).
NestView
NestView is the view driver over NestJS, That is actually one of the amazing things about clean code architecture and it how it introduce the framework as plug-in into the system and how it decouples NestJS from the Business Logic.
Actually we could say that replacing the framework has become very easy and that’s correct, 1) create a new View for the new framework, 2) change the routes 3) change the db connection and the framework is replaced (of course you should also configure the new framework).
The View only needs the “Response” object from the NestJS(/Express) framework and the view model and it renders the JSON as HTTP Response
RepositoryFactory
Helper function to initialise & return desired repositories from the data layer. the existing functions create an In-Memory Account Repository & TypeORM over PostgresSQL Account Repository.
AccountRepository
Account Repository implements IRepository act as a common interface for all repositories, and act as mechanism for changing the dependency of code over database, and make the database plug-in to the code. In the example TypeORM over PostgreSQL is the current plugged device but the example also show a InMemory Account Repository which can act as repository mock for testing.
AccountEntity
AccountEntity is a db schema file for TypeOrm which describe how the storage will be structured. It’s location is the interface-adapters or the data layer if such further separation is desired.
GetAllAccountsRequestMessageValidator
Responsible for the input parameters validation, implements IValidator interface to enable the validator plug in functionality into the architecture.
IValidator
IValidator generic interface for all validator passed as request message validators for the interactor.
GetAllAccountsValidationResult
Validation Result model implements IValidationResult interface, is the output model of the validation test over the input parameters. it can be valid or invalid with errors.
IValidationResult
common interface for validation result models
GetAllAccountsRequestMessage
Request Message represent the input passed into the interactor, implements IRequestMessage implementing a generic interface which follows the interactor input interface.
GetAllAccountsResponseMessage
Response Message represent the output passed out from the interactor and into the presenter, implements IResponseMessage implementing a generic interface which follows the interactor output interface.
IPresenter
Presenters common interface for integrating with the interactor. handle is the only function presenters required to implement, response message (from the interactor output) as input and outputs a view model
Account
Data object used for the business logic of the application, all data coming from storage or input tot he system are converted to that standard system interface
GetAllAccountsOutput
Simple inheritance of ViewOutputImplementation with passing GetAllAccountsViewModel as the view model type reusing the implementation code.
ViewOutputImplementation
View Output Model implementation of result passing the view model or the error to the presenter
IViewOutput
Generic interface for all View Output
IView
Generic interface for all view drivers, having render function and viewModel placeholder to render the proper view according to the view device
IRequestMessage
Request message interface provides a common interface for input parameters of the API request going into the interactor
IResponseMessage
Response message interface provides a common interface for output going out of the interactor and to the presenter
Final Notes
In this post I walked thru an implementation of clean architecture of a REST API using NestJS, TypeORM, Postgres as pluggable frameworks.
Clean Architecture is considered an enterprise application architecture. Having that experience with clean architecture shows that the amount of boilerplate code added to the codebase is 10x and more than a straight forward monolithic code.
Questions can rise against using it like, why should we care if the DB or NestJS is pluggable. In favour we can quickly see how easy it is to write unit test to the business logic.
So thoughts like, “is that the proper way to architect a system from day one”, or “should such architecture be evolved through many previous steps”. But I can imagine startups investing their time to bring some architecture to their codebase and the gain in productivity occurs as the code keeps remaining consistent, decoupled, and the real advantage when the development remains adaptive to future changes, keeping themselves capable of switching from one framework/storage/3rd party api/solutions to another without any substantial effort when the tsunami hits.
References
The Clean Code Blog – Clean Architecture – https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
Robert C Martin – Clean Architecture – https://www.youtube.com/watch?v=Nltqi7ODZTM
The Book
a Clean Architecture in .Net – https://medium.com/@stephanhoekstra/clean-architecture-in-net-8eed6c224c50
Nrwl – monorepo – https://nx.dev/angular/tutorial/01-create-application
NestJs – https://docs.nestjs.com/
Generating a Typescript Library – ng generate @nestjs/schematics:library mynestlib
a minimal demo that shows how to use multiple backends and share modules between them. https://github.com/creadicted/nx-modular-nest-backend
Building Full-Stack Applications https://nx.dev/angular/fundamentals/build-full-stack-applications