1. 初始化
安装
npm i -g @nestjs/cli
nest new nest-mongo
删除测试
- 删除package.json测试相关
// 以下是删除的代码
{
"scripts": {
"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"
},
"devDependencies": {
"@nestjs/testing": "^7.6.15",
"@types/supertest": "^2.0.10",
"@types/jest": "^26.0.22",
"supertest": "^6.1.3",
"ts-jest": "^26.5.4",
},
"jest": { ... }
}
-
删除
spec文件 -
安装运行
$ npm install
$ npm run start
# Hello World! 成功
定制格式化
// .pretterrc
{
"jsxSingleQuote": true,
"jsxBracketSameLine": true,
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"useTabs": true,
"tabWidth": 2,
"semi": false,
"arrowParens": "avoid"
}
$ npm run lint
2. 登录接口
配置好mongodb用户表users connections,添加几个用户
接入Mongo
$ npm install --save @nestjs/mongoose mongoose
// app.module.ts
import { MongooseModule } from '@nestjs/mongoose'
@Module({
imports: [
MongooseModule.forRoot(
'mongodb://<path-to-mongodb>?authSource=admin&retryWrites=true&w=majority',
{ useFindAndModify: false }
// 消除警告(早期mongoose使用了mongodb后期才有的方法,现在做同步)
)
],
...
})
users 模块
# 新建users目录,新建模块三件套
src/users/users.module.ts # 引入app.module.ts
src/users/users.controller.ts # 加一个 /user/login 接口
src/users/users.service.ts
// src/users/users.controller.ts
import { Controller, Post } from '@nestjs/common'
import { UsersService } from './users.service'
@Controller('user')
export class UsersController {
constructor(private usersService: UsersService) {}
@Post('login')
login() {
return 'login api'
}
}
访问数据库
// user 模块注入mongodb
// src/users/users.module.ts
import { MongooseModule } from '@nestjs/mongoose'
import { UserSchema } from './schemas/user.schema'
@Module({
imports: [MongooseModule.forFeature([{ name: 'users', schema: UserSchema }])],
})
// user 结构
// src/users/schemas/user.schema.ts
import { Prop, SchemaFactory, Schema } from '@nestjs/mongoose'
import { Document } from 'mongoose'
@Schema()
export class User extends Document {
@Prop()
...
}
export const UserSchema = SchemaFactory.createForClass(User)
// user 数据服务
// scr/users/users.service.ts
import { Injectable } from '@nestjs/common'
import { LeanDocument, Model } from 'mongoose'
import { InjectModel } from '@nestjs/mongoose'
import { User } from './schemas/user.schema'
Injectable()
export class UsersService {
constructor(@InjectModel('users') private readonly usersModel: Model<User>) {}
async validateUser(username: string, password: string): Promise<Omit<LeanDocument<User>, 'password'> | void> {
const user = await this.usersModel.findOne({ username }).exec()
if (user && user.password === password) {
const { password, ...result } = user.toObject({ getters: true })
return result
}
}
}
// user login路由
import { Controller, Post, Body } from '@nestjs/common'
import { UsersService } from './users.service'
@Controller('user')
export class UsersController {
constructor(private usersService: UsersService) {}
@Post('login')
async login(@Body() loginDto: { username: string; password: string }) {
const user = await this.usersService.validateUser(loginDto.username, loginDto.password)
if (user) return user
else return '请输入'
}
}
jwt 生成 token
$ npm install @nestjs/jwt passport-jwt
$ npm install @types/passport-jwt --save-dev # passport-jwt还没有ts声明文件
// user 模块注入jwt模块
// src/users/users.module.ts
import { JwtModule } from '@nestjs/jwt'
import { jwtContants } from './contants' // 新建常量文件,方便其它文件引用
@Module({
imports: [
JwtModule.register({
secret: jwtContants.secret,
signOptions: { expiresIn: '1d' }
})
],
...
// user 登录服务
// src/users/users.service.ts
import { JwtService } from '@nestjs/jwt'
Injectable()
export class UsersService {
constructor(
@InjectModel('users') private readonly usersModel: Model<User>,
private readonly jwtService: JwtService
) {}
...
login(user: LeanDocument<Omit<User, 'password'>>): string {
const payload = { username: user.username, id: user._id }
return 'Bearer ' + this.jwtService.sign(payload)
}
}
// src/users/users.controller.ts
@Controller('user')
@Post('login')
async login(@Body() loginDto: { username: string; password: string }) {
const user = await this.usersService.validateUser(loginDto.username, loginDto.password)
if (user) return this.usersService.login(user)
}
用户认证
// jwt 策略文件
// src/users/strategies/jwt.strategy.ts
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy, ExtractJwt } from 'passport-jwt'
import { jwtContants } from '../constants'
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtContants.secret
})
}
validate(payload) {
return { id: payload.id, username: payload.username }
}
}
// user 模块注入jwt策略
// src/users/users.module.ts
import { JwtStrategy } from './strategies/jwt.strategy'
@Module({
providers: [UsersService, JwtStrategy],
...
})
export class UsersModule {}
// user 查询用户数据服务
// src/users/users.service.ts
Injectable()
export class UsersService {
...
async findById(id: string): Promise<Partial<User> | void> {
const user = this.usersModel.findById(id).exec()
if (user) {
const { password, id, _id, ...result } = (await user).toObject({ getters: true })
return result
}
}
}
// 查询用户路由
// src/users/users.controller.ts
@Controller('user')
export class UsersController {
...
@UseGuards(AuthGuard('jwt'))
@Get('profile')
async getProfile(@Request() req) {
const user = await this.usersService.findById(req.user.id)
if (user) return user
}
}
包裹异常
// 如果token验证未通过,nest jwt自动返回401(如下)
{
"statusCode": 401,
"message": "Unauthorized"
}
// statusCode 字段并不是我想要的,需要将其换为常用的 code
// 创建一个异常过滤器
// src/common/HttpExceptionFilter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'
import { Response } from 'express'
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse<Response>()
const status = exception.getStatus()
response.status(status).json({
code: status,
message: exception.message
})
}
}
// 全局绑定
// src/main.ts
import { HttpExceptionFilter } from './common/HttpExceptionFilter'
...
app.useGlobalFilters(new HttpExceptionFilter())
...
传输验证管道
$ npm install class-validator class-transformer
// src/users/dto/login.dto
import { IsDefined, IsString, Length } from 'class-validator'
export class LoginDto {
...
}
// 绑定验证
// src/users/users.controller.ts
import { LoginDto } from './dto/login.dto'
...
async login(@Body() loginDto: LoginDto) {
...
返回错误数据没能正确显示,修改一下HttpExceptionFilter
// src/common/HttpExceptionFilter.ts
...
const resOfException: string | { message?: string } = exception.getResponse()
response.status(status).json({
code: status,
message: typeof resOfException === 'string' ? resOfException : resOfException.message
})
...
设置返回结构
// src/common/types/return.ts
export interface Return {
code: number
message: string
date: any
}
3. 日记路由
新建文件
# 建立diaries模块
src/diaries/diaries.module.ts
src/diaries/diaries.service.ts
src/diaries/diaries.controller.ts
# 注意链接模块树 app => diaries => service + controller
// 测试路由
import { Controller, Get } from '@nestjs/common'
@Controller('diaries')
export class DiariesController {
@Get()
getDiaries() {
return 'get diaries'
}
}
链接数据表
// 新建数据结构
// src/diaries/schemas/diary.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { Document } from 'mongoose'
@Schema()
export class Diary extends Document {
...
}
export const DiarySchema = SchemaFactory.createForClass(Diary)
// 模块链接diaries表
// src/diaries/diaries.module.ts
import { MongooseModule } from '@nestjs/mongoose'
import { DiarySchema } from './schemas/diary.schema'
@Module({
imports: [MongooseModule.forFeature([{ name: 'diaries', schema: DiarySchema }])],
})
export class DiariesModule {}
// 数据服务
// src/diaries/diaries.service.ts
import { Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { LeanDocument, Model } from 'mongoose'
import { Diary } from './schemas/diary.schema'
@Injectable()
export class DiariesService {
constructor(@InjectModel('diaries') private readonly diariesModel: Model<Diary>) {}
async findAll(pageSize = 10, pageNum = 1): Promise<LeanDocument<Diary>[]> {
const diaries = await this.diariesModel
.find()
.limit(pageSize)
.skip((pageNum - 1) * pageSize)
.exec()
const diariesObject = diaries.map(diary => diary.toObject())
return diariesObject
}
}
// 路由返回数据
// src/diaries/diaries.controller.ts
@Controller('diaries')
export class DiariesController {
...
@Get()
async getDiaries() {
const diaries = this.diariesService.findAll()
return diaries
}
...
- 登录限制
// src/diaries/diaries.controller.ts
@UseGuards(AuthGuard('jwt'))
@Controller('diaries')
export class DiariesController {
...
@Get()
async getDiaries(@Request() req): Promise<ReturnType> {
const diaries = this.diariesService.findAll(req.user.id)
...
}
}
- 数据筛选
// src/diaries/diaries.service.ts
...
async findAll(id: string, pageSize = 10, pageNum = 1): Promise<LeanDocument<Diary>[]> {
...
.find({ author: id })
...
}