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
🏁 简介
以下是我们要创建的应用程序架构图:
我们将为基本的 CRUD 操作创建 5 个端点:
- 创造
- 阅读全部
- 阅读一篇
- 更新
- 删除
以下是我们正在采取的步骤:
- 创建新的 NestJS 应用程序
- 为用户创建一个新模块,其中包含控制器、服务和实体
- Docker化应用程序
- 创建docker-compose.yml来运行应用程序和数据库
- 使用 Postman 和 Tableplus 测试应用程序
我们将提供一步一步的指南,以便您可以跟随。
要求:
- 节点已安装(我正在使用 v16)
- Docker 安装并运行
- (可选):Postman 和 Tableplus 可供使用,但任何测试工具都可以使用
- NestJS CLI(以下命令)
💻创建一个新的 NestJS 应用程序
我们将使用 NestJS CLI 创建我们的项目
如果您尚未安装 NestJS CLI,则可以使用以下命令安装它:
npm install -g @nestjs/cli
这将全局安装 NestJS CLI,因此您可以从任何地方使用它。
然后,您可以创建移动到您的工作区文件夹并使用以下内容创建一个新的 NestJS 应用程序(您可以nest-crud-app
用您想要的内容替换):
nest new nest-crud-app
只需按 Enter 键即可使用默认选项。
这将为您创建一个新项目(需要一段时间)。
进入目录:
cd nest-crud-app
现在安装我们需要的依赖项:
npm i pg typeorm @nestjs/typeorm @nestjs/config
- pg:NodeJS 的 Postgres 驱动程序
- typeorm:NodeJS 的 ORM
- @nestjs/typeorm:用于 TypeORM 的 NestJS 模块
- @nestjs/config:用于配置的 NestJS 模块
完成后,在您最喜欢的编辑器中打开项目(我使用 VSCode)。
code .
在开始编码之前,让我们测试一下一切是否正常。
npm start
我们应该看到类似这样的内容:
现在您可以使用 停止服务器Ctrl + C
。
🐈⬛ 创建 NestJS 应用程序
现在我们要开始开发 NestJS 应用程序。
让我们创建一个新模块、一个控制器、一个服务和一个实体。
nest g module users
nest g controller users
nest g service users
touch src/users/user.entity.ts
这将创建以下文件(以及另外 2 个我们不会使用的测试文件)
- src/用户/用户.module.ts
- src/用户/用户.控制器.ts
- src/users/users.service.ts
- src/用户/用户.entity.ts
您的文件夹结构应如下所示:
现在让我们处理这 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;
}
解释:
-
我们使用装饰器
@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);
}
}
解释:
-
我们使用装饰器
@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);
}
}
解释:
-
我们使用装饰器
@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 {}
解释:
-
我们正在导入 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 {}
解释:
-
我们正在导入 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
.dockerignore
.dockerignore 文件用于告诉 Docker 在构建镜像时要忽略哪些文件和目录。
如果您熟悉 .gitignore 文件,它的工作方式相同。
打开文件“.dockerignore”并像这样填充它:
node_modules
dist
.git
这将告诉 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"]
解释:
FROM
node: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: {}
解释:
-
我们使用的是 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
这将以分离模式运行数据库服务。
要检查服务是否正在运行,我们可以使用 docker ps 命令:
docker ps -a
我们应该看到类似这样的内容:
但是让我们用 TablePlus 检查一下。打开 TablePlus 应用程序并通过创建一个新的“Postgres”连接来连接到数据库。
您可以使用 UI 并设置:
- 主机:localhost
- 端口:5432
- 用户名:postgres
- 密码:postgres
- 数据库:postgres
然后点击右下角的“连接”按钮。
现在我们准备构建 Nest 应用程序映像并运行该应用程序。
构建 Nest 应用映像
为了构建 Nest 应用程序映像,我们将使用 docker compose 命令。
docker compose build
这将从 Dockerfile 构建镜像。
要检查镜像是否已构建,我们可以使用 docker images 命令:
运行 Nest 应用服务
要运行 Nest 应用服务,我们将使用 docker-compose 命令。
docker compose up
测试应用程序
为了测试应用程序,我们可以使用 Postman 或任何其他 API 客户端。
首先,让我们测试一下应用程序是否正在运行。打开 Postman 并创建一个新的 GET 请求。
获取所有用户
为了获取所有用户,我们可以创建一个GET request to localhost:3000/users
。
如果我们看到一个空数组,则意味着它正在工作。
创建用户
要创建用户,我们可以创建一个POST request to localhost:3000/users
。
在正文中,我们可以使用原始 JSON 格式并设置以下数据:
{
"name": "aaa",
"email": "aaa@mail"
}
您可以使用以下数据创建另外 2 个用户:
{
"name": "bbb",
"email": "bbb@mail"
}
{
"name": "ccc",
"email": "ccc@mail"
}
获取所有三个用户
为了获得所有三个用户,我们可以创建一个GET request to localhost:3000/users
。
通过 ID 获取用户
为了获得单个用户,我们可以创建一个GET request to localhost:3000/users/2
。
更新用户
要更新用户,我们可以创建一个PUT request to localhost:3000/users/2
。
让我们将名称从“bbb”更改为“Francesco”,将电子邮件从“bbb@mail”更改为“francesco@mail”。
{
"name":"Francesco",
"email":"francesco@mail"
}
删除用户
最后,要删除用户,我们可以创建一个DELETE request to localhost:3000/users/3
。
答案直接来自数据库。
使用 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