Good day to you. This series will share with you how to build a web application with server, client side, and you will learn many useful things on this project.
As the title you will learn how to 
- Use NestJs build a server connect with a MongoDB (use JWT for authentication, how to query on server, create schema, entity, controller, service)
- Use NextJS build a client side with TailwindCss fast build component, Zustand state management (easy manage state), Axios Request API.
After check all this full series, I believe you will have a mindset how to build a website app from backend to frontend. You will understand how to become a full stack developer through this todo web app
Full Tutorial TodoList Apps with NextJS Zustand Axios TailwindCSS React NestJS Mongoose JWT (Day 3)
On day 3: We will focus on how to work with JWT (Json Web Token) for storing User information
Step 0: prepare, check below package.json and go to install it first
{
   "name": "server",
   "version": "0.0.1",
   "description": "",
   "author": "",
   "private": true,
   "license": "UNLICENSED",
   "scripts": {
     "prebuild": "rimraf dist",
     "build": "nest build",
     "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
     "start": "nest start",
     "start:dev": "nest start --watch",
     "start:debug": "nest start --debug --watch",
     "start:prod": "node dist/main",
     "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
     "test": "jest",
     "test:watch": "jest --watch",
     "test:cov": "jest --coverage",
     "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
     "test:e2e": "jest --config ./test/jest-e2e.json"
   },
   "dependencies": {
     "@nestjs/common": "^9.0.0",
     "@nestjs/config": "^2.2.0",
     "@nestjs/core": "^9.0.0",
     "@nestjs/jwt": "^9.0.0",
     "@nestjs/mongoose": "^9.2.1",
     "@nestjs/passport": "^9.0.0",
     "@nestjs/platform-express": "^9.0.0",
     "@nestjs/swagger": "^6.1.4",
     "@nestjs/typeorm": "^9.0.1",
     "bcrypt": "^5.1.0",
     "moment": "^2.29.4",
     "mongoose": "^6.8.4",
     "passport": "^0.6.0",
     "passport-jwt": "^4.0.1",
     "passport-local": "^1.0.0",
     "reflect-metadata": "^0.1.13",
     "rimraf": "^3.0.2",
     "rxjs": "^7.2.0",
     "typeorm": "^0.3.11"
   },
   "devDependencies": {
     "@nestjs/cli": "^9.0.0",
     "@nestjs/schematics": "^9.0.0",
     "@nestjs/testing": "^9.0.0",
     "@types/bcrypt": "^5.0.0",
     "@types/express": "^4.17.13",
     "@types/jest": "28.1.8",
     "@types/node": "^16.0.0",
     "@types/supertest": "^2.0.11",
     "@typescript-eslint/eslint-plugin": "^5.0.0",
     "@typescript-eslint/parser": "^5.0.0",
     "eslint": "^8.0.1",
     "eslint-config-prettier": "^8.3.0",
     "eslint-plugin-prettier": "^4.0.0",
     "jest": "28.1.3",
     "prettier": "^2.3.2",
     "source-map-support": "^0.5.20",
     "supertest": "^6.1.3",
     "ts-jest": "28.0.8",
     "ts-loader": "^9.2.3",
     "ts-node": "^10.0.0",
     "tsconfig-paths": "4.1.0",
     "typescript": "^4.7.4"
   },
   "jest": {
     "moduleFileExtensions": [
       "js",
       "json",
       "ts"
     ],
     "rootDir": "src",
     "testRegex": ".*\\.spec\\.ts$",
     "transform": {
       "^.+\\.(t|j)s$": "ts-jest"
     },
     "collectCoverageFrom": [
       "**/*.(t|j)s"
     ],
     "coverageDirectory": "../coverage",
     "testEnvironment": "node"
   }
 } 	
      Step 1: Create structure like this
Step 2: Type command: nest g resource users will auto generate above folder
Step 3: Implement below code on each below file
src/auth/guard/jwt-auth.guard.ts
 
 import { AuthGuard } from '@nestjs/passport';
 import { ExecutionContext, Injectable } from '@nestjs/common';
 
 @Injectable()
 export class JwtAuthGuard extends AuthGuard('jwt') {
   canActivate(context: ExecutionContext) {
     const functionName: string = context.getHandler().name;
     if (
       functionName === 'register' ||
       functionName === 'login' ||
       functionName === 'checkAccessToken'
     ) {
       // just allow register
       return true;
     }
 
     return super.canActivate(context);
   }
 }
    src/auth/constants.ts
 
 export const jwtConstants = {
   secret: 'secretKey-vilh',
 };
 
 src/jwt.config.ts
 
 import { JwtOptionsFactory, JwtModuleOptions } from '@nestjs/jwt';
 import { Injectable, Logger } from '@nestjs/common';
 import { jwtConstants } from './constants';
 
 @Injectable()
 export class JwtConfig implements JwtOptionsFactory {
   constructor() {}
 
   createJwtOptions(): JwtModuleOptions {
     return {
       secret: jwtConstants.secret,
       signOptions: {
         expiresIn: '500s', // '100s',
       },
     };
   }
 }
 src/auth/jwt.strategy.ts
 
 import { ExtractJwt, Strategy } from 'passport-jwt';
 import { PassportStrategy } from '@nestjs/passport';
 import { Injectable, HttpStatus, HttpException } from '@nestjs/common';
 import { UsersService } from 'src/users/users.service';
 import { jwtConstants } from './constants';
 
 export interface IJWT {
   id: string;
   iat: number;
   exp: number;
 }
 @Injectable()
 export class JwtStrategy extends PassportStrategy(Strategy) {
   constructor(private usersService: UsersService) {
     super({
       jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
       ignoreExpiration: false,
       // algorithms: ['RS256'],
       secretOrKey: jwtConstants.secret,
     });
   }
 
   async validate(input: IJWT): Promise<any> {
     const user = await this.usersService.validate(input.id);
     if (!user) {
       throw new HttpException('Unauthorized Access', HttpStatus.UNAUTHORIZED);
     }
     return user;
   }
 } 
    modified src/users/user.controller.ts
