TypeScript CRUD Rest API,使用 Nest.js、TypeORM、Postgres、Docker 和 Docker Compose

2025-05-24

TypeScript CRUD Rest API,使用 Nest.js、TypeORM、Postgres、Docker 和 Docker Compose

让我们在 Typescript 中创建一个 CRUD Rest API,使用:

  • NestJS(NodeJS 框架)
  • TypeORM(ORM:对象关系映射器)
  • Postgres(关系数据库)
  • Docker(用于容器化)
  • Docker Compose

如果您更喜欢视频版本:

所有代码均可在 GitHub 存储库中找到(链接在视频描述中):https://www.youtube.com/live/gqFauCpPSlw


🏁 简介

以下是我们要创建的应用程序架构图:

使用 Docker Compose 连接 NestJS 应用和 Postgres 服务,进行 crud、read、update、delete 操作。使用 Postman 和 Tableplus 进行测试

我们将为基本的 CRUD 操作创建 5 个端点:

  • 创造
  • 阅读全部
  • 阅读一篇
  • 更新
  • 删除

以下是我们正在采取的步骤:

  1. 创建新的 NestJS 应用程序
  2. 为用户创建一个新模块,其中包含控制器、服务和实体
  3. Docker化应用程序
  4. 创建docker-compose.yml来运行应用程序和数据库
  5. 使用 Postman 和 Tableplus 测试应用程序

我们将提供一步一步的指南,以便您可以跟随。


要求:

  • 节点已安装(我正在使用 v16)
  • Docker 安装并运行
  • (可选):Postman 和 Tableplus 可供使用,但任何测试工具都可以使用
  • NestJS CLI(以下命令)

💻创建一个新的 NestJS 应用程序

我们将使用 NestJS CLI 创建我们的项目

如果您尚未安装 NestJS CLI,则可以使用以下命令安装它:



npm install -g @nestjs/cli


Enter fullscreen mode Exit fullscreen mode

这将全局安装 NestJS CLI,因此您可以从任何地方使用它。

然后,您可以创建移动到您的工作区文件夹并使用以下内容创建一个新的 NestJS 应用程序(您可以nest-crud-app用您想要的内容替换):



nest new nest-crud-app


Enter fullscreen mode Exit fullscreen mode

只需按 Enter 键即可使用默认选项。
这将为您创建一个新项目(需要一段时间)。

成功创建项目 nest-crud-app

进入目录:



cd nest-crud-app


Enter fullscreen mode Exit fullscreen mode

现在安装我们需要的依赖项:



npm i pg typeorm @nestjs/typeorm @nestjs/config


Enter fullscreen mode Exit fullscreen mode
  • pg:NodeJS 的 Postgres 驱动程序
  • typeorm:NodeJS 的 ORM
  • @nestjs/typeorm:用于 TypeORM 的 NestJS 模块
  • @nestjs/config:用于配置的 NestJS 模块

完成后,在您最喜欢的编辑器中打开项目(我使用 VSCode)。



code .


Enter fullscreen mode Exit fullscreen mode

在开始编码之前,让我们测试一下一切是否正常。



npm start


Enter fullscreen mode Exit fullscreen mode

我们应该看到类似这样的内容:

左侧为 Hello World,VS Code 运行 npm start 命令,NestJS 脚手架项目

现在您可以使用 停止服务器Ctrl + C

🐈‍⬛ 创建 NestJS 应用程序

现在我们要开始开发 NestJS 应用程序。

让我们创建一个新模块、一个控制器、一个服务和一个实体。



nest g module users
nest g controller users
nest g service users
touch src/users/user.entity.ts


Enter fullscreen mode Exit fullscreen mode

这将创建以下文件(以及另外 2 个我们不会使用的测试文件)

  • src/用户/用户.module.ts
  • src/用户/用户.控制器.ts
  • src/users/users.service.ts
  • src/用户/用户.entity.ts

您的文件夹结构应如下所示:

NestJS 应用程序的文件夹结构,这 4 个文件位于 src 中名为 users 的文件夹中

现在让我们处理这 4 个文件。

用户实体

打开文件“src/users/user.entity.ts”并像这样填充:



import { Entity, PrimaryGeneratedColumn, Column,  } from "typeorm";

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    email: string;
}


Enter fullscreen mode Exit fullscreen mode

