NestJs
Last updated
Last updated
This is the development standards guide using the NestJS framework in TypeScript. It includes guidelines for folder structuring, coding practices, strict typing implementation, as well as recommendations for unit and end-to-end (e2e) testing, based on the . Our standards are based on rules from and .
For the backend in NestJS, a semi-hexagonal structure is proposed to address the general objectives of the architecture without the need to follow conventional folder structuring. In this proposal, the following folders are established:
"app" folder: Contains everything related to the main module of the application.
"health" folder: Includes the health module.
"shared" or "common" folder: Used to store common elements such as pipes, interceptors, database connections, environment configurations (using the ConfigService), filters, logger, global middlewares, guards, types, interfaces, and utilities.
"router" folder: Contains the router module with all the routes defined using the NestJS router module.
Additionally, depending on the size of the project, the modules can be organized directly in the "src" folder or in a separate folder called "modules". This structure provides clear and modularized organization for the backend in NestJS, allowing easy management and scalability of the project.
The semi-hexagonal structure is based on organizing a module, such as the Sportbook module to manage the data of a sportsbook for a betting house, in the following way:
Integrations: Folder where all necessary integrations for asynchronous tasks are located, for example, the "bull" folder for integrations with the Bull library.
Constants: Folder to define constants used in the module, such as constants for queue names, routes, etc.
Interfaces: Folder where the necessary interfaces for the repository and the sportsbook model are defined.
Controllers: Folder for controllers that handle HTTP requests related to the module.
Services: Folder for services that contain the business logic of the module.
Enums: Folder to define enumerations used in the module.
Dto: Folder to define Data Transfer Objects (DTOs) used to transfer data between layers of the module.
Module: Main module file where the different components of the module (controllers, services, etc.) are imported and configured.
Additionally, to give the semi-hexagonal structure, a specific folder for the database is created depending on the engine used. For example:
If MongoDB is used, a "mongo" folder is created where everything related to MongoDB is placed, such as schemas and MongoDB-specific repositories.
If working with a relational database like PostgreSQL, a "typeorm" folder is created where everything related to TypeORM is included, such as entities and PostgreSQL-specific repositories.
In this way, the module is organized following the principles of the semi-hexagonal architecture, facilitating its implementation and maintenance.
Sportbook interface:
Sportbook Repository Interface:
Sportbook Service
Sportbook Module
Sportbook Mongo
Nest handles requests and generates responses in a sequence we call the request lifecycle. With the use of middleware, pipes, guards, and interceptors, it can be challenging to track where particular code is executed during the request lifecycle, especially when global, controller-level, and route-level components come into play. Generally, a request flows through middleware to guards, then to interceptors, then to pipes, and finally back to interceptors on the return path (when the response is generated).
Middleware runs in a specific sequence. First, Nest executes globally bound middleware (such as middleware linked with app.use) and then executes module-bound middleware, which is determined on routes. The middleware runs sequentially in the order they are linked, similar to how middleware operates in Express.
The execution of guards begins with global guards, then proceeds to controller guards, and finally to route guards. Similar to middleware, guards are executed in the order in which they are linked. For example:
Pipes follow the standard sequence from global to controller to route, with the same first-in, first-out principle regarding the parameters of @usePipes(). However, at the route parameter level, if there are multiple pipes in operation, they will execute in the order from the last parameter with a pipe to the first. This also applies to pipes at the route and controller levels. For example, if we have the following controller:
Then, GeneralValidationPipe will run for the query, then for the parameters, and finally for the body objects before moving on to RouteSpecificPipe, which follows the same order. If there were specific pipes for parameters instead, they would execute (again, from the last to the first parameter) after the pipes at the controller and route level.
Filters are the only component that does not resolve globally first. Instead, filters resolve from the lowest level possible, meaning execution starts with any filter bound to the route and then moves to the controller level, and finally to global filters. Note that exceptions cannot pass from filter to filter; if a route-level filter catches the exception, a controller-level or global filter cannot catch the same exception. The only way to achieve an effect like this is to use inheritance among filters.
Overall, the request lifecycle looks like this:
Incoming request
Globally bound middleware
Module-bound middleware
Global guards
Controller guards
Route guards
Global interceptors (pre-controller)
Controller interceptors (pre-controller)
Route interceptors (pre-controller)
Global pipes
Controller pipes
Route pipes
Route parameter pipes
Controller (method handler)
Service (if any)
Route interceptor (post-request)
Controller interceptor (post-request)
Global interceptor (post-request)
Exception filters (route, then controller, then global)
Server response
Naming convention in TypeScript is essential for maintaining readable and consistent code. Here are guidelines and examples to follow these conventions:
Kebab-case: Folder and file names should follow the kebab-case convention.
Type Specification in Files: Files should clearly specify their type, for example, with extensions like **.guard**
, **.controller**
, **.service**
, **.util**
, etc.
PascalCase: Class names should follow the PascalCase convention, and nest classes like modules, services, consumers should have a specification in the name.
camelCase: Function and method names should follow the camelCase convention.
Decorator Functions: If a function acts as a decorator, the name may follow the PascalCase convention.
PascalCase with 'I' Prefix: Interfaces should follow the PascalCase convention and use an 'I' prefix.
camelCase: Variables, properties, and objects should follow the camelCase convention.
UPPER_CASE: Constants should follow the UPPER_CASE convention to distinguish them.
UPPER_CASE: Enumeration properties should follow the UPPER_CASE convention.
Use the English language when naming your variables and functions.
Whether you like it or not, English is the dominant language in programming: the syntax of all programming languages is written in English, as well as countless documentations and educational materials. By writing your code in English, you drastically increase its cohesion.
Choose the camelCase naming convention and stick to it.
A name should be short, intuitive, and descriptive:
Short. A name should not take long to be written and thus to be remembered;
Intuitive. A name should read naturally, as close to common speech as possible;
Descriptive. A name should reflect what it does or owns in the most efficient way.
Do not use contractions. They contribute to nothing more than decreasing the readability of the code. Finding a short and descriptive name might be hard, but contraction is not an excuse for not doing so.
A name should not duplicate the context in which it is defined. Always remove the context from a name if that does not decrease its readability.
A name should reflect the expected outcome.
The prefix enhances the meaning of a variable. It is rarely used in function names.
Is: Describes a feature or state of the current context (usually boolean).
Has: Describes whether the current context possesses a certain value or state (usually boolean).
Should: Reflects a positive conditional assertion (usually boolean) combined with a specific action.
Min/max: Represents a minimum or maximum value. It is used when describing limits or boundaries.
Singular and Plurals
Just like a prefix, variable names can be singular or plural depending on whether they contain a single value or multiple values.
These are just some of the StandardJS rules, and their application in TypeScript helps maintain clean and consistent code. More information at