@UseGuards(JwtAuthGuard)
 
 // huuvi168@gmail.com
 // Zidane - Webzone Tech Tips, all things about web development
 import {
   Controller,
   Get,
   Post,
   Body,
   UseGuards,
   Request,
   Delete,
 } from '@nestjs/common';
 import { UsersService } from './users.service';
 import { UsersDto } from './dto/users.dto';
 import { JwtAuthGuard } from 'src/auth/guard/jwt-auth.guard';
 
 @Controller('users')
 export class UsersController {
   constructor(private readonly usersService: UsersService) {}
   
   @UseGuards(JwtAuthGuard)
   @Get('getProfile')
   public async getProfile(@Request() req: any) {    
     return await this.usersService.getProfile(req.user.params._id);
   } 
 } 
    
 
  
 import { ApiErrorResponse } from './../util/api-error-response.util';
import { ApiSucceedResponse } from 'src/util/api-success-response.util';
import { IUser } from './interface/users.interface';
import { Injectable, HttpException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import mongoose, { Model, Types } from 'mongoose';
import { UsersDto } from './dto/users.dto';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class UsersService {
  constructor(
    @InjectModel('User') private readonly userModel: Model<IUser>,
    private readonly jwtService: JwtService,
  ) {}
  // https://localhost:8000/users => ok
  public async getUsers() {
    const users = await this.userModel.find().exec();
    if (!users || !users[0]) {
      throw new HttpException('User Not Found', 404);
    }
    return new ApiSucceedResponse("Retrieved all users", users);
  }
  public async login(user: UsersDto) {
    const result = await this.userModel.findOne({
      username: user.username,
    });
    if (!result) {
      throw new HttpException('User not found', 404);
    }
    const isMatch = await bcrypt.compare(user.password, result.password);
    if (!isMatch) {
      throw new HttpException('Password is not match', 404);
    }
 
    const access_token = await this.jwtService.sign({ id: result._id });
    return new ApiSucceedResponse('Login succeed', access_token);
  }
  public async register(newUser: UsersDto) {
    // check exist
    const checkExist = await this.userModel.findOne({
      username: newUser.username
    }).exec()
    
    if (checkExist) {
      throw new HttpException(`username ${newUser.username} is Exist`, 404)
    }
    const salt = await bcrypt.genSalt();
    const hashPassword = await bcrypt.hash(newUser.password, salt);
    newUser.password = hashPassword;
    const user = await new this.userModel(newUser);
    user.save();
    if (user) {
      return new ApiSucceedResponse('Registered user successfully', user);
    }
    return new ApiErrorResponse('Registered user failed', []);
  }
  async validate(id: string) {
    const user = await this.getUserById(id);
    return user ? user : null;
  }
  public async getUserById(id: string) {
    const user = await this.userModel
      .findById({ _id: new mongoose.Types.ObjectId(id) }) // use this way for get mongo objectID
      .exec();
    if (!user) {
      throw new HttpException('User Not Found!', 404);
    }
    return new ApiSucceedResponse('Retrieved data successfully', user);
  }
  public async deleteUserById(id: string) {
    const user = await this.userModel
      .deleteOne({ _id: new mongoose.Types.ObjectId(id) })
      .exec();
    if (user.deletedCount === 0) {
      throw new HttpException('User Not Found', 404);
    }
    return new ApiSucceedResponse('User was removed', []);
  }
  public async putUserById(
    id: string,
    propertyName: string,
    propertyValue: string,
  ) {
    const user = await this.userModel
      .findOneAndUpdate(
        { _id: new Types.ObjectId(id) },
        {
          [propertyName]: [propertyValue],
        },
      )
      .exec();
    if (!user) {
      throw new HttpException('Not Found', 404);
    }
    return new ApiSucceedResponse('User was update succeed', user);
  }
  public async getProfile(id: number) {
    let user = await this.userModel
      .findById({
        _id: id,
      })
      .exec();
    if (!user) {
      throw new HttpException('User Not Found', 404);
    }
    return new ApiSucceedResponse('Retrieved data successfully', user);
  }
}  
   
 Remember put Users on Module file
src/users/users.module.ts
 
 import { MongooseModule } from '@nestjs/mongoose';
 import { Module } from '@nestjs/common';
 import { UsersService } from './users.service';
 import { UsersController } from './users.controller';
 import { UsersSchema } from './schemas/users.schema';
 
 import { jwtConstants } from 'src/auth/constants';
 import { JwtModule } from '@nestjs/jwt';
 import { PassportModule } from '@nestjs/passport';
 import { JwtStrategy } from 'src/auth/jwt.strategy';
 
 @Module({
   imports: [
     MongooseModule.forFeature([
       {
         name: 'User',
         schema: UsersSchema,
       },
     ]),
     PassportModule,
 
     JwtModule.register({
       secret: jwtConstants.secret,
       signOptions: {
         expiresIn: '3600s',
       },
     }),
   ],
   controllers: [UsersController],
   providers: [UsersService, JwtStrategy],
 })
 export class UsersModule {} 
   
     
 
 
 

