搭建Nestjs+TypeORM+TS服务端应用架构

1:前言

首先输出一个小小的个人观念,不要把自己看做一个某个语言的开发者,放开思维与眼界不要让标签限制自己。 本文也比较简单,是一个入门分享。如果你是一位前端开发者,应该可以无缝过渡基本没有什么痛点都是自己熟悉的领域。如果你是一位后端开发者那么这些设计结构与思想应该是你非常熟悉的。 最后我为什么选用Nestjs,而不是expressjs?总结有以下4点原因(你想学一个东西,至少你要对比一下竞对再下手吧)

1. TypeScript 支持

NestJS 是用 TypeScript 编写的,提供了强大的类型系统和开发工具支持。这使得开发者能够在编写代码时获得更好的开发体验,包括自动补全、类型检查和重构支持。

2. 功能丰富

NestJS 内置了许多功能,如管道、守卫、拦截器和异常过滤器等,这些都是开箱即用的功能。相比之下,Express.js 更加轻量,但需要开发者自己集成和配置这些功能(相对投入大)。

3. 文档和学习曲线

NestJS 提供了详尽的文档和教程,帮助开发者快速上手。开箱即用成本低,相比之下,Express.js 的学习曲线相对较陡。

4. 集成

NestJS 可以与许多其他技术(如 GraphQL、WebSocket、微服务等)无缝集成,提供了更广泛的应用支持。这种灵活性使得它在不同项目中都能得到应用。

也上两张 NestjsExpressjs 的Github对比图

2:Nestjs的核心概念与设计原则

首先我们要先理解一些概念:

  • 模块化架构(Modularity):Nest.js使用模块化的方式组织应用程序,将功能划分为模块,每个模块负责特定的任务。模块提供了一种封装和隔离的机制,使代码更具可维护性和可测试性。
  • 依赖注入(Dependency Injection):Nest.js支持依赖注入模式,通过注入依赖项来解耦组件之间的依赖关系。依赖注入提供了灵活性和可测试性,并促进了代码的重用和可维护性。
  • 控制器与路由(Controllers and Routing):Nest.js使用控制器来处理HTTP请求,并使用装饰器和路由来定义请求的处理方法。控制器负责处理输入和输出,并将请求转发给相应的服务。
  • 提供者(Providers):提供者是Nest.js应用程序的基本构建块,它们是可注入的类,用于处理业务逻辑、数据访问、服务调用等任务。提供者可以在模块中定义,并通过依赖注入在应用程序中使用。
  • 中间件(Middleware):中间件是在请求和响应之间进行处理的功能组件,用于处理请求前、请求后和错误处理等任务。Nest.js中的中间件提供了对请求和响应的灵活处理和修改的机制。
  • 过滤器(Filters):过滤器用于在异常发生时对异常进行全局处理。它们提供了一种集中处理错误和异常的机制,可以捕获和转换异常,并生成标准化的错误响应。
  • Guards:守卫是一种用于权限验证和路由保护的功能组件。它们提供了在路由处理之前进行身份验证和授权的机制,以确保只有满足特定条件的请求可以通过。
  • 拦截器(Interceptors):拦截器(是不是很像axios里的拦截器)用于在请求处理过程中进行全局拦截和修改。它们提供了对请求和响应的拦截和修改的机制,可以在请求前、请求后和异常发生时进行处理。
  • 异常处理(Exception Handling):Nest.js提供了一种统一的异常处理机制,可以捕获和处理应用程序中的异常。通过自定义异常过滤器,可以对异常进行全局处理和转换。

3:数据流向

  • 客户端请求:用户通过 HTTP 请求与服务器交互。
  • 中间件:在请求处理过程中,可以使用中间件进行日志记录、身份验证等操作。
  • 装饰器:NestJS 的装饰器用于定义控制器、服务和路由,简化代码结构。
  • 守卫:用于身份验证和授权,确保用户有权访问特定资源。
  • 拦截器:可以在请求和响应之间执行额外的逻辑,如缓存、转换响应等。
  • 路由:请求被路由到相应的控制器。
  • 控制层:控制器接收请求,调用服务层处理业务逻辑。
  • 服务层:服务层与数据库交互,通过 TypeORM 进行数据操作。
  • 异常过滤器:处理应用中的异常,提供统一的错误响应。
  • 实体类:使用实体类定义数据模型,TypeORM 通过这些模型与数据库进行映射。

来一张图可能更直接:

4:搭建

4.1:安装脚手架

js 复制代码
pnpm i -g @nestjs/cli 
nest new project-name 

