My journey in NestJS starts from here

My journey in NestJS starts from here

Why NestJS happens to be?

Because of Node.js echo system. What is Node.js echo system?

  • No assumption by default
  • No extra package, Just a bare-bon which supports essentials
  • Minimalist setup. If I wanna restate it more vividly I would say you're in charge about almost everything. This gave us great flexibility, but also it would be too much tedium and meticulous.

First, you have to know what is NestJS, Here is the most simplest definition- Obviously IMO:

  • It is an abstraction on top of traditional Node.js server side frameworks - ExpressJS and Fastify
  • It is a complete, awesome, extensible, clean framework.
  • It has IoC/Dependency Injection.
  • Abstracted unnecessary details
  • A boilerplate and more important a comprehensive architecture.
  • A full fledged toolkit

Jargons and concepts in NestJS

  • Routing with decorators

You can use @Get(), @Post(), etc to specify the endpoint HTTP method. In the parenthesis you can pass a string as the pas for that endpoint. e.x. @Get('/users/:id'). One quick help and very useful in developing a good RESTful API, use :id when the id is resource id - In other word do this if necessary @Get('/users/:postId').

  • Swagger for endpoints - endpoint itself, input/output DTO

DTOs will be used to validate input and specify the response schema. In a more mature system our DTOs should be mapped to the database and specify which fields should be selected and how they should be returned to the client.

In DTOs we also tends to keep our eyes on user's role to prevent exposing too much data. In general we will have a generic mapper function for each resource. Then we have custom DTOs for each endpoint which says which fields should be selected from database and it should be in the response if user has X role.

  • Exception Filters
  • Guards

Used in authentication and authorization. We do have access to the execution context in the guards. Guards is executed after all middlewares, before pipes, interceptors, and controllers. Guards have to implement CanActivate interface.

For example for a simple RBAC you can define a custom decorator named Roles which accepts needed roles to access that endpoint and then another guard which check if the user/jwt payload has those roles.

We can apply guards globally, or endpoint level, or controller level. In the global one we can ignore then those endpoint/controllers with attaching extra data to the request and use it in the guard. read this stackoverflow Q&A.

  • Providers:

DI comes into play. Any provider should be annotated with @Injectable decorator and listed in the providers. We usually do create a controller, and multiple service, just to have a neat, modular, SOLID codebase. In other word for sake of separation of concerns we do use MVCS architecture.

In NestJS we do follow singleton design pattern which implies that providers usually are alive as long as our app is up. But we can also create request scope provider. The other important notion in providers is that you should export soly those where you need them in other modules.

When you faced with circular dependency use forwardRef function -?forwardRef(() => UsersModule) - in imports instead of normal imports. The other portion is that your code should not rely on which constructor would be called in this scenario.

  • Modules

Gather close application domains in one module.

  • Unit test
  • E2E test
  • Pipes

Methods where runs/interpose just before the controller. Most common examples: app.useGlobalPipes where adds global pipelines and @UsePipe where only works for that controller. Usually we use them for validation, and transforming. e.x.

// Controller level pipelines

@Param('id', ParseIntPipe)

@Param('id', new ParseIntPipe(/*In case that you wanna do specific configuration*/))

// Global level pipelines

app.useGlobalPipes(new ValidationPipe(), new ValidationPipe())

providers: [

{ provider: APP_PIPE, useClass: ValidationPipe }

]

  • Interceptors

Run the following command in the terminal to create a new App

nest new app-name        

Configure tsconfig:

  • "alwaysStrict": "true"
  • "noImplicitAny": "true"
  • "experimentalDecorators": "true"

then you have to install the following packages in your app

