上一篇文章介紹過Nest後,我們今天要來時做一個登入系統
老話一句: 有任何錯誤都歡迎留言給我
那我們話不多說進入今天的文章
流程
登入系統分為兩個部分: 1. 註冊 2. 登入
註冊
註冊首先會先確認是否有該使用者,確定沒有以後會將新使用者的密碼用 bcrypt 這個套件做基本的加密,然後回傳 JWT 給前端,前端之後有需要打 Api 都要拿這組 JWT 來驗證是否為該使用者
登入
登入步驟稍微多一點點,首先須先確認使用者的密碼正確,然後使用驗證 JWT 是否一致,確定無誤以後,才能登入成功
環境下載
先創立一個 Nest 的專案,project-name 這邊名字自己取就可以了
$ npm i -g @nestjs/cli
$ nest new [project-name]
下載完以後,我們會需要下載一些,等一下要用到的套件
npm i @nestjs/jwt @nestjs/passport @types/passport-jwt passport passport-jwt
以上都下載完以後,我們現在開始實作,如何連線到 PostgreSql
連線 DB PostgreSql
npm install --save @nestjs/typeorm typeorm pg
這邊推薦一篇文章,可以快速的建立本地端的 PostgreSQL server,並透過 pgAdmin 做圖形化的管理
以後會專門做影片跟大家講解
開始實作
nest g res [資料夾名稱]
ex. nest g res user
透過 nest g res [資料夾名稱]
我們可以快速的建立一個 resource
,生成的程式碼中,有兩個比較特別 dto 跟 entities:
dto: dto 我們前一篇介紹過了,這篇就不多做解釋
entities: entities 就是建立 database tables 的地方
設計 table
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
@Column()
password: string;
}
PrimaryGeneratedColumn 是這個 table 的主鍵(primary key)
Column 代表 table 裡面的其他欄位
在 service 中調用 db 的資料
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
// 將 DB 注入到 service 中
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
// ...CRUD functions
}
註冊
controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto/registerDto';
@Controller('user')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('/register')
async register(@Body() registerDto: RegisterDto) {
return this.authService.register(registerDto);
}
}
service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { User } from '@/entity/user.entities';
import { RegisterDto } from './dto/registerDto';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
private jwtService: JwtService,
) {}
// 註冊
async register(registerDto: RegisterDto) {
const { email, password } = registerDto;
// 透過 bcrypt 幫明碼加密
const salt = await bcrypt.genSalt();
const passwordHash = await bcrypt.hash(password, salt);
try {
// 因為我們的 email 在 entity 有定義為 unique
// 所以如果輸入 db 裡已經有的 email 會 response error
this.usersRepository.save({
email,
password,
});
return {
status: HttpStatus.OK,
message: 'User registered successfully',
};
} catch (error) {
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
registerDto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class RegisterDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsNotEmpty()
password: string;
}
登入
controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/loginDto';
@Controller('user')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('/login')
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
}
service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { User } from '@/entity/user.entities';
import { LoginDto } from './dto/loginDto';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
private jwtService: JwtService,
) {}
// 登入
async login(loginDto: LoginDto) {
const { email, password } = loginDto;
const user = await this.usersRepository.findOne({ where: { email } });
// 如果沒有該使用者就回傳,找不到該使用者
if (!user) {
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
}
// 這邊需要特別注意,compare 後方的兩個欄位不能放錯
// 第一個欄位是使用者輸入的密碼(明碼)
// 第二個欄位是我們儲存在 db 裡面的密碼(暗碼)
const isSameUser = await bcrypt.compare(password, user.password);
// 密碼錯誤
if (!isSameUser) {
throw new HttpException('Invalid credentials', HttpStatus.BAD_REQUEST);
}
// 如果確定為本人,則把 requests 包裝成 jwt 回傳給前端,作為之後 call api 的鑰匙
const access_token = await this.jwtService.signAsync(loginDto);
return {
status: HttpStatus.OK,
message: 'User logged in successfully',
access_token,
};
}
}
loginDto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class LoginDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsNotEmpty()
password: string;
}