cd nest-typeorm-example 
pnpm install @nestjs/typeorm typeorm mysql

4.2:初始化项目结构

js 复制代码
+-- dist[目录] // 编译后的目录,用于预览项目 
+-- node_modules[目录] // 项目使用的包目录,开发使用和上线使用的都在里边 
+-- src[目录] // 源文件/代码,程序员主要编写的目录 
| +-- app.controller.spec.ts // 对于基本控制器的单元测试样例
| +-- app.controller.ts // 控制器文件,可以简单理解为路由文件
| +-- app.module.ts // 模块文件,在NestJS世界里主要操作的就是模块
| +-- app.service.ts // 服务文件,提供的服务文件,业务逻辑编写在这里
| +-- app.main.ts // 项目的入口文件,里边包括项目的主模块和监听端口号
+-- test[目录]// 测试文件目录,对项目测试时使用的目录,比如单元测试...
| +-- app.e2e-spec.ts // e2e测试,端对端测试文件,测试流程和功能使用
| +-- jest-e2e.json // jest测试文件,jset是一款简介的JavaScript测试框架
+-- .eslintrc.js // ESlint的配置文件
+-- .gitignore // git的配置文件,用于控制哪些文件不受Git管理
+-- .prettierrc // prettier配置文件,用于美化/格式化代码的
+-- nest-cli.json // 整个项目的配置文件,这个需要根据项目进行不同的配置
+-- package-lock.json // 防止由于包不同,导致项目无法启动的配置文件,固定包版本
+-- package.json // 项目依赖包管理文件和Script文件,比如如何启动项目的命令
+-- README.md // 对项目的描述文件,markdown语法
+-- tsconfig.build.json // TypeScript语法构建时的配置文件
+-- tsconfig.json // TypeScript的配置文件,控制TypeScript编译器的一些行为

4.3:创建实体类(user.entity.ts与对应的数据库表桥接)

在src目录下创建一个entity的文件夹,在里面你可以创建任意数据库对应的实体如下:

js 复制代码
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
} from 'typeorm';

// 表名
@Entity({ name: 'user' })
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 255, nullable: true })
  username: string;

  @Column({ length: 255, nullable: true })
  password: string;

  @CreateDateColumn({
    name: 'create_time',
    type: 'datetime',
    comment: 'Create Time',
  })
  create_time: Date;
}

4.4:创建服务器/层(user.service.ts与数据库交互)

主要写数据库的交互逻辑代码,实现增删改查

js 复制代码
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entity/mdb/user.entity';
import { ApiResponse } from '@/helper/type';
import * as bcryptjs from 'bcryptjs';
import * as dotenv from 'dotenv';
dotenv.config();

@Injectable()
export class UserService {
  // 使用InjectRepository装饰器并引入Repository即可使用typeorm的操作
  constructor(
    @InjectRepository(User, 'mdb')
    private readonly usersRepository: Repository<User>,
  ) {}
  // 获取所有用户数据列表(usersRepository.query()方法属于typeoram)
  async getList(): Promise<ApiResponse> {
    try {
      const res = await this.usersRepository.find();
      return {
        data: res,
        success: true,
        message: 'Successfully',
      };
    } catch (error) {
      return {
        data: null,
        success: false,
        message: `Error: ${error.message}`,
      };
    }
  }
  // 新增用户
  async addUser(body: any): Promise<ApiResponse> {
    try {
      if (!(body.username && body.password)) {
        return {
          data: null,
          success: false,
          message: '用戶名或密码错误',
        };
      }
      const hashedPassword = await bcryptjs.hash(body.password, 10);
      const res = await this.usersRepository.save({
        ...body,
        password: hashedPassword,
      });
      // console.log('res:', res);
      if (res.id) {
        return {
          data: res,
          success: true,
          message: 'Successfully',
        };
      } else {
        return {
          success: false,
          message: `Error: ${res}`,
        };
      }
    } catch (error) {
      return {
        success: false,
        message: `Error: ${error}`,
      };
    }
  }
  // 更新用户信息
  async updateUser(user: any): Promise<ApiResponse> {
    try {
      if (!user.id || !user.password) {
        return {
          data: null,
          success: false,
          message: '用戶ID或密码错误',
        };
      }
      // todo 校验超级管理员权限
      const userInfo = await this.usersRepository.findOne({
        where: { id: user.id },
      });
      if (
        userInfo &&
        (await bcryptjs.compare(user.password, userInfo.password))
      ) {
        const res = await this.usersRepository.update(
          user.id,
          user,
        );
        // console.log('updateUser:', res);
        if (res.affected) {
          return {
            data: user,
            success: true,
            message: 'Successfully',
          };
        } else {
          return {
            success: false,
            message: `Error: ${res}`,
          };
        }
      } else {
        return {
          success: false,
          message: 'Failed',
        };
      }
    } catch (error) {
      return {
        success: false,
        message: `Error: ${error}`,
      };
    }
  }
  // 删除用户
  async deleteUser(body: any): Promise<ApiResponse> {
    try {
      if (!body.id) {
        return {
          success: false,
          message: '用戶ID不存在',
        };
      }
      // 校验超级管理员权限
      const res = await this.usersRepository.delete({ id: body.id });
      if (res.affected) {
        return {
          success: true,
          message: 'Successfully',
        };
      } else {
        return {
          success: false,
          message: `Error: ${res}`,
        };
      }
    } catch (error) {
      return {
        success: false,
        message: `Error: ${error}`,
      };
    }
  }
}