pnpm add helmet csurf winston express-winston nest-winston cookie-parser class-validator class-transformer passport passport-local passport-jwt @nestjs/passport @nestjs/jwt?@nestjs/config argon2        

  • csurf to protect your app against XSS attacks
  • argon2 to hash things - Password, otp codes, etc - in a contemporary fashion.
  • cookie-parser to config the csurf correctly
  • wisnton to unify your logs
  • express-winston to log your incoming requests
  • nest-winston a module wrapper to use winston in the NestJS
  • class-validator to validate your DTOs with decorators & non-decorators validation
  • class-transformer util to validate nested objects in the DTOs. It transforms a plain object into some instance of class and versa.
  • passport to implement authentication as an express middleware
  • passport-local to config login process with email & password
  • passport-jwt to implement JWT strategy in passport
  • @nestjs/jwt utility to use the JWT in the NestJS
  • @nestjs/passport utility to implement passport in the NestJS
  • @nestjs/config to implement reading environmental variable from the .env
  • helmet another layer to secure your express app, in this case, your NestJS app

In case you have MongoDB as your database:

npm i mongoose mongodb @nestjs/mongoose        

  • @nestjs/mongoose module to configure your mongoose connection and schemas
  • mongodb official driver to connect to the MongoDB instance
  • mongoose is an ODM to communicate with MongoDB

Installing development dependencies in your app, some DefinitelyTyped packages, and also some other packages:

npm i -D @types/cookie-parser @types/csurf @types/passport-jwt        

  • We do not use husky in our project. Due to git hooks.

No alt text provided for this image

Now you have to config the @nestjs/config package to read the env from a .env files or from the OS exported envs. As I love to keep my `app.module.ts` file as clean as is possible I wrote the way I separate the different environmental variables in different config files:

mkdir src/configs
touch src/configs/auth.config.ts src/configs/web-app.config.ts src/configs/winston.config.ts src/configs/mongodb.config.ts        

Ignore each one that does not make sense to you. Now just as an example, you can see my mongodb.config.ts:

No alt text provided for this image

You will see how we can use this envs in action a little down. Now just let me to complete ConfigModule setup. Open the app.module.ts and add the following code in the imports section:

No alt text provided for this image

With the isGlobal conf we lessen the burden of importing ConfigModule for each module. Obviously if you wanna introduce each module's conf in it this config is not so handy.

Let's use @nestjs/config package:

No alt text provided for this image

Now we can config our MongoDB, open the app.module.ts file, and add the following code in the imports:

No alt text provided for this image

As you can see I would like to config the mongoose asynchronously. The entered string defined in the mongodb.config.ts file. And this is how you can use the ConfigService in the other modules. First, you have to inject them via DI and then use them.

Now it's time to define our entities. note that we define entities for each module like this:

No alt text provided for this image

As you see I define some nested schemas to make my entity definition cleaner. And this is how you can define nested schemas:

No alt text provided for this image


Now it is time for introducing our models to the mongoose. So we need to go to the created module and in its `imports` add the following codes:

No alt text provided for this image

I like one thing here, your collection name would be completely explicit. Because we set it With the `Test.name`.

No alt text provided for this image

You install it before. now its time to use it as a global middleware in the main.ts, It is not that simple. you need to pass its configs to it too. Or you can out its configurations in another file in configs directory like CORS.

app.use(helmet({
? ? contentSecurityPolicy: true,
? ? crossOriginEmbedderPolicy: true,
? ? crossOriginOpenerPolicy: true,
? ? crossOriginResourcePolicy: true,
? ? dnsPrefetchControl: true,
? ? expectCt: true,
?   frameguard: true,
? ? hidePoweredBy: true,
? ? hsts: true,
? ? ieNoOpen: true,
? ? noSniff: true,
? ? originAgentCluster: true,
? ? permittedCrossDomainPolicies: true,
? ? referrerPolicy: true,
? ? xssFilter: true,
}));        
No alt text provided for this image

Cross-origin resource sharing (CORS) is a mechanism that allows resources to be requested from another domain.?Now I wanna config the CORS in a separated file, in the configs directory:

No alt text provided for this image

Now we will config the cors in the main.ts in its bootstrap function:

const { corsConfigs } = corsConfigsGenerator();
app.enableCors(corsConfigs);        
No alt text provided for this image

