简单介绍
NestJS是一个用于构建高效、可扩展 Node.js 服务器端应用程序的框架。它使用渐进式的JavaScript,完全支持并构建于 TypeScript 上,结合了面向对象编程(OOP)、函数式编程(FP)和函数响应式编程(FRP)的元素。
Nest在这些常见的Node.js框架(Express/Fastify)之上提供了一层抽象,同时也直接向开发者暴露了它们的API。这使得开发者可以自由使用众多为底层平台提供的第三方模块。
安装
安装命令
$ npm i -g @nestjs/cli
$ nest new project-name
提示: 要使用更严格的功能集创建新的TypeScript项目,请在nest new命令中添加--strict标志。
运行命令
$ npm install
$ npm run start
在浏览器中打开 http://localhost:3000/
认识目录
创建 project-name 项目之后。src主要有以下文件:
在介绍目录之前我们先梳理一下后端的写接口的流程(假设写一个http://localhost:3000/about 接口):
- 我们可以现在创建一个about接口文件夹
- 有个文件功能是对数据库的about表进行增删查改的,我们命名:about.service.ts
- 有个文件是给前端暴露出GET或者POST等请求接口的,我们命名:about.controller.ts
- 我们已经经历了数据库的读写->向前端抛出接口api,这是一个简单的流程,实际中我们的流程比这复杂的多,所以我们应该一个个流程组织起来,因此我们需要的组织起来的文件,就是about.module.ts
初始这些核心文件的简要概述为:
- app.controller.ts 一个具有单一路由的基本控制器().
- app.controller.spec.ts 控制器的单元测试.
- app.module.ts 应用程序的根模块.
- app.service.ts 一个基本的服务,拥有一个单一的方法.
- main.ts 应用程序的入口文件将使用核心函数 NestFactory 来创建一个 Nest 应用程序实例.
项目基础搭建
安装数据库和redis
我们把没用的文件先删除掉创建以下目录:
修改主入口文件main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session'; // 安装npm i express-session缓存插件
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(
// session缓存
session({
secret: 'lh',
name: 'lh.session',
rolling: true,
cookie: { maxAge: null } as any,
resave: false,
saveUninitialized: true,
})
)
await app.listen(3000);
console.log('🚀 启动成功: http://localhost:3000');
}
bootstrap();
修改基本控制器app.module.ts文件:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import {SharedModule} from './shared/shared.module'; // 导入数据库以及redis控制器模块
@Module({
imports: [
// 配置文件模块, 在项目根目录创建.env配置文件,配置数据库和redis信息
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
// 共享数据库+Redis模块
SharedModule
],
})
export class AppModule {}
- 数据库以及redis控制器模块
数据库:npm install pg --save
redis:npm i redis --save
shared/shared.module.ts:
import { Global, Module, ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RedisService } from './redis.service'; // redis的增删查改工具类供以后使用
import { createClient } from 'redis';
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { AllExceptionFilter } from '@/common/filters/all-exception.filter'; // 全局错误过滤器
import { TransformInterceptor } from '@/common/interceptors/transform.interceptor';
@Global()
@Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres', // 这里我选择用了该数据库,所以要安装一下npm install pg --save
autoLoadEntities: true,
// 以下是.env中的数据库的配置
host: configService.get('POSTGRES_HOST'),
port: configService.get('POSTGRES_PORT'),
username: configService.get('POSTGRES_USER'),
password: configService.get('POSTGRES_PASSWORD'),
database: configService.get('POSTGRES_DATABASE'),
syncronize: process.env.NODE_ENV === 'production' ? false : configService.get('POSTGRES_SYNC'),
timezone: '+08:00',
})
})
],
providers: [
RedisService,
{
inject: [ConfigService],
provide: 'REDIS_CLIENT',
async useFactory(configService: ConfigService){
const client = createClient({
url: configService.get('REDIS_URL'), // .env配置redis地址
});
await client.connect();
return client
}
},
{
// 全局错误过滤器
provide: APP_FILTER,
useClass: AllExceptionFilter,
},
{
// 全局拦截器
provide: APP_INTERCEPTOR,
useClass: TransformInterceptor,
},
{
// 全局参数校检管道
provide: APP_PIPE,
useValue: new ValidationPipe({
whitelist: true,
transform: true, // 自动类型转换
}),
}
],
exports: [RedisService],
})
export class SharedModule { }
- redis服务类
shared/redis.service.ts文件:redis的增删查改工具类供以后使用
import { Inject, Injectable } from '@nestjs/common';
import { RedisClientType } from 'redis'
@Injectable()
export class RedisService {
@Inject('REDIS_CLIENT')
private redisClient: RedisClientType;
async get(key: string) {
return await this.redisClient.get(key);
}
async set(key: string, value: string | number, ttl?: number) {
await this.redisClient.set(key, value);
if (ttl) {
await this.redisClient.expire(key, ttl);
}
}
async del(key: string) {
await this.redisClient.del(key);
return true;
}
async hashSet(key: string, obj: Record<string, any>, ttl?: number) {
for (const name in obj) {
await this.redisClient.hSet(key, name, obj[key]);
}
if (ttl) {
await this.redisClient.expire(key, ttl);
}
}
}
- 全局错误过滤器
src\common\filters\all-exception.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common";
import { Request, Response } from 'express';
@Catch()
export class AllExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const exceptionResponse = exception.getResponse?.();
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
code: exception.code ?? status,
error: exception.name,
message: exceptionResponse?.message || exception.message,
originUrl: request.originalUrl,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
- 全局拦截器
src\common\interceptors\transform.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Observable, map } from "rxjs";
import { ReturnType } from '@/types';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
constructor(private reflector: Reflector) { }
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
const returnType = this.reflector.get<ReturnType>('returnType', context.getHandler())
const req: any = context.getArgByIndex(1).req as Request
return next.handle().pipe(
map((data) => {
switch (returnType) {
case "primitive":
return data;
default:
return {
code: 0,
message: 'ok',
data,
originalUrl: req.originalUrl,
}
}
})
)
}
}