解释:

  • 我们使用装饰器@Entity()告诉 TypeORM 这是一个实体

  • 我们使用装饰器@PrimaryGeneratedColumn()告诉 TypeORM 这是表的主键

  • 我们使用装饰器@Column()告诉 TypeORM 这是表的一列

我们正在创建一个具有 3 列的用户实体:id、name 和 email。

用户服务

打开文件“src/users/users.service.ts”并像这样填充:



import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {User} from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async findAll(): Promise<User[]> {
    return this.userRepository.find();
  }

  async findOne(id: number): Promise<User> {
    return this.userRepository.findOne({ where: { id } });
  }

  async create(user: Partial<User>): Promise<User> {
    const newuser = this.userRepository.create(user);
    return this.userRepository.save(newuser);
  }

  async update(id: number, user: Partial<User>): Promise<User> {
    await this.userRepository.update(id, user);
    return this.userRepository.findOne({ where: { id } });
  }

  async delete(id: number): Promise<void> {
    await this.userRepository.delete(id);
  }
}


Enter fullscreen mode Exit fullscreen mode

解释:

  • 我们使用装饰器@Injectable()告诉 NestJS 这是一个服务

  • 我们使用装饰器@InjectRepository(User)告诉 NestJS 我们想要注入 User 实体的存储库

  • 我们使用装饰器@Repository(User)告诉 NestJS 我们想要注入 User 实体的存储库

  • 我们正在创建一个具有 5 种方法的 UserService:findAll、findOne、create、update 和 delete

用户控制器

打开文件“src/users/users.controller.ts”并像这样填充:



import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './user.entity';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  //get all users
  @Get()
  async findAll(): Promise<User[]> {
    return this.usersService.findAll();
  }

  //get user by id
  @Get(':id')
  async findOne(@Param('id') id: number): Promise<User> {
    const user = await this.usersService.findOne(id);
    if (!user) {
      throw new NotFoundException('User does not exist!');
    } else {
      return user;
    }
  }

  //create user
  @Post()
  async create(@Body() user: User): Promise<User> {
    return this.usersService.create(user);
  }

  //update user
  @Put(':id')
  async update (@Param('id') id: number, @Body() user: User): Promise<any> {
    return this.usersService.update(id, user);
  }

  //delete user
  @Delete(':id')
  async delete(@Param('id') id: number): Promise<any> {
    //handle error if user does not exist
    const user = await this.usersService.findOne(id);
    if (!user) {
      throw new NotFoundException('User does not exist!');
    }
    return this.usersService.delete(id);
  }
}



Enter fullscreen mode Exit fullscreen mode

解释:

  • 我们使用装饰器@Controller('users')告诉 NestJS 这是一个控制器,并且路由是“用户”

  • 我们正在定义类的构造函数,并注入 UserService

  • 我们定义了 5 个方法:findAll、findOne、create、update 和 delete,并用我们想要使用的 HTTP 方法修饰,然后使用 UserService 调用相应的方法

用户模块

打开文件“src/users/users.module.ts”并像这样填充:



import { Module } from '@nestjs/common';
import { UserController } from './users.controller';
import { UserService } from './users.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService]
})
export class UsersModule {}


Enter fullscreen mode Exit fullscreen mode

解释:

  • 我们正在导入 TypeOrmModule 和 User 实体(UserController 和 UserService 已经导入)

  • 我们使用装饰器@Module()告诉 NestJS 这是一个模块

  • 我们将 TypeOrmModule.forFeature([User]) 添加到 imports 数组中,以告诉 NestJS 我们想要使用 User 实体

更新主模块

打开文件“src/app.module.ts”并像这样填充:



import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';