4.5:创建控制器/层(user.controller.ts与服务层数据交互)

在这里你可以看到引用了服务层的方法在这里进行调用,路由也在这里进行分发

js 复制代码
import {
  Controller,
  Get,
  Post,
  Body,
  Delete,
  UseFilters,
} from '@nestjs/common';
import { UserService } from '../service/user.service';
import { ApiResponse } from '@/helper/type';
import { HttpExceptionFilter } from '@/http-exception.filter';

@Controller('user')
@UseFilters(new HttpExceptionFilter())
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('add')
  addUser(@Body() body: any): Promise<ApiResponse> {
    return this.userService.addUser(body);
  }

  @Get('list')
  getList(): Promise<ApiResponse> {
    // console.log('进入user/list控制层')
    return this.userService.getList();
  }

  // 更新用户
  @Post('update')
  updateUser(@Body() body): Promise<ApiResponse> {
    return this.userService.updateUser(body);
  }

  @Delete('delete')
  deleteUser(@Body() body: any): Promise<ApiResponse> {
    return this.userService.deleteUser(body);
  }
}

4.6:创建模块(user.module.ts)

在这里你可以看到这里我们引入了服务层、控制层、以及其他模块的代码进入这个模块

js 复制代码
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { UserService } from '../service/user.service'; // 导入用户服务
import { UserController } from '../controller/user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../entity/mdb/user.entity';
import { UserMiddleware } from '../middleware/user.middleware';
import { JwtModule } from '@nestjs/jwt';
import * as dotenv from 'dotenv'; // 导入 dotenv 模块,用于加载环境变量
import { JwtStrategy } from '../auth/jwt.strategy'; // 导入 JWT 策略
import { PassportModule } from '@nestjs/passport';

dotenv.config(); // 加载 .env 文件中的环境变量

@Module({
  imports: [
    TypeOrmModule.forFeature([User], 'mdb'), // 导入并注册 User 实体到 TypeORM 模块
    PassportModule, // 导入 Passport 模块,用于身份验证
    JwtModule.register({
      secret: process.env.T_k, // 从环境变量中获取 JWT 密钥
      signOptions: { expiresIn: '24h' }, // 配置 JWT 的过期时间为 24 小时
    }),
  ],
  providers: [UserService, JwtStrategy], // 注册 UserService 和 JwtStrategy 为提供者(服务)
  controllers: [UserController], // 注册 UserController 为控制器
})
export class UserModule implements NestModule {
  // 实现 NestModule 接口的 configure 方法,用于配置中间件
  configure(consumer: MiddlewareConsumer) {
    // 应用 UserMiddleware 中间件到 /user/* 路由
    consumer.apply(UserMiddleware).forRoutes('/user/*');
  }
}

4.7:汇总至根模块app.module.ts(配置TypeORM)

js 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserModule } from './module/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as dotenv from 'dotenv';
import { SharedModule } from './shared.module';
dotenv.config();
const mdbPath = __dirname + '/**/mdb/*.entity{.ts,.js}';

@Module({
  imports: [
    UserModule, // 用户模块
    SharedModule, // 导入 SharedModule
    // 数据库连接配置
    TypeOrmModule.forRoot({
      name: 'mdb',
      type: 'mysql',
      host: process.env.DATABASE_HOST,
      port: +process.env.DATABASE_PORT,
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE_NAME,
      retryDelay: +process.env.DATABASE_RETRYDELAY,
      retryAttempts: +process.env.DATABASE_RETRYATTEMPTS,
      entities: [mdbPath],
      synchronize: true,
      autoLoadEntities: true,
    })
  ],
  controllers: [AppController],
  providers: [],
})
export class AppModule {}

