Dev Conventions
  • Dev Conventions
  • Git
    • Branch Flow
    • Conventional Commits
    • Pull requests template
    • Before making the PRs in typescript, keep in mind
  • Typescript
    • Introduction
    • Good practices
    • Solid
    • Using pnpm for Package Management
    • NestJs
    • NextJs
    • React
    • NestJs Testing
    • React Testing
    • Npm Registry
  • PYTHON
    • Introduction
    • Good practices
    • Testing
    • Naming Convention
  • DevOps
    • Introduction
    • Github Actions
    • Creating a Github Actions Workflow
  • Agile
    • Story Points
Powered by GitBook
On this page
  • Introduction
  • StandardJS
  • Project Structure
  • Request Lifecycle
  • Middleware
  • Guards
  • Pipes
  • Filters
  • Naming Convention
  • Good Naming Practices
  1. Typescript

NestJs

PreviousUsing pnpm for Package ManagementNextNextJs

Last updated 2 months ago

Introduction

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 .

StandardJS

2-space Indentation:

Use of double quotation marks for strings:

Project Structure

Definitions

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.

Example

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:

  1. Integrations: Folder where all necessary integrations for asynchronous tasks are located, for example, the "bull" folder for integrations with the Bull library.

  2. Constants: Folder to define constants used in the module, such as constants for queue names, routes, etc.

  3. Interfaces: Folder where the necessary interfaces for the repository and the sportsbook model are defined.

  4. Controllers: Folder for controllers that handle HTTP requests related to the module.

  5. Services: Folder for services that contain the business logic of the module.

  6. Enums: Folder to define enumerations used in the module.

  7. Dto: Folder to define Data Transfer Objects (DTOs) used to transfer data between layers of the module.

  8. 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.

Structure

  • Sportbook interface:

  • Sportbook Repository Interface:

  • Sportbook Service

  • Sportbook Module

  • Sportbook Mongo

Request Lifecycle

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

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.

Guards

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

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

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.

Summary

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

Naming convention in TypeScript is essential for maintaining readable and consistent code. Here are guidelines and examples to follow these conventions:

Folders and Files

  • 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.

Classes

  • PascalCase: Class names should follow the PascalCase convention, and nest classes like modules, services, consumers should have a specification in the name.

Functions and Methods

  • 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.

Interfaces

  • PascalCase with 'I' Prefix: Interfaces should follow the PascalCase convention and use an 'I' prefix.

Variables, Properties, and Objects

  • camelCase: Variables, properties, and objects should follow the camelCase convention.

Constants

  • UPPER_CASE: Constants should follow the UPPER_CASE convention to distinguish them.

Enumerations

  • UPPER_CASE: Enumeration properties should follow the UPPER_CASE convention.

Good Naming Practices

English Language

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.

Naming Convention

Choose the camelCase naming convention and stick to it.

S-I-D (Short, Intuitive, Descriptive)

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.

Avoid Contractions

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.

Avoid Duplication of Context

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.

Reflect the Expected Outcome

A name should reflect the expected outcome.

Prefixes

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

StandardJS Docs
official NestJS documentation
StandardJS
typescript-eslint
2-space identation
Double quotation marks for strings
Sportbook interface
Sportbook repository interface
Sportbook Service
Sportbook Module
Sportbook Mongo
Request lifecycle
Guard1 will execute before Guard2, and both will run before Guard3.
Pipes