Nex.js is a modern enterprise Node.js Web framework. I have recently used nex.js to practice some projects and summed up some experience, but also learned a lot from it.

1. API sets the global prefix

Setting a global prefix for the API differentiates interface versions, such as the usual/API /v1 prefix for API endpoints.

Why do we need prefixes? Good apis are designed with backward compatibility in mind. “When we enhance or add an API, we should ensure that businesses that are already using the API online are not affected.” In short, API prefixes are for backward compatibility.

2. Module division

Nest.js is based on a modular structure. The server application should be divided into several parts by function. In general, your directory structure should be divided into modules rather than folders by type.

Here is a folder classification by type (not recommended)

Here are folders by module (recommended)

For Nex.js, a module is a folder containing the.module.ts file, which contains an @module ({}) decorator. But not every folder needs to have a.module.ts file. For example, you can create a folder called utils to store your utility functions or JSON files.

By organizing files into module folders, it becomes clear and many errors can be avoided. Also, if you don’t follow this principle, Nest.js may crash during build.

3. Use DTOs

DTO = Data transfer object. Dtos are like interfaces, the goal is to transfer data and validate it, primarily for routers/controllers.

You can use them to simplify THE API body and query validation logic. For example, the following AuthDto automatically maps the user email and password to the object DTO enforce authentication.

In the example above where passwords are expected to be longer than 5 characters, you can pair the DTOS with the class-Validator package to automatically throw errors.

4. Use the Data Mapper/Repository pattern instead of Active Record

If you’re using relational databases such as PostgreSQL or MySQL, use TypeOrm, one of Typescript’s most powerful ORMs.

TypeOrm can use two modes, the active record mode popularized by Ruby on Rails and the data mapper mode using the repository.

With the Active Record method, you can define all query methods within the model itself and use the model methods to save, delete, and load objects. Here is what the Active Record mode looks like:

const user = new UserEntity();
user.name = "Vladimir";
user.job = "programmer";
await user.save();
Copy the code

With the Data Mapper method, you can define all query methods in a separate class called a “repository” and use the repository to save, delete, and load objects:

const user = this.userRepository.create();
user.name = "Vladimir";
user.job = "programmer";
await this.userRepository.save(user);
Copy the code

While active records may seem better at first glance, they violate the modularity provided by Nest.js, because active records work with global entities, whereas the data mapper needs to inject entities into each module before using them.

A data mapper may seem a bit verbose, but it is a better solution for medium/large projects. It’s also great for testing because it’s good for dependency injection!

5. Use relative paths, not absolute paths

You can import es6 modules using absolute or relative paths. But nest.js crashes when it uses absolute paths in development and builds the app again.

// relative imports
import { SecurityService } from '.. /security/security.service';
import { CommentService } from '.. /comment/comment.service';

// absolute imports
import { SecurityService } from 'src/security/security.service';
import { CommentService } from 'src/comment/comment.service';
Copy the code

6. Use Exclude to hide unnecessary data

It is common to use filters to retrieve data from a database. The whole goal of a filter is to delete or format data from a database. This leads to a lot of junk logic and makes the code more redundant. If you need to hide certain fields, you can use the @exclude () decorator.

import { Exclude } from 'class-transformer';

export class UserEntity {
  id: number;
  firstName: string;
  lastName: string;
  @Exclude()
  password: string;
  constructor(partial: Partial<UserEntity>) {
    Object.assign(this, partial); }}Copy the code

7. Use entity getters

Some generic logic can be added directly to your entity logic as properties. The most common use cases have to do with password hashing and getting full names, where getters can be used, but be careful not to overdo it and burden entities with a lot of business logic.

import { Exclude } from 'class-transformer';

export class UserEntity {
  id: number;
  firstName: string;
  lastName: string;
  get fullName() {
    return this.firstName + "" + this.lastName; }}Copy the code

8. Export using a set name

Instead of importing your classes from different files, you can import them all from the same folder. If there are the following directories:

// index.ts
export * from './createPost.dto';
export * from './editPost.dto';
export * from './editPostCategory.dto';
export * from './editPostStatus.dto';

/ / recommend
import { CreatePostDto, EditPostDto } from './dto';

/ / do not recommend
import { CreatePostDto } from './dto/createPost.dto';
import { EditPostDto } from './dto/editPost.dto';
Copy the code