使用 Node、Express、MongoDB 和 Docker 构建 API
在本教程中,我们将使用 TypeScript 和 Docker,通过 Node、Express 和 MongoDB 从头构建一个 Menu Restaurant API 应用。Docker 部分是可选的。
基本上,我们应该能够:
- 检索所有菜单
- 检索一个菜单
- 创建菜单
- 更新菜单
- 删除菜单
太好了,让我们开始吧。
设置
要创建一个新的 Nodejs 项目,我们首先在终端上运行此命令。
yarn init
在初始化项目之前,它会询问几个问题。无论如何,您可以通过-y
在命令中添加一个标志来绕过这些问题。
下一步是为我们的项目创建一个结构。
├── dist
├── src
├── app.ts
├── controllers
| └── menus
| └── index.ts
├── models
| └── menu.ts
├── routes
| └── index.ts
└── types
└── menu.ts
├── nodemon.json
├── package.json
├── tsconfig.json
让我快速解释一下该项目的结构。
dist
一旦 TypeScript 代码被编译为纯 JavaScript,它将作为输出文件夹。src
将包含我们的 API 的逻辑。app.ts
是服务器的入口点。controllers
将包含处理请求并将数据从模型返回到客户端的函数models
将包含允许对我们的数据库进行基本操作的对象。
routes
用于将请求转发到适当的控制器。types
将包含我们在此项目中的对象的接口。
接下来,让我们添加一些配置tsconfig.json
。这将帮助计算机按照我们的偏好进行开发。
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "dist/js",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["src/types/*.ts", "node_modules", ".vscode", ".idea"]
}
现在我们可以开始安装依赖项来启动我们的项目了。但首先,让我们启用 TypeScript。
yarn add typescript
我们还添加一些依赖项来使用 Express 和 MongoDB。
yarn add express cors mongoose
接下来,我们将添加它们的类型作为开发依赖项。这将有助于 TypeScript 计算机理解这些包。
yarn add -D @types/node @types/express @types/mongoose @types/cors
让我们添加一些依赖项,以便在文件被修改时自动重新加载服务器并同时启动服务器(我们将能够同时进行更改并启动服务器)。
yarn add -D concurrently nodemon
我们需要package.json
使用启动服务器和构建项目所需的脚本来更新该文件。文件
内容如下package.json
。
{
"name": "menu-node-api",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"mongoose": "^6.0.11",
"nodemon": "^2.0.13",
"typescript": "^4.4.4"
},
"scripts": {
"build": "tsc",
"start": "concurrently \"tsc -w\" \"nodemon dist/js/app.js\""
},
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/mongoose": "^5.11.97",
"@types/node": "^16.11.1",
"concurrently": "^6.3.0"
}
}
项目已准备就绪。我们现在可以开始编码了。:)
构建 API
我们的工作方式如下:
- 创建菜单类型
- 创建菜单模型
- 创建菜单控制器
- 添加菜单路由
- 配置
app.ts
连接到 Mongo Atlas 并启动服务器。
创建菜单类型
我们将编写一个 Menu 接口,它将扩展Document
提供的类型mongoose
。稍后与 MongoDB 交互时会用到它。
import { Document } from "mongoose";
export interface IMenu extends Document {
name: string;
description: string;
price: number;
}
创建菜单模型
import { IMenu } from "../types/menu";
import { model, Schema } from "mongoose";
const menuSchema: Schema = new Schema(
{
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
price: {
type: String,
required: true,
},
},
{ timestamps: true }
);
export default model<IMenu>("Menu", menuSchema);
mongoose
提供创建模型的实用工具。请注意,此处IMenu
用作导出模型之前的类型。
现在模型已经写好了,我们可以开始与其他文件上的数据库进行交互。
创建控制器
我们将在这里编写 5 个控制器。
getMenus
:获取数据库中所有的菜单对象addMenu
:创建菜单updateMenu
:更新菜单deleteMenu
:删除菜单retrieveMenu
:检索菜单
让我们从开始吧getMenus
。
// ./src/controllers/menus/index.ts
import { Response, Request } from "express";
import { IMenu } from "../../types/menu";
import Menu from "../../models/menu";
const getMenus = async (req: Request, res: Response): Promise<void> => {
try {
const menus: IMenu[] = await Menu.find();
res.status(200).json({ menus });
} catch (error) {
throw error;
}
};
首先,我们导入Request
并Response
键入,express
以便明确输入值。下一步,getMenus
创建函数以从数据库中获取数据。
- 它接收
req
和res
参数并返回一个承诺 - 在之前创建的模型的帮助下
Menu
,我们现在可以从 MongoDB 中检索所有内容menus
并返回包含这些对象的响应。
太好了,让我们转到addMenu
控制器。
const addMenu = async (req: Request, res: Response): Promise<void> => {
try {
const body = req.body as Pick<IMenu, "name" | "description" | "price">;
const menu: IMenu = new Menu({
name: body.name,
description: body.description,
price: body.price,
});
const newMenu: IMenu = await menu.save();
res.status(201).json(newMenu);
} catch (error) {
throw error;
}
};
与 稍有不同getMenus
,此函数现在接收一个包含用户输入的数据的 body 对象。
接下来,我们使用类型转换来避免类型并确保body
变量匹配IMenu
,然后我们创建一个新的Menu
并将其保存Menu
在数据库中。
const retrieveMenu = async (req: Request, res: Response): Promise<void> => {
try {
const {
params: { id },
} = req;
const menu: IMenu | null = await Menu.findById({ _id: id });
res.status(menu ? 200 : 404).json({ menu });
} catch (error) {
throw error;
}
};
id
该函数将从对象中提取req
,然后将其作为参数传递给findById
方法以访问对象并将其返回给客户端。
const updateMenu = async (req: Request, res: Response): Promise<void> => {
try {
const {
params: { id },
body,
} = req;
const updateMenu: IMenu | null = await Menu.findByIdAndUpdate(
{ _id: id },
body
);
res.status(updateMenu ? 200 : 404).json({
menu: updateMenu,
});
} catch (error) {
throw error;
}
};
此函数接受一个id
参数,但也接受body
对象。
接下来,我们使用findByIdAndUpdate
从数据库中检索相应的菜单并进行更新。
const deleteMenu = async (req: Request, res: Response): Promise<void> => {
try {
const deletedMenu: IMenu | null = await Menu.findByIdAndRemove(
req.params.id
);
res.status(204).json({
todo: deletedMenu,
});
} catch (error) {
throw error;
}
};
此函数允许我们从数据库中删除菜单。
在这里,我们提取表单id
并将req
其作为参数传递给findByIdAndRemove
方法,以访问相应的菜单并将其从数据库中删除。
我们已经准备好控制器并将它们导出。
这是该文件的最终代码src/controllers/menus/index.ts
。
import { Response, Request } from "express";
import { IMenu } from "../../types/menu";
import Menu from "../../models/menu";
const getMenus = async (req: Request, res: Response): Promise<void> => {
try {
const menus: IMenu[] = await Menu.find();
res.status(200).json({ menus });
} catch (error) {
throw error;
}
};
const retrieveMenu = async (req: Request, res: Response): Promise<void> => {
try {
const {
params: { id },
} = req;
const menu: IMenu | null = await Menu.findById({ _id: id });
res.status(menu ? 200 : 404).json({ menu });
} catch (error) {
throw error;
}
};
const addMenu = async (req: Request, res: Response): Promise<void> => {
try {
const body = req.body as Pick<IMenu, "name" | "description" | "price">;
const menu: IMenu = new Menu({
name: body.name,
description: body.description,
price: body.price,
});
const newMenu: IMenu = await menu.save();
res.status(201).json(newMenu);
} catch (error) {
throw error;
}
};
const updateMenu = async (req: Request, res: Response): Promise<void> => {
try {
const {
params: { id },
body,
} = req;
const updateMenu: IMenu | null = await Menu.findByIdAndUpdate(
{ _id: id },
body
);
res.status(updateMenu ? 200 : 404).json({
menu: updateMenu,
});
} catch (error) {
throw error;
}
};
const deleteMenu = async (req: Request, res: Response): Promise<void> => {
try {
const deletedMenu: IMenu | null = await Menu.findByIdAndRemove(
req.params.id
);
res.status(204).json({
todo: deletedMenu,
});
} catch (error) {
throw error;
}
};
export { getMenus, addMenu, updateMenu, deleteMenu, retrieveMenu };
API 路由
我们将创建五条路由,分别用于从数据库中获取、创建、更新和删除菜单。我们将使用之前创建的控制器,并将它们作为参数传递,以便在定义路由时处理请求。
import { Router } from "express";
import {
getMenus,
addMenu,
updateMenu,
deleteMenu,
retrieveMenu,
} from "../controllers/menus";
const menuRoutes: Router = Router();
menuRoutes.get("/menu", getMenus);
menuRoutes.post("/menu", addMenu);
menuRoutes.put("/menu/:id", updateMenu);
menuRoutes.delete("/menu/:id", deleteMenu);
menuRoutes.get("/menu/:id", retrieveMenu);
export default menuRoutes;
创建服务器
首先,让我们添加一些包含 MongoDB 数据库凭据的环境变量。
// .nodemon.js
{
"env": {
"MONGO_USER": "your-username",
"MONGO_PASSWORD": "your-password",
"MONGO_DB": "your-db-name"
}
}
您可以通过在MongoDB Atlas上创建新集群来获取凭据。
由于这些是数据库凭证,请确保不要将凭证推送到存储库或暴露它们。
// .src/app.ts
import express from "express";
import mongoose from "mongoose";
import cors from "cors";
import menuRoutes from "./routes";
const app = express();
const PORT: string | number = process.env.PORT || 4000;
app.use(cors());
app.use(express.json());
app.use(menuRoutes);
const uri: string = `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@cluster0.raz9g.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`
mongoose
.connect(uri)
.then(() =>
app.listen(PORT, () =>
console.log(`Server running on http://localhost:${PORT}`)
)
)
.catch((error) => {
throw error;
});
我们首先导入express
库来使用use
方法来处理菜单路由。
接下来,我们使用 mongoose 包通过将文件中保存的凭据附加到 URL 来连接到 MongoDB nodemon.json
。
现在,如果与 MongoDB 数据库的连接成功,服务器将启动,否则将抛出错误。
我们现在已经使用 Node、Express、TypeScript 和 MongoDB 构建了 API。
要启动您的项目,请运行yarn start
并点击http://localhost:4000
。
以下是您可以使用 Postman 或 Insomnia 对 API 进行的一些测试。
获取所有菜单-
http://localhost:4000/menu
GET http://localhost:4000/menu
创建菜单-
http://localhost:4000/menu
POST http://localhost:4000/menu
Content-Type: application/json
{
"name": "Hot Dog",
"description": "A hot dog",
"price": 10
}
更新菜单-
http://localhost:4000/menu/<menuId>
PUT http://localhost:4000/menu/<menuId>
Content-Type: application/json
{
"price": 5
}
现在让我们将项目docker化。
Docker + Docker Compose(可选)
Docker是一个用于在容器内开发、交付和运行应用程序的开放平台。
为什么要使用 Docker?因为
它可以帮助您将应用程序与基础架构分离,并有助于更快地交付代码。
如果这是您第一次使用 Docker,我强烈建议您阅读快速教程并阅读一些相关文档。
以下是一些对我有帮助的优秀资源:
Dockerfile
代表Dockerfile
一个文本文档,其中包含可以在命令行上调用以创建图像的所有命令。
将 Dockerfile 添加到项目根目录:
FROM node:16-alpine
WORKDIR /app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
在这里,我们从基于 Alpine 的 Node.js Docker 镜像开始。它是一个专为安全性和资源效率而设计的轻量级 Linux 发行版。
之后,我们执行如下操作:
- 设置工作变量
- 复制
package.json
文件yarn.lock
到我们的应用程序路径 - 安装项目依赖项
- 最后复制整个项目
另外,让我们添加一个.dockerignore
文件。
.dockerignore
Dockerfile
node_modules
完成后,我们现在可以添加 docker-compose。
Docker Compose是一款很棒的工具(<3)。你可以使用它来定义和运行多容器 Docker 应用程序。
我们需要什么?嗯,只需要一个包含我们应用程序服务所有配置的 YAML 文件。
然后,使用docker-compose
命令,我们可以创建并启动所有这些服务。
version: '3.8'
services:
api:
container_name: node_api
restart: on-failure
build: .
volumes:
- ./src:/app/src
ports:
- "4000:4000"
command: >
sh -c "yarn start"
设置已完成。让我们构建容器并测试一切是否在本地正常运行。
docker-compose up -d --build
您的项目将在 上运行https://localhost:4000/
。
结论
在本文中,我们学习了如何使用 NodeJS、TypeScript、Express、MongoDB 和 Docker 构建 API。
每篇文章都可以变得更好,所以欢迎在评论区提出您的建议或问题。😉
在此处查看本教程的代码。
文章来源:https://dev.to/koladev/build-an-api-using-node-express-mongodb-and-docker-128p