@Module({
  imports: [
    ConfigModule.forRoot(),
    UsersModule,
    TypeOrmModule.forRoot({
      type: process.env.DB_TYPE as any,
      host: process.env.PG_HOST,
      port: parseInt(process.env.PG_PORT),
      username: process.env.PG_USER,
      password: process.env.PG_PASSWORD,
      database: process.env.PG_DB,
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}


Enter fullscreen mode Exit fullscreen mode

解释:

  • 我们正在导入 ConfigModule、UsersModule 和 TypeOrmModule

  • 我们在 imports 数组中导入 ConfigModule、UsersModule 和 TypeOrmModule

  • 对于 TypeOrmModule,我们使用该方法forRoot()告诉 NestJS 我们要使用默认连接,并定义一些环境变量来连接数据库。我们很快会在 docker-compose.yml 文件中进行设置。

  • 同步选项设置为 true,以便在应用程序启动时自动更新数据库模式


🐳 Docker 化应用程序

让我们创建 3 个文件来将应用程序 dockerize:一个 Dockerfile 和一个 .dockerignore 文件。



touch Dockerfile .dockerignore docker-compose.yml


Enter fullscreen mode Exit fullscreen mode

.dockerignore

.dockerignore 文件用于告诉 Docker 在构建镜像时要忽略哪些文件和目录。

如果您熟悉 .gitignore 文件,它的工作方式相同。

打开文件“.dockerignore”并像这样填充它:



node_modules
dist
.git


Enter fullscreen mode Exit fullscreen mode

这将告诉 Docker 在构建镜像时忽略 node_modules、dist 和 .git 目录。

Dockerfile

Dockerfile 是一个文本文档,其中包含用户可以在命令行上调用来组装图像的所有命令。

打开文件“Dockerfile”并按如下方式填充:



FROM node:16

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["npm", "run", "start:prod"]


Enter fullscreen mode Exit fullscreen mode

解释:

FROMnode:16 用于告诉 Docker 使用哪个镜像作为基础镜像。

WORKDIR是命令执行的目录。在我们的例子中,它是 /app 目录。

COPY package*.json用于将 package.json 和 package-lock.json 文件复制到 /app 目录。

RUN npm install用于安装依赖项。

COPY . .用于将当前目录中的所有文件复制到 /app 目录。

RUN npm run build用于构建应用程序。

EXPOSE用于向主机公开端口 3000。

CMD用于在容器启动时执行命令,在我们的例子中,它是“npm run start:prod”。

docker-compose.yml 文件

我们将使用 docker compose 来运行应用程序和数据库。

像这样填充文件“docker-compose.yml”:



version: '3.9'
services:
  nestapp:
    container_name: nestapp
    image: francescoxx/nestapp:1.0.0
    build: .
    ports:
      - '3000:3000'
    environment:
      - DB_TYPE=postgres
      - PG_USER=postgres
      - PG_PASSWORD=postgres
      - PG_DB=postgres
      - PG_PORT=5432
      - PG_HOST=db
    depends_on:
      - db
  db:
    container_name: db
    image: postgres:12
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    ports:
      - '5432:5432'
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata: {}


Enter fullscreen mode Exit fullscreen mode

解释:

  • 我们使用的是 docker-compose.yml 文件格式的 3.9 版本

  • 我们定义了两个服务:nestapp 和 db

  • nestapp 服务用于运行 NestJS 应用程序

  • db 服务用于运行 Postgres 数据库

  • nestapp 服务依赖于 db 服务,因此 db 服务在 nestapp 服务之前启动

对于nestapp服务:

container_name用于设置容器的名称

image用于设置要使用的图像,在我们的例子中,它是 francescoxx/nestapp:1.0.0 将 francescoxxx 更改为您的 docker hub 用户名

build用于从 Dockerfile 构建镜像。我们使用当前目录作为构建上下文。

ports用于向主机公开端口 3000

environment用于设置环境变量:DB_TYPE、PG_USER、PG_PASSWORD、PG_DB、PG_PORT、PG_HOST。这些变量将被应用程序用于连接数据库

depends_on用于告诉docker-compose必须在nestapp服务之前启动db服务。

对于数据库服务:

container_name用于设置容器的名称

image用于设置要使用的图像,在我们的例子中,它是 postgres:12

environment用于设置环境变量:POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB

ports用于向主机公开端口 5432

volumes用于将卷挂载到容器。在本例中,我们将 pgdata 卷挂载到 /var/lib/postgresql/data 目录。

我们还在文件末尾定义了 pgdata 卷。

运行 Postgres 服务

要运行 Postgres 服务,我们将使用 docker-compose 命令。



docker compose up -d db


Enter fullscreen mode Exit fullscreen mode

这将以分离模式运行数据库服务。

要检查服务是否正在运行,我们可以使用 docker ps 命令:



docker ps -a


Enter fullscreen mode Exit fullscreen mode

我们应该看到类似这样的内容:

$ docker ps -a<br> 容器 ID 镜像 命令 创建状态 端口 名称<br> e045f74a36ca postgres:12

但是让我们用 TablePlus 检查一下。打开 TablePlus 应用程序并通过创建一个新的“Postgres”连接来连接到数据库。

您可以使用 UI 并设置:

  • 主机:localhost
  • 端口:5432
  • 用户名:postgres
  • 密码:postgres
  • 数据库:postgres

然后点击右下角的“连接”按钮。

TablePlus 应用程序

现在我们准备构建 Nest 应用程序映像并运行该应用程序。

构建 Nest 应用映像

为了构建 Nest 应用程序映像,我们将使用 docker compose 命令。



docker compose build


Enter fullscreen mode Exit fullscreen mode

这将从 Dockerfile 构建镜像。

要检查镜像是否已构建,我们可以使用 docker images 命令:

$ docker images<br> 存储库标签图像 ID 创建大小<br> francescoxx/nestapp 1.0.0 53267c897590 大约一小时前 1.16GB<br> postgres 12 1db9fa309607 44 小时前 373MB

运行 Nest 应用服务

要运行 Nest 应用服务,我们将使用 docker-compose 命令。



docker compose up 


Enter fullscreen mode Exit fullscreen mode

测试应用程序

为了测试应用程序,我们可以使用 Postman 或任何其他 API 客户端。

首先,让我们测试一下应用程序是否正在运行。打开 Postman 并创建一个新的 GET 请求。

Postman 获取请求到 localhost:3000

获取所有用户

为了获取所有用户,我们可以创建一个GET request to localhost:3000/users

如果我们看到一个空数组,则意味着它正在工作。

Postman 获取对 localhost:3000/users 的请求

创建用户

要创建用户,我们可以创建一个POST request to localhost:3000/users

在正文中,我们可以使用原始 JSON 格式并设置以下数据:



{
  "name": "aaa",
  "email": "aaa@mail"
}


Enter fullscreen mode Exit fullscreen mode

Postman 将请求发布到 localhost:3000/users

您可以使用以下数据创建另外 2 个用户:



{
  "name": "bbb",
  "email": "bbb@mail"
}


Enter fullscreen mode Exit fullscreen mode


{
  "name": "ccc",
  "email": "ccc@mail"
}


Enter fullscreen mode Exit fullscreen mode

获取所有三个用户

为了获得所有三个用户,我们可以创建一个GET request to localhost:3000/users

Postman 获取对 localhost:3000/users 的请求

通过 ID 获取用户

为了获得单个用户,我们可以创建一个GET request to localhost:3000/users/2

Postman 获取对 localhost:3000/users/2 的请求

更新用户

要更新用户,我们可以创建一个PUT request to localhost:3000/users/2

让我们将名称从“bbb”更改为“Francesco”,将电子邮件从“bbb@mail”更改为“francesco@mail”。



{
  "name":"Francesco",
  "email":"francesco@mail"
}


Enter fullscreen mode Exit fullscreen mode

Postman PUT 请求到 localhost:3000/users/2

删除用户

最后,要删除用户,我们可以创建一个DELETE request to localhost:3000/users/3

Postman DELETE 请求到 localhost:3000/users/3

答案直接来自数据库。

使用 TablePlus 进行最终测试

让我们检查数据是否正确存储在数据库中。

作为最后的测试,让我们回到 TablePlus 并检查数据是否已更新。

TablePlus 应用程序


🏁 结论

我们成功了!我们用 TypeScript 构建了一个 CRUD rest API,使用:

  • NestJS(NodeJS 框架)
  • TypeORM(ORM:对象关系映射器)
  • Postgres(关系数据库)
  • Docker(用于容器化)
  • Docker Compose

如果您更喜欢视频版本:

所有代码均可在 GitHub 存储库中找到(链接在视频描述中):https://www.youtube.com/live/gqFauCpPSlw

就这样。

如果您有任何疑问,请在下面发表评论。

弗朗西斯科

文章来源:https://dev.to/francescoxx/typescript-crud-rest-api-using-nestjs-typeorm-postgres-docker-and-docker-compose-33al
PREV
Guia Completo para Usar o 虚拟环境 (venv) 无 Python
NEXT
使用 Nodejs、Express、Sequelize、Postgres、Docker 和 Docker Compose 的 JavaScript CRUD Rest API 简介分步指南 Docker 部分构建 Docker 镜像并运行 Docker 容器结论