Implementing a sane backend in Node.js using NestJS: Project setup and structure

Piotr Jaworski
Jit Team
Published in
5 min readJun 30, 2021

Welcome to the second part of a series of articles about writing a real-life scalable, performant, and maintainable backend in Node.js. Be sure to read the previous parts:

Part I: Architecture

Stay tuned (or follow me) for the upcoming parts!

In my first article in this series, I briefly described the general architecture of the solution. Now, it’s time to go a bit deeper and explore how such a project can be set up and structured.

Let’s start with the setup. Firstly, we need to install the NestJs CLI that can be used to handle all actions related to NestJs, the most important of which is probably creating a new project:

Running this, as you might imagine, will install the NestJS CLI that will be extremely helpful. The CLI itself is required to initialize a new NestJS project. Still, we can also use it for some other routine tasks, including configuration, NestJS version updates, and, last but not least, generating new components. Let’s move on with the initialization! In our projects directory, we need to run:

NestJS CLI is well-mannered enough to ask whether we’d like to use npm or yarn and after that proceeds to scaffold the project and install all the dependencies. Let’s have a look at the structure out of the box:

├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

That is definitely not very impressive. I’m a fan of keeping models, controllers, and services together — the same as Mateusz Gajewski - Tuttle mentioned in this article. However, in the case of NestJS, this approach is not that straightforward. If we were to implement a simple controller with a service, repository, and model in, say, Spring Boot, it’s possible to keep those classes together, as they are usable as standalone. NestJS, however, utilizes the same Dependency Injection pattern that Angular uses, which means that for the same functionality as mentioned before, we’d need an additional file — a module. NestJS uses those to structure code, and it’s done by specifying controllers, providers, imports, and exports that make up for a module, in a manner very similar to Angular’s ngModule.

This makes placing services, controllers, models, etc., in dedicated folders a bit less sensible; since modules are supposed to keep all the dependencies of a single atomic block of the business domain together, it makes sense to put those blocks in separate folders. By default, NestJS generates those in folders placed directly in src but I prefer to immediately move them to a dedicatedmodels folder.

Let’s generate our first module, which will handle the cards in our API. NestJS CLI makes it possible to do this using a single command:

Here’s the result:

├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── cards
│ │ ├── cards.controller.spec.ts
│ │ ├── cards.controller.ts
│ │ ├── cards.module.ts
│ │ ├── cards.service.spec.ts
│ │ ├── cards.service.ts
│ │ ├── dto
│ │ │ ├── create-card.dto.ts
│ │ │ └── update-card.dto.ts
│ │ └── entities
│ │ └── card.entity.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

Whoah, that’s a whole lot of new files! As we can see, the CLI generated the controller, module, service, some DTOs, an entity, and even some test files! As we can see, all the files have been placed in a single folder — cards — placed directly in src. This is fine, but it’s easy to imagine that with a lot of resources and with some other useful components (like some custom decorators, providers, pipelines, filters, etc.), this approach can lead to a mess really quickly! To counter that, I’m going to move the cards into a dedicated models folder:

├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── main.ts
│ └── models
│ └── cards
│ ├── cards.controller.spec.ts
│ ├── cards.controller.ts
│ ├── cards.module.ts
│ ├── cards.service.spec.ts
│ ├── cards.service.ts
│ ├── dto
│ │ ├── create-card.dto.ts
│ │ └── update-card.dto.ts
│ └── entities
│ └── card.entity.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

This will make navigating around the code a bit easier later on — especially when we’ll start adding some additional components like pipes, filters, guards, or interceptors — let’s do that right away!

├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── auth.guard.spec.ts
│ ├── auth.guard.ts
│ ├── http-exception.filter.spec.ts
│ ├── http-exception.filter.ts
│ ├── logging.interceptor.spec.ts
│ ├── logging.interceptor.ts
│ ├── main.ts
│ └── models
│ └── cards
│ └── ...
│ ├── validation.pipe.spec.ts
│ └── validation.pipe.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

This doesn’t look too tidy — a quick and easy fix is to move all the newly generated components to their own new homes:

├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── filters
│ │ ├── http-exception.filter.spec.ts
│ │ └── http-exception.filter.ts
│ ├── guards
│ │ ├── auth.guard.spec.ts
│ │ └── auth.guard.ts
│ ├── interceptors
│ │ ├── logging.interceptor.spec.ts
│ │ └── logging.interceptor.ts
│ ├── main.ts
│ └── models
│ └── cards
│ └── ...
│ └── pipes
│ ├── validation.pipe.spec.ts
│ └── validation.pipe.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

That wraps it up for today — stay tuned for the next part! Sign up for our newsletter to stay up to date, and if you’re interested in how we can help you use technology to empower your business, be sure to visit our website!

This article is the first of the series about building a well-structured API in Node.js using NestJS and PostgreSQL:

--

--