An in-depth implementation of Clean Architecture using NestJS and type-script

Overview

Clean Architecture With NestJS

Description

It's been a while since my last article on how to implement clean architecture on Node.js applications, git repo. After working with NestJS and TypeScript I thought it was a good idea to come back and write a new article on the subject. This time we are going to take the super power of typescript and the methodologies and tools of NestJS and harness them to our benefits.

Coming from a background of object-oriented languages, it was natural that we wanted to keep all our SOLID principles in our new and shiny node API.

Like any other architecture, we had to make different trade-offs in the implementation.

We had to be careful not to over-engineer or over-abstract our layers, but rather keep it as flexible as needed.

In recent years, we have implemented clean architecture by Robert C. Martin (Uncle Bob) on our API projects. This architecture attempts to integrate some of the leading modern architecture like Hexagonal Architecture, Onion Architecture, Screaming Architecture into one main architecture. It aims to achieve good separation of concerns. Like most architecture, it also aims to make the application more flexible to inevitable changes in client requirements (which always happens).

clean architecture diagram - dependencies direction are from outside in. source

This diagram is taken from the official article by Robert C. Martin. I recommend reading his article before diving into the node implementation. This is the best source knowledge about this architecture.

Few words about this diagram and how to read it:

  • Dependency - the dependency direction is from the outside in. meaning that the Entities layer is independent and the Frameworks layer depend on all the other layers.

  • Entities - contains all the business entities that construct our application.

  • Use Cases - This is where we centralize our logic. Each use case orchestrates all of the logic for a specific business use case.

  • Controllers and Presenters - Our controller, presenters, and gateways are intermediate layers. You can think of them as an entry and exit gates to the use cases .

  • Frameworks - This layer has all the specific implementations. The database, the web frameworks, error handling etc.
    Robert C. Martin describes this layer :
    “This layer is where all the details go. The Web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.”

In this point you will probably say to yourself “database is in outer layer, database is a detail ???” database is supposed to be my core layer.

I love this architecture because it has a smart motivation behind it. Instead of focusing on frameworks and tools, it focuses on the business logic of the application. This architecture is framework independent (or as much as it can be). This means it doesn’t matter which database, frameworks, UI, external services you are using, the entities and the business logic of the application will always stay the same. We can change all of the above without changing our logic. This is what makes it so easy to test applications built on this architecture. Don’t worry if you don’t understand this yet, we will explore it step-by-step.

Getting Started

Dependencies

  • mongoDb - you need to provide a valid mongDb connection string. add a new environment variable named CLEAN_NEST_MONGO_CONNECTION_STRING
export CLEAN_NEST_MONGO_CONNECTION_STRING='valid mongoDB connection string' 

Installing

npm install

Executing program

npm start

Authors

Royi Benita

Version History

  • 1.0

License

This project is licensed under the [NAME HERE] License - see the LICENSE.md file for details

Acknowledgments

Inspiration, code snippets, etc.

