简介
NestJS是基于Node的后端结构,底层运用了Express作为HTTP服务结构(也可运用Fastify),对TypeScript有着十分杰出的支撑,上手也十分简略。本文意在经过一个简略的接口示例,协助读者快速学会如何运用NestJS写接口。
前置概念
IoC 操控回转 与 DI 依靠注入
IoC,Inversion of Control,意为操控回转,旨在解耦组件之间的依靠联系。
在传统的编程模型中,组件之间的依靠联系由调用者来操控,即调用者创立并办理被调用者的实例。而在 IoC 的模式下,这种操控权被回转,由容器(一般是一个结构或容器库)担任创立和办理组件的实例,同时担任解决它们之间的依靠联系。
IoC 的核心思维是将组件之间的依靠联系的创立和解析过程委托给一个容器,调用者只需求声明它们的依靠联系,而不需求直接担任实例的创立和办理。容器担任根据依靠联系进行实例的创立、注入和生命周期的办理。
DI,Dependency Injection,依靠注入,是IoC的一种表现形式。
运用小满zs供给的一个例子:
未运用IoC:
class A {
name: string
constructor(name: string) {
this.name = name
}
}
class B {
age:number
entity:A
constructor (age:number) {
this.age = age;
this.entity = new A('小满')
}
}
const c = new B(18)
c.entity.name
运用IoC:
class A {
name: string
constructor(name: string) {
this.name = name
}
}
class C {
name: string
constructor(name: string) {
this.name = name
}
}
//中间件用于解耦
class Container {
modeuls: any
constructor() {
this.modeuls = {}
}
provide(key: string, modeuls: any) {
this.modeuls[key] = modeuls
}
get(key) {
return this.modeuls[key]
}
}
const mo = new Container()
mo.provide('a', new A('1'))
mo.provide('c', new C('2'))
class B {
a: any
c: any
constructor(container: Container) {
this.a = container.get('a')
this.c = container.get('c')
}
}
new B(mo)
示例中运用了一个容器container
来收集依靠,供其他组件运用。
装修器 @
装修器分为类装修器(Class Decorator),特点装修器(Property Decorator),办法装修器(Method Decorator),参数装修器(Parameter Decorator),差异在于润饰的目标不同,承受的参数也不同。
const CDecorator: ClassDecorator = (target: any) => {
target.prototype.name = "哈哈";
};
@CDecorator
class A {
constructor() {}
}
const a: any = new A();
console.log(a.name);
// => 哈哈
const PDecorator: PropertyDecorator = (target: any, key: string | symbol) => {
console.log(target, key);
};
const MDecorator: MethodDecorator = (
target: any,
key: string | symbol,
descriptor: any
) => {
console.log(target, key, descriptor);
};
const ParameterDecorator: ParameterDecorator = (
target: any,
key: string | symbol | undefined,
index: number
) => {
console.log(target, key, index);
};
class B {
@PDecorator
public name: string;
constructor() {
this.name = "";
}
@MDecorator
getName(@ParameterDecorator name: string) {
return this.name;
}
}
//输出成果
特点装修器
{} name
参数装修器
{} getName 0
办法装修器
{} getName {
value: [Function: getName],
writable: true,
enumerable: false,
configurable: true
}
初始化项目
-
首要装置@nestjs/cli
pnpm install -g @nestjs/cli
-
新建项目
nest new [项目名称]
-
发动项目(热更新)
pnpm run start:dev
检查是否发动成功:
curl http://127.0.0.1:3000
Hello World!
文件结构
*.controller.spec.ts | 测验文件 |
---|---|
*.controller.ts | controller 路由部分 |
*.module.ts | IoC 容器 |
*.service.ts | controller 逻辑部分 |
main.ts | 入口 |
nest 指令
@nestjs/cli 预置了许多创立指令,能够经过nest --help
检查。
能够经过nest g [name]
来快速创立文件,例如
nest g mo user
nest g co user
经过指令生成的文件,也会主动DI,例如这儿咱们创立了一个模块文件,后创立了一个操控器,后创立的操控器就主动注入到模块中了。
也能够经过nest g res [name]
快速创立一整个模块,例如:
nest g res menu
----------------------------------------------------
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/menu/menu.controller.spec.ts (556 bytes)
CREATE src/menu/menu.controller.ts (883 bytes)
CREATE src/menu/menu.module.ts (240 bytes)
CREATE src/menu/menu.service.spec.ts (446 bytes)
CREATE src/menu/menu.service.ts (607 bytes)
CREATE src/menu/dto/create-menu.dto.ts (30 bytes)
CREATE src/menu/dto/update-menu.dto.ts (169 bytes)
CREATE src/menu/entities/menu.entity.ts (21 bytes)
UPDATE package.json (1987 bytes)
UPDATE src/app.module.ts (369 bytes)
✔ Packages installed successfully.
Controller 操控器
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}
参数装修器
恳求中往往会带着许多信息,咱们能够经过参数装修器便捷的获取咱们需求的数据。
@Param() | 途径参数 /user/:id |
---|---|
@Query() | 查询参数 /user?page=1&pageSize=5 |
@Body() | 恳求体 |
@Headers() | 恳求头 |
DTO 与数据校验
在 Nest.js 中,DTO(Data Transfer Object)是一种用于数据传输的目标,用于在不同层之间传递数据。DTO 能够包括一组特点,这些特点与数据传输相关,而且能够用于从客户端向服务器传递数据,或者在服务器内部的不同模块之间传递数据。
在 dto 文件中描绘数据:
// create-user.dto.ts
export class CreateUserDto {
username: string;
password: string;
}
咱们稍微改造一下逻辑作为示例:
// user.service.ts
async create(createUserDto: CreateUserDto) {
return createUserDto;
}
curl --location --request POST 'http://localhost:3000/user' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=1' \
--data-urlencode 'password=123'
--------------------------------------------------------
{"username":"1","password":"123"}%
此刻并不会对数据进行验证,例如咱们没传username
:
curl --location --request POST 'http://localhost:3000/user' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'password=123'
--------------------------------------------------------
{"password":"123"}%
而咱们期望将username
和password
作为必传项,因而需求进行数据验证。
NestJS 供给了 pipe(管道)来进行数据的转换和验证,在现在的情景下,咱们运用 NestJS 自带的ValidationPipe
结合class-validator
就能够满意咱们验证数据的需求。
首要咱们在main.ts
中大局注册管道ValidationPipe
:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
然后装置class-validator
:
pnpm i class-validator class-transformer -S
同样是运用装修器语法:
// create-user.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty({ message: '用户名不能为空' })
username: string;
@IsNotEmpty()
password: string;
}
此刻,咱们重新发恳求:
curl --location --request POST 'http://localhost:3000/user' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'password=123'
---------------------------------------------------------
{"message":["用户名不能为空"],"error":"Bad Request","statusCode":400}%
衔接数据库
这儿咱们运用 TypeORM 作为咱们的 ORM 结构。
ORM 是一种将目标模型与联系数据库之间进行映射的技能,经过 ORM 结构能够将数据库中的表和行映射到程序中的目标和特点。
装置依靠:
pnpm i --save @nestjs/typeorm typeorm mysql2
准备好咱们的数据库,然后在app.module.ts
中注册 TypeORM :
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { MenuModule } from './menu/menu.module';
@Module({
imports: [
UserModule,
MenuModule,
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '123456',
database: 'oss-nest',
synchronize: true,
retryDelay: 500,
retryAttempts: 10,
autoLoadEntities: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Entity 实体
在目标联系映射(ORM)中,实体(Entity)是指映射到数据库表的目标模型。在 ORM 中,实体一般与数据库表一一对应。每个实体类代表一个数据库表,在类中界说的特点(字段)对应表中的列,类的实例对应表中的行。经过ORM结构供给的功能,能够完成实体目标与数据库之间的增修改查操作。
这儿咱们以user
为例:
// user.entity.ts
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'account', unique: true })
username: string;
@Column()
password: string;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
}
然后在user.module.ts
中相关:
// user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
相关后,重启服务后实体就会同步到数据库中。
对数据库进行 CRUD 的一个例子
CRUD,即增加(Create)、读取(Read)、更新(Update)和删去(Delete)
在user.service.ts
中注入:
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
export class UserService {
constructor(
@InjectRepository(User) private readonly user: Repository<User>,
) {}
...
}
然后就能够写对应的逻辑。
Create 增
// user.service.ts
async create(createUserDto: CreateUserDto) {
const user = new User();
user.username = createUserDto.username;
user.password = createUserDto.password;
return await this.user.save(user);
}
测验一下:
curl --location --request POST 'http://localhost:3000/user' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=1' \
--data-urlencode 'password=123'
--------------------------------------------------------------
{"username":"1","password":"123","id":1,"created_at":"2023-07-10T07:24:48.532Z","updated_at":"2023-07-10T07:24:48.532Z"}%
检查数据库:
数据库中也出现了对应数据,表示新增用户成功。
Read 读
查询一条
// user.service.ts
async findOne(id: number) {
return await this.user.findOne({ where: { id } });
}
测验一下:
curl --location --request GET 'http://localhost:3000/user/1'
---------------------------------------------------------------
{"id":1,"username":"1","password":"123","created_at":"2023-07-10T07:24:48.532Z","updated_at":"2023-07-10T07:24:48.532Z"}%
分页查询
// user.controller.ts
@Get()
findAll(@Query() query: { page: number; size: number }) {
return this.userService.findAll(query);
}
// user.service.ts
async findAll(query: { page: number; size: number }) {
const { page, size } = query;
const [users, total] = await this.user.findAndCount({
skip: (page - 1) * size,
take: size,
});
return { users, total };
}
测验一下:
curl --location --request GET 'http://localhost:3000/user/?page=2&size=5'
{
"users": [
{
"id": 6,
"username": "例工般",
"password": "92780",
"created_at": "2023-07-10T07:41:25.090Z",
"updated_at": "2023-07-10T07:41:25.090Z"
},
{
"id": 7,
"username": "百提同",
"password": "5766884758",
"created_at": "2023-07-10T07:41:25.760Z",
"updated_at": "2023-07-10T07:41:25.760Z"
},
{
"id": 8,
"username": "号消米发具",
"password": "483747755",
"created_at": "2023-07-10T07:41:26.358Z",
"updated_at": "2023-07-10T07:41:26.358Z"
},
{
"id": 9,
"username": "全要色院",
"password": "6442764",
"created_at": "2023-07-10T07:41:27.017Z",
"updated_at": "2023-07-10T07:41:27.017Z"
},
{
"id": 10,
"username": "接去度件路",
"password": "7585781",
"created_at": "2023-07-10T07:41:27.582Z",
"updated_at": "2023-07-10T07:41:27.582Z"
}
],
"total": 13
}
Update 改
// user.service.ts
async update(id: number, updateUserDto: UpdateUserDto) {
return await this.user.update(id, updateUserDto);
}
测验一下:
curl --location --request PATCH 'http://localhost:3000/user/1' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=123' \
--data-urlencode 'password=123'
----------------------------------------------------------------
{"generatedMaps":[],"raw":[],"affected":1}%
检查数据库:
能够看到,数据已经更新成功了。
Delete 删
// user.service.ts
async remove(id: number) {
return await this.user.delete(id);
}
测验一下:
curl --location --request DELETE 'http://localhost:3000/user/1'
----------------------------------------------------------------
{"raw":[],"affected":0}%
检查数据库:
能够看到,id 为 1 的数据已经被删去了。
跋文
本文旨在协助读者创立一个简略的 NestJS 项目,并完成最简略的 CRUD ,如有不足,请各位读者批评指正。