4.8:根模块导入程序入口(main.ts)

js 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { GlobalMiddleware } from './global.middleware';
import { JwtAuthGuard } from '@/auth/auth.guard';
import { JwtModule, JwtService } from '@nestjs/jwt';
import { Reflector } from '@nestjs/core';
import helmet from 'helmet';
import { json, urlencoded } from 'express';

declare const module: any;
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const jwtService = app.get(JwtService);
  const reflector = app.get(Reflector);
  // 添加 body-parser 中间件
  app.use(json());
  app.use(urlencoded({ extended: true }));
  // 全局处理(考虑效率问题)
  app.use(helmet());
  app.use(new GlobalMiddleware().use);
  app.useGlobalGuards(new JwtAuthGuard(jwtService, reflector));
  // 启用 CORS
  app.enableCors({
    exposedHeaders: ['Authorization'],  // 确保暴露 Authorization 头
  });
  await app.listen(8888);
  if (module.hot) {
    module.hot.accept();
    module.hot.dispose(() => app.close());
  }
}
bootstrap();

4.9:创建中间件(global.middleware.ts)

js 复制代码
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class GlobalMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // // 打印请求的方法和路径
    // console.log(`G-Request Method: ${req.method}`);
    // console.log(`G-Request URL: ${req.url}`);
    // // 打印请求头
    // console.log('G-Request Headers:', req.headers);
    // // 打印查询参数
    // console.log('G-Query Parameters:', req.query);
    // // 打印请求体(如果有)
    // console.log('G-Request Body:', req.body);
    // 如果路径以 /api 开头,移除这个前缀
    if (req.url.startsWith('/api')) {
      req.url = req.url.replace('/api', '');
    }
    // 从请求头中获取 Authorization 字段
    const token = req.headers['authorization'];
    if (token) {
      // 将 token 添加到响应头
      res.setHeader('Authorization', token);
    }
    next();
    // console.log(`GGGGlobal-${req.method}-${req.url}`);
  }
}

4.10:创建异常过滤器(http-exception.filter.ts)

js 复制代码
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';
// 对应的自定义异常
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp(); // 获取请求上下文
    const response = ctx.getResponse<Response>(); // 获取 response 对象
    const request = ctx.getRequest<Request>(); // 获取 request 对象
    const status = exception.getStatus(); // 获取异常的状态码

    // console.log('response:', response)
    // console.log('request:', request)
    // console.log('HttpException:')
    response.status(status).json({
      status: status,
      message: new Date().toISOString(),
      path: request.url,
    });
  }
}

经过以上步骤我们基本就将nestjs服务端的架构搭建起来了,后续开发只需要创建对应的服务、控制、模块这三个即可。

5:启动

最后,启动 NestJS 应用:

js 复制代码
pnpm run start

6:总结

通过搭建 NestJS + TypeORM + TS 的服务端应用架构,我们可以感受到这一组合的强大之处。 NestJS 提供了清晰的模块化结构和丰富的功能,使得开发变得高效,而 TypeORM 则简化了与数据库的交互。 通过合理使用中间件、异常过滤器等特性,我们可以构建出一个可维护、高效且安全的服务端应用。

7:参考

nestjs.bootcss.com/

docs.nestjs.cn/

docs.nestjs.com/graphql/qui...

www.kancloud.cn/juukee/nest...

www.yuque.com/u295415/xba...

相关推荐
山山而川粤2 小时前
母婴用品系统|Java|SSM|JSP|
java·开发语言·后端·学习·mysql
过往记忆3 小时前
告别 Shuffle!深入探索 Spark 的 SPJ 技术
大数据·前端·分布式·ajax·spark
高兴蛋炒饭4 小时前
RouYi-Vue框架,环境搭建以及使用
前端·javascript·vue.js
m0_748240444 小时前
《通义千问AI落地—中》:前端实现
前端·人工智能·状态模式
ᥬ 小月亮4 小时前
Vue中接入萤石等直播视频(更新中ing)
前端·javascript·vue.js
玉红7775 小时前
R语言的数据类型
开发语言·后端·golang
神雕杨5 小时前
node js 过滤空白行
开发语言·前端·javascript
网络安全-杰克6 小时前
《网络对抗》—— Web基础
前端·网络
m0_748250746 小时前
2020数字中国创新大赛-虎符网络安全赛道丨Web Writeup
前端·安全·web安全
周伯通*6 小时前
策略模式以及优化
java·前端·策略模式