Comments
  • Type 'UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>' is not assignable to type 'T'.

    Type 'UnpackedIntersectionWithNull, {}>' is not assignable to type 'T'.

    Hi again,

    for some reason I got the following error that comes from the MongoGenericRepository.

    Here's the mongo generic repo:

    import { Model } from 'mongoose';
    import { IGenericRepository } from 'src/core/abstracts/generic-repository.abstract';
    
    export class MongoGenericRepository<T> implements IGenericRepository<T> {
      private _repository: Model<T>;
      private _populateOnFind: string[];
    
      constructor(repository: Model<T>, populateOnFind: string[] = []) {
        this._repository = repository;
        this._populateOnFind = populateOnFind;
      }
    
      getAll(): Promise<T[]> {
        return this._repository.find().populate(this._populateOnFind).exec();
      }
    
      get(id: any): Promise<T> {
        return this._repository.findById(id).populate(this._populateOnFind).exec();
      }
    
      create(item: T): Promise<T> {
        return this._repository.create(item);
      }
    
      update(id: string, item: T) {
        return this._repository.findByIdAndUpdate(id, item);
      }
    }
    

    The error is in the get method, here's the whole traceback:

    src/infrastructure/data-services/mongo/mongo-generic.repository.ts:18:5 - error TS2322: Type 'Promise<UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>>' is not assignable to type 'Promise<T>'.
      Type 'UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>' is not assignable to type 'T'.
        'T' could be instantiated with an arbitrary type which could be unrelated to 'UnpackedIntersectionWithNull<HydratedDocument<T, {}, {}>, {}>'.
    
    return this._repository.findById(id).populate(this._populateOnFind).exec();
    

    For some reason I don't get it in your code but it arises in mine but the code is identical. Any ideas?

    opened by icodefish 1
  • TypeScript errors

    TypeScript errors

    Hi, nicely written article, however running the repo as-is according to the README, and I am seeing TypeScript errors.

    These might be the same type mismatch errors reported in this issue:

    https://github.com/royib/clean-architecture-nestJS/issues/2

    $ npm run start
    
    > [email protected] start
    > nest start
    
    src/frameworks/data-services/mongo/mongo-data-services.service.ts:33:55 - error TS2345: Argument of type 'Model<AuthorDocument, {}, {}, {}, any>' is not assignable to parameter of type 'Model<Author, {}, {}, {}, any>'.
      Types of property 'bulkWrite' are incompatible.
        Type '{ (writes: AnyBulkWriteOperation<AuthorDocument>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }' is not assignable to type '{ (writes: AnyBulkWriteOperation<Author>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }'.
          Types of parameters 'writes' and 'writes' are incompatible.
            Type 'AnyBulkWriteOperation<Author>[]' is not assignable to type 'AnyBulkWriteOperation<AuthorDocument>[]'.
              Type 'AnyBulkWriteOperation<Author>' is not assignable to type 'AnyBulkWriteOperation<AuthorDocument>'.
                Type '{ insertOne: InsertOneModel<Author>; }' is not assignable to type 'AnyBulkWriteOperation<AuthorDocument>'.
                  Type '{ insertOne: InsertOneModel<Author>; }' is not assignable to type '{ insertOne: InsertOneModel<AuthorDocument>; }'.
                    The types of 'insertOne.document' are incompatible between these types.
                      Type 'OptionalId<Author>' is not assignable to type 'OptionalId<AuthorDocument>'.
                        Type 'OptionalId<Author>' is missing the following properties from type 'Pick<AuthorDocument, keyof Author | keyof Document>': title, close, normalize, URL, and 247 more.
    
    33     this.authors = new MongoGenericRepository<Author>(this.AuthorRepository);
                                                             ~~~~~~~~~~~~~~~~~~~~~
    src/frameworks/data-services/mongo/mongo-data-services.service.ts:34:51 - error TS2345: Argument of type 'Model<BookDocument, {}, {}, {}, any>' is not assignable to parameter of type 'Model<Book, {}, {}, {}, any>'.
      Types of property 'bulkWrite' are incompatible.
        Type '{ (writes: AnyBulkWriteOperation<BookDocument>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }' is not assignable to type '{ (writes: AnyBulkWriteOperation<Book>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }'.
          Types of parameters 'writes' and 'writes' are incompatible.
            Type 'AnyBulkWriteOperation<Book>[]' is not assignable to type 'AnyBulkWriteOperation<BookDocument>[]'.
              Type 'AnyBulkWriteOperation<Book>' is not assignable to type 'AnyBulkWriteOperation<BookDocument>'.
                Type '{ insertOne: InsertOneModel<Book>; }' is not assignable to type 'AnyBulkWriteOperation<BookDocument>'.
                  Type '{ insertOne: InsertOneModel<Book>; }' is not assignable to type '{ insertOne: InsertOneModel<BookDocument>; }'.
                    The types of 'insertOne.document' are incompatible between these types.
                      Type 'OptionalId<Book>' is not assignable to type 'OptionalId<BookDocument>'.
                        Type 'OptionalId<Book>' is missing the following properties from type 'Pick<BookDocument, "title" | "publishDate" | "close" | "normalize" | "author" | "genre" | "URL" | "alinkColor" | "all" | "anchors" | "applets" | "bgColor" | "body" | ... 244 more ... | "evaluate">': close, normalize, URL, alinkColor, and 246 more.
    
    34     this.books = new MongoGenericRepository<Book>(this.BookRepository, [
                                                         ~~~~~~~~~~~~~~~~~~~
    src/frameworks/data-services/mongo/mongo-data-services.service.ts:38:53 - error TS2345: Argument of type 'Model<GenreDocument, {}, {}, {}, any>' is not assignable to parameter of type 'Model<Genre, {}, {}, {}, any>'.
      Types of property 'bulkWrite' are incompatible.
        Type '{ (writes: AnyBulkWriteOperation<GenreDocument>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }' is not assignable to type '{ (writes: AnyBulkWriteOperation<Genre>[], options: BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], callback: Callback<...>): void; (writes: AnyBulkWriteOperation<...>[], options?: BulkWriteOptions & MongooseBulkWriteOptions): Promise<...>; }'.
          Types of parameters 'writes' and 'writes' are incompatible.
            Type 'AnyBulkWriteOperation<Genre>[]' is not assignable to type 'AnyBulkWriteOperation<GenreDocument>[]'.
              Type 'AnyBulkWriteOperation<Genre>' is not assignable to type 'AnyBulkWriteOperation<GenreDocument>'.
                Type '{ insertOne: InsertOneModel<Genre>; }' is not assignable to type 'AnyBulkWriteOperation<GenreDocument>'.
                  Type '{ insertOne: InsertOneModel<Genre>; }' is not assignable to type '{ insertOne: InsertOneModel<GenreDocument>; }'.
                    The types of 'insertOne.document' are incompatible between these types.
                      Type 'OptionalId<Genre>' is not assignable to type 'OptionalId<GenreDocument>'.
                        Type 'OptionalId<Genre>' is missing the following properties from type 'Pick<GenreDocument, "name" | keyof Document>': title, close, normalize, URL, and 247 more.
    
    38     this.genres = new MongoGenericRepository<Genre>(this.GenreRepository);
                                                           ~~~~~~~~~~~~~~~~~~~~
    src/frameworks/data-services/mongo/mongo-generic-repository.ts:18:5 - error TS2322: Type 'Promise<UnpackedIntersection<HydratedDocument<T, {}, {}>, {}>>' is not assignable to type 'Promise<T>'.
      Type 'UnpackedIntersection<HydratedDocument<T, {}, {}>, {}>' is not assignable to type 'T'.
        'T' could be instantiated with an arbitrary type which could be unrelated to 'UnpackedIntersection<HydratedDocument<T, {}, {}>, {}>'.
    
    18     return this._repository.findById(id).populate(this._populateOnFind).exec();
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Found 4 error(s).
    
    
    opened by ndias-bt 0
  • Add env.example file

    Add env.example file

    Hi,

    I went through your work. Sincerely, I love what you have done so far.

    Can you add an env.example file to show the env variables and their types. This will help reduce the complexities involved in using the env variables as the codebase grows.

    opened by devcraft1 1
  • How to add Sequelize?

    How to add Sequelize?

    Hi, thank you for your work. How can I replace mongo with sequelize?

    I've created a data-services/mysql/mysql.module following the example of mongo you provided. However I get a lot of Type errors like: Property 'findOneById' does not exist on type 'Model<T, T>'.ts(2339)

    Can you provide an example?

    Thanks

    opened by mattia-beta 0
  • Using Framework import in core module

    Using Framework import in core module

    Hi,

    Thanks for your article. I noticed that in your core module you are using element from class-validator and @nestjs/mapped-types. Does the core module shouldn't use that king of stuff

    opened by MTA1711 2
  • Incompatible types

    Incompatible types

    Hi,

    great article but here's the problem I came across while looking through your code. In your MongoDataServices class you import your Author, Book and Genre from your mongo models but the abstract class IDataServices makes use of Author, Book and Genre that come from entites of the core directory. And these classes are different so my linter cries with errors.

    opened by icodefish 2
Owner
Full Stack Lead Developer
null
Ecommerce-backend-nestjs - Ecommerce app with Nestjs + Prisma ORM + GraphQL + SQLite

ECOMMERCE BACKEND NESTJS APP Nestjs + Prisma ORM + GraphQL + SQLite USER Create Account Login Product Create Product Get Products Get Product Search P

Rui Paulo Calei 5 Apr 6, 2022
Starter template for NestJS 😻 includes GraphQL with Prisma Client, Passport-JWT authentication, Swagger Api and Docker

Instructions Starter template for ?? NestJS and Prisma. Checkout NestJS Prisma Schematics to automatically add Prisma support to your Nest application

notiz.dev 1.6k Jan 4, 2023
around nestjs, with prisma and some graphql lib,write less code,create power api

介绍 这是一个 prisma + nestjs + graphql 的集成示例 对于开发者来说,特别是使用 graphql 的时候,只需要写非常少量的代码即可完成数据的各种操作,同时也支持接口透传。 开发&部署 本地开发 npm run start:dev swagger 地址:http://loc

芋头 26 Nov 24, 2022
Simple CRUD application with Nestjs, Prisma and Docker with Postgres

A progressive Node.js framework for building efficient and scalable server-side applications. Description Nest framework TypeScript starter repository

Torr7s 5 Nov 6, 2022
Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture, inspired by Hapi and Express.

Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture, inspired by Hapi and Express.

Jared Hanson 5 Oct 11, 2022
This is a repository that contains an simple NestJS API about Movies developed at Blue EdTech.

NestJS Movies Technologies and requirements NestJS JavaScript TypeScript Prisma MySQL Project This is a repository that contains an simple NestJS API

Isabella Nunes 2 Sep 28, 2021
Nestjs boilerplate for beginners.

Description Nest framework boilerplate. Installation $ npm install Running the docker for postgres or simply update env with running postgres values $

Harris Gurung 18 Nov 30, 2022
The nestjs cache module based on cache-manager & decorators 🍃

A progressive Node.js framework for building efficient and scalable server-side applications. zirus-cache zirus-cache for Nest.JS - simple and modern

Yakov Bobroff 4 May 9, 2022
First NestJS project powered by TypeScript (Simple CRUD)

First Nest TS (TypeScript) First NestJS project powered by TypeScript (Simple CRUD) Routes Get All GET http://localhost:3000/products/ Get one GET htt

Max Base 3 Feb 22, 2022
NestJS + MikroORM example repository for testing within transactional contexts. Achieve a much faster test suite.

Description Nest NestJS + MikroORM example repository for testing within transactional contexts. Running tests in transactions will speedup your test

Tolga Paksoy 5 Dec 20, 2022
API Back-End de NodeJS com NestJS

A progressive Node.js framework for building efficient and scalable server-side applications. Description Nest framework TypeScript starter repository

Glaucio Daniel 5 May 29, 2022
Nestjs + Typescript + GraphQL

Nestjs Boilerplate: Nestjs + Typescript + GraphQL How to run Install dependencies yarn install Start server for development yarn start:dev Start ser

doankhoi 3 Jun 27, 2022
ObjectionJS on steroids for your nestjs application!

Introduction Almost every application nowadays, requires a database to persist data. We have integrated ObjectionJS as our supported ORM. ObjectionJS

Squareboat 7 Dec 15, 2022
external eventbus module for @nestjs/cqrs

A Cqrs External EventBus module for Nest framework (node.js) Now available: Redis-EventBus Installation with npm npm install --save nest-external-even

H.John Choi 9 Dec 23, 2022
This is a boilerplate for Nodejs (Nestjs/typescript) that can be used to make http server application.

Hexagonal architecture Table of Contents Overview Code architecture source code Service build information Regular user Advanced user Deployment Helm K

Moeid Heidari 20 Sep 13, 2022
🦁 REST API for the NestJS + Next.js Todo application

Create new NestJS project $ npm i -g @nestjs/cli $ npm i -g yarn $ nest new api-lesson # set strict true in tsconfig.json Install packages # install

Udemy course repositories by Kazu T+ 21 Jan 4, 2023
Bulk follow GitHub users using a NodeJS script.

Github bulk follow Getting Started Prerequisites Clone the project to your local environment: git clone [email protected]:farid-ouachrar/github-bulk-

Farid Ouachrar 3 Sep 27, 2021
just a graphql example created by typescript + fastify + mikro-orm(postgresql) + mercurius(graphql adaptor) + type-graphql

fastify-mikro-orm-mercurius-graphql-example A MikroORM boilerplate for GraphQL made with Fastify, Mercurius, Typescript using TypeGraphQL ?? Packages

Vigen 10 Aug 28, 2022
graphql-codegen plugin to generate type-safe, easy-to use hooks for Flutter

graphql-codegen-flutter-artemis-hooks This is graphql-codegen plugin to generate type-safe, easy-to use Flutter artemis hooks. For further detail, see

seya 18 Jan 2, 2023