寫在前面
Nest(NestJS)是用于建構高效,可擴充的Node.js伺服器端應用程式的架構。它使用漸進式JavaScript,并已建構并完全支援TypeScript(但仍使開發人員能夠使用純JavaScript進行編碼),并結合了OOP(面向對象程式設計),FP(功能程式設計)和FRP(功能反應性程式設計)的元素。
這次我就幫助童鞋快速入門,盡快應用起 nest,在實戰中學習 nest,對工作和學習都有更好的幫助。
1、利用基于 nest 快速搭建項目。
2、學習如何連接配接 mysql 資料庫,利用 typeorm 操作 mysql 資料庫。
3、建立使用者子產品,并進行 token 驗證
4、傳回資料進行處理
5、檔案上傳
1、利用基于 nest 快速搭建項目
使用 nest cli 快速建立項目
$ npm i -g @nestjs/cli
$ nest new project-name
将建立 project 目錄, 安裝node子產品和一些其他樣闆檔案,并将建立一個 src 目錄,目錄中包含幾個核心檔案。
src
├── app.controller.ts
├── app.module.ts
└── main.ts
以下是這些核心檔案的簡要概述:
app.controller.ts 帶有單個路由的基本控制器示例。
app.module.ts 應用程式的根子產品。
main.ts 應用程式入口檔案。它使用 NestFactory 用來建立 Nest 應用執行個體。
運作程式
npm run start
在應用程式運作時, 打開浏覽器并通路 http://localhost:3000/。 應該看到 Hello world! 資訊。
2、學習如何連接配接 mysql 資料庫,利用 typeorm 操作 mysql 資料庫。
typeorm 對 mysql 資料庫版本有要求,需要5.6以上,在這個地方遇到過問題,找了很長時間,才發現這個問題。最好是使用最新的 mysql 資料庫。
運作指令安裝 mysql、typeorm
npm install --save @nestjs/typeorm typeorm mysql
安裝過程完成後,我們可以将 TypeOrmModule 導入AppModule 。
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [],
synchronize: true,
}),
],
})
export class AppModule {}
我們可以也建立 ormconfig.json ,而不是将配置對象傳遞給 forRoot()。
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
然後,我們可以不帶任何選項地調用 forRoot() :
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
})
export class AppModule {}
建立實體,這裡建立 admin 實體為例。
import {
Entity,
PrimaryGeneratedColumn,
Column,
BeforeInsert,
JoinTable,
ManyToMany,
OneToMany,
BeforeUpdate
} from 'typeorm';
import { IsEmail } from 'class-validator';
import * as argon2 from 'argon2';
@Entity('admin')
export class AdminEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@BeforeInsert()
async hashPassword() {
this.password = await argon2.hash(this.password);
}
}
admin.module.ts
import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common';
import { AdminController } from './admin.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AdminEntity } from './admin.entity';
import { AdminService } from './admin.service';
import { AuthMiddleware } from './auth.middleware';
@Module({
imports: [TypeOrmModule.forFeature([AdminEntity])],
providers: [AdminService],
controllers: [
AdminController
],
exports: [AdminService]
})
此子產品使用 forFeature() 方法定義在目前範圍中注冊哪些存儲庫。這樣,我們就可以使用 @InjectRepository()裝飾器将 adminRepository 注入到 AdminService 中:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository, DeleteResult } from 'typeorm';
import { AdminEntity } from './admin.entity';
import {UserDto, UserPassDto} from './dto';
const jwt = require('jsonwebtoken');
import { SECRET } from '../config';
import { validate } from 'class-validator';
import { HttpException } from '@nestjs/common/exceptions/http.exception';
import { HttpStatus } from '@nestjs/common';
import * as argon2 from 'argon2';
import {MsgService} from "../msg/msg.service";
@Injectable()
export class AdminService {
constructor(
@InjectRepository(AdminEntity)
private readonly adminRepository: Repository<AdminEntity>,
private readonly MSG: MsgService
) {}
async findAll(): Promise<AdminEntity[]> {
return await this.adminRepository.find();
}
async findOne({username, password}: UserDto): Promise<AdminEntity> {
const user = await this.adminRepository.findOne({username});
if (!user) {
return null;
}
if (await argon2.verify(user.password, password)) {
return user;
}
return null;
}
}
3、建立使用者子產品,并進行 token 驗證
使用 nest 指令建立使用者子產品
nest g module admin
nest g service admin
nest g controller admin
這裡我們使用中間件middleware來做使用者的 token 驗證
中間件是在路由處理程式 之前 調用的函數。 中間件函數可以通路請求和響應對象,以及應用程式請求響應周期中的
next()
中間件函數。
next()
中間件函數通常由名為
next
的變量表示。
Nest 中間件實際上等價于 express 中間件。 下面是Express官方文檔中所述的中間件功能:
中間件函數可以執行以下任務:
- 執行任何代碼。
- 對請求和響應對象進行更改。
- 結束請求-響應周期。
- 調用堆棧中的下一個中間件函數。
- 如果目前的中間件函數沒有結束請求-響應周期, 它必須調用
将控制傳遞給下一個中間件函數。否則, 請求将被挂起。next()
您可以在函數中或在具有
@Injectable()
裝飾器的類中實作自定義
Nest
中間件。 這個類應該實作
NestMiddleware
接口, 而函數沒有任何特殊的要求。
import { HttpException } from '@nestjs/common/exceptions/http.exception';
import { NestMiddleware, HttpStatus, Injectable } from '@nestjs/common';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Request, Response, NextFunction } from 'express';
import * as jwt from 'jsonwebtoken';
import { SECRET } from '../config';
import { AdminService } from './admin.service';
import {MsgService} from "../msg/msg.service";
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private readonly userService: AdminService,
private readonly MSG: MsgService) {}
async use(req: Request, res: Response, next: NextFunction) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
const token = authHeaders;
const decoded: any = this.verify(token);
const user = await this.userService.findById(decoded.id);
if (!user) {
throw new HttpException('User not found.', HttpStatus.UNAUTHORIZED);
}
req.user = user;
next();
} else {
throw new HttpException('Not authorized.', HttpStatus.UNAUTHORIZED);
}
}
private verify (token:string){
let decoded: any
try {
decoded = jwt.verify(token, SECRET);
}catch (e) {
this.MSG.fail('token error')
}
return decoded
}
}
在admin.module.ts中應用AuthMiddleware,forRoutes裡面配置的路徑是 controller 裡需要進行 token 驗證的接口路徑
import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common';
import { AdminController } from './admin.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AdminEntity } from './admin.entity';
import { AdminService } from './admin.service';
import { AuthMiddleware } from './auth.middleware';
@Module({
imports: [TypeOrmModule.forFeature([AdminEntity])],
providers: [AdminService],
controllers: [
AdminController
],
exports: [AdminService]
})
export class AdminModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.forRoutes({path: 'admin/users', method: RequestMethod.GET}, {path: 'user', method: RequestMethod.PUT});
}
}
admin.controller.ts
import { Get, Post, Body, Put, Delete, Param, Controller, UsePipes } from '@nestjs/common';
import { Request } from 'express';
import { AdminService } from './admin.service';
import {UserDto, UserPassDto} from './dto';
import { HttpException } from '@nestjs/common/exceptions/http.exception';
import { ValidationPipe } from '../shared/pipes/validation.pipe';
import {
ApiBearerAuth, ApiTags
} from '@nestjs/swagger';
import {MsgService} from "../msg/msg.service";
@ApiBearerAuth()
@ApiTags('admin')
@Controller('admin')
export class AdminController {
constructor(private readonly userService: AdminService,
private readonly MSG: MsgService) {}
@Get('users')
async getall(){
return this.userService.findAll();
}
@UsePipes(new ValidationPipe())
@Put('users')
async create(@Body() userData: UserDto) {
return this.userService.create(userData);
}
@UsePipes(new ValidationPipe())
@Post('users/login')
async login(@Body() loginUserDto: UserDto) {
console.log(loginUserDto)
const _user = await this.userService.findOne(loginUserDto);
if (!_user) this.MSG.fail('no user')
const token = await this.userService.generateJWT(_user);
const { username} = _user;
const user = {token, username};
return {user}
}
@Post('change/pass')
async changePass(@Body() changePassBody: UserPassDto){
return this.userService.changePass(changePassBody)
}
}
4、傳回資料進行處理
這裡的傳回資料我們并不滿意,希望可以反回類似
{
data:{},
code: 200,
message: '請求成功',
}
那我們要在每個請求後面都進行這樣的資料結構的整理,這樣太麻煩了,可以使用interceptor攔截器來處理傳回資料
transform.interceptor.ts
import {
Injectable,
NestInterceptor,
CallHandler,
ExecutionContext,
} from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>> {
intercept(
context: ExecutionContext,
next: CallHandler<T>,
): Observable<Response<T>> {
return next.handle().pipe(
map(data => {
return {
data,
code: 200,
message: '請求成功',
};
}),
);
}
}
對異常傳回處理
使用Exception filters異常過濾器。
http-exception.filter.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import {QueryFailedError} from "typeorm";
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const message = exception.message;
Logger.log('錯誤提示', message);
const errorResponse = {
message: message,
code: exception.getStatus(), // 自定義code
url: request.originalUrl, // 錯誤的url位址
};
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
// 設定傳回的狀态碼、請求頭、發送錯誤資訊
response.status(status);
response.header('Content-Type', 'application/json; charset=utf-8');
response.send(errorResponse);
}
}
如果覺得平常寫抛出異常太長太麻煩,可以對抛出異常再進行封裝。
throw new HttpException({message: 'reason'},HttpStatus.BAD_REQUEST);
改為
this.MSG.fail('Username must be unique',400)
this.MSG是對異常的封裝
import {HttpException, HttpStatus, Injectable} from '@nestjs/common';
@Injectable()
export class MsgService {
public fail(reason:string, code?:number){
if (code) {
throw new HttpException({message: reason},code);
} else {
throw new HttpException({message: reason},HttpStatus.BAD_REQUEST);
}
}
5、檔案上傳
一個項目離不開檔案上傳,nest 對檔案上傳也做了相應處理
建立檔案子產品。
如果需要詳細了解檔案上傳,可以看另外一篇文章https://www.jianshu.com/p/28f8dd9a732e
還是使用指令來做
nest g module file
nest g service file
nest g controller file
file.module.ts
import { Module } from '@nestjs/common';
import { FileController } from './file.controller';
import { FileService } from './file.service';
import { MulterModule } from '@nestjs/platform-express';
import dayjs = require('dayjs');
import { diskStorage } from 'multer';
import * as nuid from 'nuid';
@Module({
imports:[
MulterModule.register({
storage: diskStorage({
// 配置檔案上傳後的檔案夾路徑
destination: `./public/uploads/${dayjs().format('YYYY-MM-DD')}`,
filename: (req, file, cb) => {
// 在此處自定義儲存後的檔案名稱
// const filename = `${nuid.next()}.${file.mimetype.split('/')[1]}`;
// return cb(null, filename);
return cb(null, file.originalname);
},
}),
}),
],
controllers: [FileController],
providers: [FileService]
})
export class FileModule {}
file.controller.ts
import { Controller, Post, UseInterceptors, UploadedFile, UploadedFiles, Body } from '@nestjs/common';
import {
AnyFilesInterceptor,
FileFieldsInterceptor,
FileInterceptor,
FilesInterceptor,
} from '@nestjs/platform-express';
import multer = require('multer');
// import {StorageService} from 'src/common/storage/storage.service'
import {FileService} from './file.service'
@Controller('file')
export class FileController {
constructor(
private readonly fileService:FileService,
) {
}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async UploadedFile(@UploadedFile() file) {
// 這裡的 file 已經是儲存後的檔案資訊了,在此處做資料庫處理,或者直接傳回儲存後的檔案資訊
// const buckets =await this.storageService.getBuckets()
console.log(file)
return file;
}
@Post('upload3')
@UseInterceptors(FilesInterceptor('file'))
uploadFile(@UploadedFiles() file) {
console.log(file);
}
@Post('uploads')
@UseInterceptors(FileFieldsInterceptor([
{ name: 'avatar', maxCount: 1 },
{ name: 'background', maxCount: 1 },
{ name: 'avatar_name' },
{ name: 'background_name'}
]))
async uploads(@UploadedFiles() files,@Body() body) {
console.log(files,body)
}
總結
至此關于 nest 實戰結束,希望對你有所幫助。全部代碼https://github.com/baiqingchun/nest