Background
Break News
How to add local font to Tailwind Css and NextJS? - Tutorial Design Pattern? - Blockchain Technology, How to create own Bitcoin virtual currency - Zustand mordern management state - Design Pattern - Flyweight Pattern? - Docker Full training Topic

[Project Day 3] Full Tutorial "Todo web app" with "NextJS Zustand Axios TailwindCSS React NestJS Mongoose" JWT - JWT part

Sunday 5 February 2023
|
Read: Completed in minutes

[Project Day 3] Full Tutorial "Todo web app" with "NextJS Zustand Axios TailwindCSS React NestJS Mongoose" JWT - JWT part

Hello everyone, welcome to Learn Tech Tips I am Zidane, (my nickname: ιΈ‘θ›‹)

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

Full Tutorial "Todo web app" with "NextJS Zustand Axios TailwindCSS React NestJS Mongoose" JWT - JWT part

Full Tutorial "Todo web app" with "NextJS Zustand Axios TailwindCSS React NestJS Mongoose" JWT - JWT part - Webzone Tech Tips Zidane

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 {} 
   
     
See you on day 4 πŸ’—
 
Thanks for reading. Any feedback and questions about Full Tutorial Todo web app with NextJS Zustand Axios TailwindCSS React NestJS Mongoose JWT (Day 3). Leave your comment on below post, we can discuss about it.
✋✋✋✋ Webzone Tech Tips - I am Zidane, See you next time



 

 


πŸ™‡πŸΌ We Appreciate Your Comments and Suggestions - Webzone - all things Tech Tips web development πŸ™‡πŸΌ
Popular Webzone Tech Tips topic maybe you will be like it - by Webzone Tech Tips - Zidane
As a student, I found Blogspot very useful when I joined in 2014. I have been a developer for years . To give back and share what I learned, I started Webzone, a blog with tech tips. You can also search for tech tips zidane on Google and find my helpful posts. Love you all,

I am glad you visited my blog. I hope you find it useful for learning tech tips and webzone tricks. If you have any technical issues, feel free to browse my posts and see if they can help you solve them. You can also leave a comment or contact me if you need more assistance. Here is my blog address: https://learn-tech-tips.blogspot.com.

My blog where I share my passion for web development, webzone design, and tech tips. You will find tutorials on how to build websites from scratch, using hot trends frameworks like nestjs, nextjs, cakephp, devops, docker, and more. You will also learn how to fix common bugs on development, like a mini stackoverflow. Plus, you will discover how to easily learn programming languages such as PHP (CAKEPHP, LARAVEL), C#, C++, Web(HTML, CSS, javascript), and other useful things like Office (Excel, Photoshop). I hope you enjoy my blog and find it helpful for your projects. :)

Thanks and Best Regards!
Follow me on Tiktok @learntechtips and send me a direct message. I will be happy to chat with you.
Webzone - Zidane (huuvi168@gmail.com)
I'm developer, I like code, I like to learn new technology and want to be friend with people for learn each other
I'm a developer who loves coding, learning new technologies, and making friends with people who share the same passion. I have been a full stack developer since 2015, with more than years of experience in web development.
Copyright @2022(November) Version 1.0.0 - By Webzone, all things Tech Tips for Web Development Zidane
https://learn-tech-tips.blogspot.com