使用Node.js + Express + MongoDB从零开始快速构建REST API
概括本次目标是使用Node.js + Express + MongoDB来创建一个REST API的功能。关于详细说明,请阅读各个链接。但在此之前,最重要的是快速创建一个能够正常运行的系统。
只要把这些组合起来,就可以轻松地使用例子中的/users/:id API从数据库中获取用户信息的代码。
app.get("/users/:id", async (req, res) => {
const id = req.params.id
const users = await usersResource.find({ _id: id });
res.status(200).json(users);
});
让我们一起动手来尝试做一下,是非如何?
另外,示例代码可以在这里找到。
前提 tí)
环境
-
MacOS
-
- MacOS
-
- Homebrew
未インストールの方は 公式サイト を参照
Docker
未インストールの方は 公式ドキュメント を参照
在阅读本文章时我们将基于以下知识进行进一步操作。
-
JavaScript, TypeScript, Node.js のある程度の知識
- JavaScript, TypeScript, Node.js のある程度の知識
- Dockerのある程度の知識(なくてもなんとなくでいけます)
本次使用的物品在解释所用物品的同时,我们将以飞快的速度进行安装。
Node.js
的中文释义是 “节点.js”。首先要安装Node.js。如果您已经安装过了,请跳过这一步。由于我经常需要切换Node.js的版本,所以我通常使用nvm进行安装,但为了速度考虑,我将选择直接安装它。
$ brew install node
的中文释义是 “节点.js”。首先要安装Node.js。如果您已经安装过了,请跳过这一步。由于我经常需要切换Node.js的版本,所以我通常使用nvm进行安装,但为了速度考虑,我将选择直接安装它。
$ brew install node
执行命令后会弹出提示说”请设置PATH环境变量”,请按照提示的方法设置好路径。我个人使用zsh,所以我在~/.zshrc文件中添加了以下内容。请不要忘记在添加后执行source ~/.zshrc命令。
export PATH=/usr/local/opt/node/bin:$PATH
我会检查一下是否安装成功。
$ node -v
v18.12.1
好的。
TypeScript 是一种编程语言。首先,创建一个 Node.js 项目。
# プロジェクトディレクトリ作成
$ mkdir sample-express-mongoose
$ cd sample-express-mongoose
# package.json の作成
$ npm init --yes
# プロジェクトディレクトリ作成
$ mkdir sample-express-mongoose
$ cd sample-express-mongoose
# package.json の作成
$ npm init --yes
需要在添加到已打开的 package.json 文件中的内容中添加一行。
"version": "1.0.0",
"description": "Express + Mongoose の REST API 作成サンプルです。",
"main": "index.js",
+ "type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
接下来我们安装和配置TypeScript。在这里我们也顺便安装了非常方便的工具ts-node(稍后会用到)。
# TypeScript をインストール
$ npm install --save-dev typescript @types/node
$ npm install --save-dev ts-node
# tsconfig.json 作成
$ npx tsc --init
将tsconfig.json的内容更改如下。
- "target": "es2016",
+ "target": "es2020", /* 新しい構文もトランスパイル可能に */
- "module": "commonjs",
+ "module": "esnext", /* ES Modules も解釈可能に */
- // "moduleResolution": "node",
+ "moduleResolution": "node", /* npmでインストールしたモジュールも認識可能に */
- // "outDir": "./",
+ "outDir": "./dist", /* コンパイル結果の出力先を指定 */
- }
+ },
+ "include": ["./src/**/*.ts"] /* src以下の全てのtsファイルをコンパイル対象に */
表达 (Express)Express 是一个在 Node.js 中可以方便地创建 WEB 应用程序的框架。这次我们将使用它来快速创建 REST API。请在这里进行安装。
# Express をインストール
$ npm install --save-dev @types/express express
蒙古数据库
在Express中,可以使用MySQL、Redis、MongoDB等常见的数据库。我想在这里尝试使用MongoDB。
MongoDB是著名的NoSQL数据库之一。它可以以JSON格式存储数据,因此可以灵活且直观地管理数据。此外,与关系型数据库(RDB)不同,MongoDB不需要规范化数据或担心数据的一致性,因此可以更容易地进行水平扩展。
# Express をインストール
$ npm install --save-dev @types/express express
在Express中,可以使用MySQL、Redis、MongoDB等常见的数据库。我想在这里尝试使用MongoDB。
MongoDB是著名的NoSQL数据库之一。它可以以JSON格式存储数据,因此可以灵活且直观地管理数据。此外,与关系型数据库(RDB)不同,MongoDB不需要规范化数据或担心数据的一致性,因此可以更容易地进行水平扩展。
这次我们将使用Docker来构建MongoDB的环境。
创建docker-compose.yml文件,并进行以下描述。
在这里,我们将设置MongoDB的root用户和密码。
version: '3.8'
services:
mongo:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password
ports:
- "27017:27017"
我来试试看。
docker-compose up -d
我们进去容器里面检查一下。
# コンテナが立ち上がっているか確認
$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
sample-express-mongoose-mongo-1 "docker-entrypoint.s…" mongo running 0.0.0.0:27017->27017/tcp
# コンテナの中に入る
$ docker exec -it sample-express-mongoose-mongo-1 /bin/sh
# mongosh を使ってDBにログインしてみる。先ほどのユーザ、パスワードを入力する。
$ mongosh -u root
Enter password: password
# データベースを users に切り替える
test> use users
switched to db users
# データベースにusersコレクションを作り値を入れてみる
users> db.users.insertOne({name: "nyanchu"})
{
acknowledged: true,
insertedId: ObjectId("638c0fb94ceb083d25f210f6")
}
# コレクションを表示してみる
users> db.users.find()
[ { _id: ObjectId("638c0fb94ceb083d25f210f6"), name: 'nyanchu' } ]
# 終わり
users> quit
我可以在MongoDB中存储和检索值。
您可能注意到,我们之前并没有创建数据库或集合,但仍然可以使用”use”和”insert”命令。这是因为MongoDB的设计是,在插入值时会自动创建相应的数据库和集合。
此外,您可能还注意到,插入的集合会自动赋予一个名为”_id”的值。在MongoDB中,集合会自动分配一个唯一的ID。
我打算稍后从API中引用在此处输入的数据。
猫鼬(マングース)在 Express 中,作为 MongoDB 的 ORM,很受欢迎的是 mongoose。ORM 是指一个可以简化数据库操作的库。
由于 MongoDB 是以 JSON 格式的文档存储数据,所以可以将任何类型的数据存入集合中,但 mongoose 可以为此添加类型约束,这是它的重要优点。
让我们立即安装它。
$ npm install --save-dev mongoose
$ npm install --save-dev mongoose
这样就安装了所需的软件包。
试着创建一个 REST API现在我们来使用刚刚安装的软件开始创建 REST API。
创建一个名为src的目录,并在其中放置程序。
顺便说一下,当前的目录结构看起来是这样的。
node_modules/
package.json
docker-compose.yml
package-lock.json
src/
如果您想要迅速运行成品系统,请立即查看第4项。代码很简单,所以即使没有解释,您也应该大致明白。
如果您想要按顺序进行,请从第1项开始查看。
试着用 Express 创建 API
首先,我们将使用 Express 来创建一个简单的 API。我们创建一个名为 src/app.ts 的文件,并在其中编写以下内容。在注释中有简单的说明。
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// 「GET /」のルーティングと処理を記述
app.get("/", (req, res) => {
res.send("Hello World!!");
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
(async function main() {
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// 「GET /」のルーティングと処理を記述
app.get("/", (req, res) => {
res.send("Hello World!!");
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
(async function main() {
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
我会试着执行一下。我正在这里悄悄地使用之前安装的实用工具 ts-node。
通常 TypeScript 需要经过“编译”>“执行”这两个步骤,但它可以为我们省去这一步骤。
$ npx ts-node --esm src/app.ts
server running
显示为”服务器正在运行”。
尝试访问http://localhost:3000/。

尝试使用Mongoose连接到MongoDB数据库。
现在,我们接下来用 Mongoose 来连接 MongoDB,让我们试着写一下代码。
请将 src/app.ts 修改为以下内容。稍后会解释代码的要点。
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// (1) Mongoose のスキーマを作成
const userSchema = new mongoose.Schema(
{
name: {
type: String, // 型を指定する
required: true, // 必須カラムかどうか
},
},
);
const usersResource = mongoose.model("users", userSchema);
// 「GET /users」のルーティングと処理を記述
app.get("/users", async (req, res) => {
// (2) users から全件取得する
const users = await usersResource.find();
res.status(200).json(users);
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
// (3) Mongoose のコネクションストリング
const MONGOOSE_URI = "mongodb://root:password@localhost:27017/users";
(async function main() {
// MongoDB への接続
await mongoose.connect(MONGOOSE_URI);
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// (1) Mongoose のスキーマを作成
const userSchema = new mongoose.Schema(
{
name: {
type: String, // 型を指定する
required: true, // 必須カラムかどうか
},
},
);
const usersResource = mongoose.model("users", userSchema);
// 「GET /users」のルーティングと処理を記述
app.get("/users", async (req, res) => {
// (2) users から全件取得する
const users = await usersResource.find();
res.status(200).json(users);
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
// (3) Mongoose のコネクションストリング
const MONGOOSE_URI = "mongodb://root:password@localhost:27017/users";
(async function main() {
// MongoDB への接続
await mongoose.connect(MONGOOSE_URI);
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
以下是代码的要点。请参照代码中的相应编号一起查看。
-
- (1) MongoDB の users コレクションを扱うスキーマを作っています。ここでカラムの型や「必須かどうか」などを定義できます。
-
- (2) MongoDB の users コレクションから検索する部分です。 find() で引数を何も指定しないと全件取得になります。
- (3) MongoDB に接続するためには”コネクションストリング”という文字列を指定します。少しクセがありますのでここではおまじないと思ってください。
出现后,再次执行 npx ts-node –esm src/app.ts,并在浏览器中尝试打开 http://localhost:3000/users。
返回的结果如下所示吗?
从 API 返回了在 MongoDB 中存储的值!
[{"_id":"638c0fb94ceb083d25f210f6","name":"nyanchu"}]
尝试编写一个POST请求处理。
下面我将写一个接受 JSON 请求并将其写入 MongoDB 的代码段。请将 src/app.ts 更改为以下内容。稍后我会解释代码的含义。
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// Mongoose のスキーマを作成
const userSchema = new mongoose.Schema(
{
name: {
type: String, // 型を指定する
required: true, // 必須カラムかどうか
},
},
);
const usersResource = mongoose.model("users", userSchema);
// (1) リクエストボディを JSON で取得するための設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 「GET /users」のルーティングと処理を記述
app.get("/users", async (req, res) => {
// users から全件取得する
const users = await usersResource.find();
res.status(200).json(users);
});
// 「POST /users」のルーティングと処理を記述
app.post("/users", async (req, res) => {
const body = req.body;
try {
// (2) users に書き込む
await usersResource.create(body);
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
// Mongoose のコネクションストリング
const MONGOOSE_URI = "mongodb://root:password@localhost:27017/users?authSource=admin";
(async function main() {
// MongoDB への接続
await mongoose.connect(MONGOOSE_URI);
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// Mongoose のスキーマを作成
const userSchema = new mongoose.Schema(
{
name: {
type: String, // 型を指定する
required: true, // 必須カラムかどうか
},
},
);
const usersResource = mongoose.model("users", userSchema);
// (1) リクエストボディを JSON で取得するための設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 「GET /users」のルーティングと処理を記述
app.get("/users", async (req, res) => {
// users から全件取得する
const users = await usersResource.find();
res.status(200).json(users);
});
// 「POST /users」のルーティングと処理を記述
app.post("/users", async (req, res) => {
const body = req.body;
try {
// (2) users に書き込む
await usersResource.create(body);
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
// Mongoose のコネクションストリング
const MONGOOSE_URI = "mongodb://root:password@localhost:27017/users?authSource=admin";
(async function main() {
// MongoDB への接続
await mongoose.connect(MONGOOSE_URI);
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
这次代码的重点如下。
-
- (1) POST でリクエストボディを JSON で取得するための設定を前段に入れます。
- (2) MongoDB の users コレクションに値を入れる処理です。先ほど程度したスキーマ以外のものを入れようとするとエラーになります。
我将在实际中进行尝试。请不要忘记使用 npx ts-node –esm src/app.ts 进行重新执行。
由于我想使用 POST 请求,所以我将尝试使用 curl 命令执行。
$ curl -X POST -H "Content-Type: application/json" -d '{"name":"sample"}' -i http://localhost:3000/users
HTTP/1.1 200 OK
X-Powered-By: Express
Date: Sun, 04 Dec 2022 06:52:20 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 0
返回了200。看起来很成功。
那么我们来敲击GET /users,查看其内容吧。
> curl http://localhost:3000/users
[{"_id":"638c0fb94ceb083d25f210f6","name":"nyanchu"},{"_id":"638c43a4220a7532fbefdc0b","name":"sample","__v":0}]%
总共返回了两个值。成功了!
让我们试试请求错误的值。
因为现在代码将请求的值直接传递给Mongoose,所以我们可以验证Mongoose是否会拦截它。
我们将尝试请求 {“title”: “hoge”} 而不是 {“name”: “hoge”}。
> curl -X POST -H "Content-Type: application/json" -d '{"title":"hoge"}' -i http://localhost:3000/users
HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 69
ETag: W/"45-8a3w5Br0p00nfvyzxIpNSyLPEXM"
Date: Sun, 04 Dec 2022 09:27:38 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"message":"users validation failed: name: Path `name` is required."}%
我被拒绝了。
用户验证失败:名称:路径名称是必需的。(必须提供name参数) 我感到很生气。
我感受到了Mongoose的伟大。
完成CRUD操作
那么在 GET、POST 之后,我们要继续添加 PUT 和 DELETE 来完成 CRUD 操作。请将 src/app.ts 修改如下所示。关于这部分的重点将在稍后进行解释。
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// Mongoose のスキーマを作成
const userSchema = new mongoose.Schema(
{
name: {
type: String, // 型を指定する
required: true, // 必須カラムかどうか
},
},
);
const usersResource = mongoose.model("users", userSchema);
// リクエストボディを JSON で取得するための設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 「GET /users」のルーティングと処理を記述
app.get("/users", async (req, res) => {
// users から全件取得する
const users = await usersResource.find();
res.status(200).json(users);
});
// 「POST /users」のルーティングと処理を記述
app.post("/users", async (req, res) => {
const body = req.body;
try {
// users に書き込む
await usersResource.create(body);
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// (1) 「PUT /users/{:id}」のルーティングと処理を記述
app.put("/users/:id", async (req, res) => {
const id = req.params.id
const body = req.body;
try {
// (2) 指定した id に対し更新をかける。 id が見つからなかったら失敗扱いにする。
await usersResource.findByIdAndUpdate({ _id: id }, body).orFail();
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// 「DELETE /users/{:id}」のルーティングと処理を記述
app.delete("/users/:id", async (req, res) => {
const id = req.params.id
try {
// (3) 指定した id を削除する。 id が見つからなかったら失敗扱いにする。
await usersResource.findByIdAndDelete({ _id: id }).orFail();
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
// Mongoose のコネクションストリング
const MONGOOSE_URI = "mongodb://root:password@localhost:27017/users?authSource=admin";
(async function main() {
// MongoDB への接続
await mongoose.connect(MONGOOSE_URI);
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
import express, { Application } from "express";
import mongoose from "mongoose";
// Express のアプリケーションを作成
const app: Application = express();
// Mongoose のスキーマを作成
const userSchema = new mongoose.Schema(
{
name: {
type: String, // 型を指定する
required: true, // 必須カラムかどうか
},
},
);
const usersResource = mongoose.model("users", userSchema);
// リクエストボディを JSON で取得するための設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 「GET /users」のルーティングと処理を記述
app.get("/users", async (req, res) => {
// users から全件取得する
const users = await usersResource.find();
res.status(200).json(users);
});
// 「POST /users」のルーティングと処理を記述
app.post("/users", async (req, res) => {
const body = req.body;
try {
// users に書き込む
await usersResource.create(body);
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// (1) 「PUT /users/{:id}」のルーティングと処理を記述
app.put("/users/:id", async (req, res) => {
const id = req.params.id
const body = req.body;
try {
// (2) 指定した id に対し更新をかける。 id が見つからなかったら失敗扱いにする。
await usersResource.findByIdAndUpdate({ _id: id }, body).orFail();
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// 「DELETE /users/{:id}」のルーティングと処理を記述
app.delete("/users/:id", async (req, res) => {
const id = req.params.id
try {
// (3) 指定した id を削除する。 id が見つからなかったら失敗扱いにする。
await usersResource.findByIdAndDelete({ _id: id }).orFail();
res.status(200).send();
} catch (error: any) {
res.status(500).json({ message: error.message })
}
});
// Express を立ち上げるポート番号
const EXPRESS_PORT = 3000;
// Mongoose のコネクションストリング
const MONGOOSE_URI = "mongodb://root:password@localhost:27017/users?authSource=admin";
(async function main() {
// MongoDB への接続
await mongoose.connect(MONGOOSE_URI);
try {
// 指定したポートでリッスンするサーバを立ち上げる
app.listen(EXPRESS_PORT, () => {
console.log("server running");
});
} catch (e: any) {
console.error(e.message);
}
})();
以下是此次代码的要点。
-
- (1) パスパラメータで :id を受け付けるには /users/:id とルーティングを記述します。その後 req.params.id で取得できます。
-
- (2) findByIdAndUpdate() で指定した id に対し更新をかけます。もし id 自体が見つからなかったときはその後 .orFail() をつけておくことで処理を失敗扱いにします。(そのままだとエラーにならないため)
- (3) findByIdAndDelete() で指定した id を削除します。上記と同じく id が見つからなかった場合はその後の .orFail() で処理を失敗扱いにします。
既然已经出来了,让我们来试试看吧。请记得使用 npx ts-node –esm src/app.ts 重新执行。
我们将使用 curl 发送请求。请将指定的 id 部分更改为合适的值。
# PUT リクエスト
$ curl -X PUT -H "Content-Type: application/json" -d '{"name":"huga"}' -i http://localhost:3000/users/638c43a4220a7532fbefdc0b
# 試しに GET して確認。書き換わっている!
$ curl http://localhost:3000/users
[{"_id":"638c0fb94ceb083d25f210f6","name":"huga"},{"_id":"638c43a4220a7532fbefdc0b","name":"sample","__v":0}]%
# DELETE リクエスト
$ curl -X DELETE -i http://localhost:3000/users/638c0fb94ceb083d25f210f6
# 試しに GET して確認。消えている!
$ curl http://localhost:3000/users
[{"_id":"638c43a4220a7532fbefdc0b","name":"sample","__v":0}]%
这样CRUD就完成了!
总结
你觉得怎么样呢?能够快速完成吗?我发现使用 Express 和 Mongoose 可以轻松编写应用程序。
我当前为了简单易懂而将所有内容写在一个文件中,但如果想要扩大代码规模,我认为应该按照以下步骤进行重构。
-
- Mongoose のスキーマを別ファイルに切り出す
-
- API のコントローラー部分を別ファイルに切り出す
-
- API のルーティング部分を別ファイルに切り出す
- Express のリッスンする部分や Mongoose のコネクション部分を分離する(テストしやすくするため)
如果要继续写测试的话
-
- jest を入れてビジネスロジックのテストを書く
-
- jest-mongodb を入れて Mongoose 周りのテストを書く
- supertest を入れて Express のコントローラーのテストを書く
让我们思考这个问题。如果有余力的话,我也会考虑把那方面的内容写入文章中。
谢谢您的陪伴和支持。