使用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

 

    • Homebrew

未インストールの方は 公式サイト を参照

Docker

未インストールの方は 公式ドキュメント を参照

在阅读本文章时我们将基于以下知识进行进一步操作。

    • JavaScript, TypeScript, Node.js のある程度の知識

 

    Dockerのある程度の知識(なくてもなんとなくでいけます)

本次使用的物品在解释所用物品的同时,我们将以飞快的速度进行安装。

Node.js
的中文释义是 “节点.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

需要在添加到已打开的 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不需要规范化数据或担心数据的一致性,因此可以更容易地进行水平扩展。

这次我们将使用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

这样就安装了所需的软件包。

试着创建一个 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);
    }
})();

我会试着执行一下。我正在这里悄悄地使用之前安装的实用工具 ts-node。
通常 TypeScript 需要经过“编译”>“执行”这两个步骤,但它可以为我们省去这一步骤。

$ npx ts-node --esm src/app.ts
server running

显示为”服务器正在运行”。
尝试访问http://localhost:3000/。

スクリーンショット 2022-12-04 15.02.39.png你好世界!!看到了这个消息!只需在 app.get(“/”) … 中编写路由即可轻松创建 API。

尝试使用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);
    }
})();

以下是代码的要点。请参照代码中的相应编号一起查看。

    • (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);
    }
})();

这次代码的重点如下。

    • (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);
    }
})();

以下是此次代码的要点。

    • (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 のコントローラーのテストを書く

让我们思考这个问题。如果有余力的话,我也会考虑把那方面的内容写入文章中。

谢谢您的陪伴和支持。

bannerAds