Now we wanna protect our app against CSRF attacks. As my experience shows me I have to define a middleware to handle it:

No alt text provided for this image

and another middleware for the errors:

No alt text provided for this image

Now we can config csurf in the `bootstrap` function:

No alt text provided for this image

You also have to config the cookie-parser as well:

app.use(cookieParser());        
No alt text provided for this image

Now assume you wanna config the port of your NestJS app. It should be read from the env file. So we can use ConfigService like this in the main.ts in the bootstrap function:

const configService = app.get(ConfigService);
const appConfigs = configService.get<webAppConfigs>(
    'webAppConfigs',
);

// ...
await app.listen(appConfigs.port);        

You can access other configured envs in this way in the `main.ts`

No alt text provided for this image

Now its time to configure our Winston, So in the `src/configs/winston.config.ts` write the following configuration:

No alt text provided for this image

Now we have to config it in the `app.module.ts`:

No alt text provided for this image

and if you wanna change the whole NestJS logger you can add the following configurations in the `main.ts` file into the `bootstrap` function to change it to the wisnton:

const nestWinston = app.get(WINSTON_MODULE_NEST_PROVIDER);
app.useLogger(nestWinston);        
No alt text provided for this image

If you need a very simple JWT based authentication you can clone my repo from the this GitHub repositiry & checkout to this commit sha:

1af64a56f6f41c4b9b7ccb3aa9df624658874310

You need to create a new module with the `nest g module auth` and then you can start defining the JWT guard in the guards' directory:

No alt text provided for this image

Now you can implement multiple strategies as your business needed. In this case I just want to define the JWT strategy:

No alt text provided for this image

If you had a mindset like me you need to define the decoded type of the JWT token in the types directory as I did:

No alt text provided for this image

And in the `auth.module.ts` we move our last chess:

No alt text provided for this image

To make it works we need to import the auth module in the `app.module.ts`, Therefore I import the auth module in the `app.module.ts` and add it in the imports:

AuthModule        

Now we can create other modules...

keep calm and you can see the final result here in my repo

Any question? feel free to ask, As I could I will answer you.

From Iran, Afghan developer. Kasir barati.

要查看或添加评论,请登录

Mohammad Jawad barati的更多文章

  • Write test for nanostores

    Write test for nanostores

    why I wrote this post in the first place? Lack of tut and doc around this awesome library that makes our lives easier…

    1 条评论
  • My very own Linux cheat sheet

    My very own Linux cheat sheet

    HAL: Hardware Abstraction Layer do abstract hardwares for us /sys is where we can see what is connected to the system…

    1 条评论
  • ?????? ???? ?? ?????

    ?????? ???? ?? ?????

    ?? ???? ????: ???? ???? 43 ???? ?? (????? ?? ??????) ?? ??? ???? ???? ? ??????? ?????. ??? ??? ??????? ??? 56 ???? ??…

    2 条评论
  • Angular Material UI

    Angular Material UI

    IMO you need to know 3 things before reading this article: Authentication and Authorization in Angular. Whether you…

  • Create Angular project

    Create Angular project

    First please note that the following instructions are totally biased and is my favorite way of doing it. BTW if you…

  • Learn Angular fundamentals.

    Learn Angular fundamentals.

    Note that I am still a backend developer who is dealing with frontend stuff BTW it is not so hard. IMO Angular is much…

  • NestJS task scheduler

    NestJS task scheduler

    To tackle queue issues in NestJS we use @nestjs/schedule. With this package we can define declarative cron jobs.

  • OTP code in Node.js

    OTP code in Node.js

    Send a time-based OTP code - define TTL for the code Keep the symetric keys very safe Use approved cryptographic…

  • Refresh Token + Logout functionality in Node.js

    Refresh Token + Logout functionality in Node.js

    Here is how I implement refresh token in Node.js.

  • My journey in gPRC

    My journey in gPRC

    I am just fooling around gPRC. Please do not count this article as complete right now.

社区洞察

其他会员也浏览了