RESTfulなAPIを作成する!(with Node.js Express & MongoDB & TypeScript)

10 min read読了の目安(約9600字

はじめに

本記事では、Node.js ウェッブアプリケーションサーバーフレームワークであるExpressと、NoSQLのMongoDBで簡単なAPIを作成する手順をまとめました。


必要なパッケージを導入

yarn add express mongoose
yarn add -D @types/express @types/mongoose @types/node @types/nodemon nodemon ts-node typescript

yarn run tsc --initで tsconfig.json ファイルを作成します。


nodemon,ts-node導入

nodemonについての概要についてはこちらの記事で勉強させていただきました!

https://qiita.com/mitsuhiro_K/items/429ca479b4e191bfea4d

ファイル監視させるためpackage.jsonに以下を追記します。

"scripts": {
    "start": "nodemon --exec ts-node src/index.ts"
 }

ts-nodeはソースコードを修正するたびに tsc を実行し、さらにそのあと node コマンドを実行するのは手間なので導入しました。


コードを書く

以下、 es6 のモジュール管理方式を採用します。

express

まずはexpressについて軽く触れるため、ポート番号 3000 に起動させます。

import express from "express";

const app = express();

app.get('/',(req:Request,res:Response) => {
  res.send('Hello from node');
});

const PORT = process.env.PORT || 3000;// port番号を指定

//サーバー起動
app.listen(PORT, () => console.log(`Server run at port ${PORT}`));

ターミナル上でyarn startとタイプすると

Server run at port 3000

と表示されれば ok です。

サーバーを起動後Postmanでurlにローカルホスト3000番を指定し、SENDボタンを押すと意図した結果が返されます。
postman


MongoDB

MongoDBについては以下の記事が画像付きでわかりやすいです。

https://qiita.com/n0bisuke/items/4d4a4599ee7ce9cf4fd9

コードに戻り、MongoDBとの接続を図ります。

import express from "express";
+ import mongoose from "mongoose";


const app = express();

app.get('/',(req:Request,res:Response) => {
  res.send('Hello from node');
});

+ mongoose
+  .connect(
+    'mongodb://username:<password>@cluster0-shard-00-00.uppu6.mongodb.net:27017,cluster0-shard-00-01.uppu6.mongodb.net:27017,cluster0-shard-00-02.uppu6.mongodb.net:27017/myFirstDatabase?ssl=true&replicaSet=atlas-k12pxg-shard-0&authSource=admin&retryWrites=true&w=majority',
+    {
+      useNewUrlParser: true, //ユーザーが新しいパーサーにバグを見つけたとき古いパーサーに逆戻りする機能
+      useUnifiedTopology: true,//新しいトポロジエンジンに関連しなくなったいくつかの接続オプションのサポートが削除される機能
+      useFindAndModify: false,
+    }
+  )
+  .then(() => console.log("mongodb connected!"))
+  .catch((error) => console.log(error));

const PORT = process.env.PORT || 3000;// port番号を指定

//サーバー起動
app.listen(PORT, () => console.log(`Server run at port ${PORT}`));

MongoDBの url ですが、ご自身のユーザー名やパスワードを入力する必要があります。

保存すると、nodemonが働きコンソール上に "mongodb connected!" と表示されるはずです。

ユーザー名やパスワードをリポジトリにコミットさせないためにenvファイルを導入します。


envファイル

まず必要なパッケージをインストールしましょう。

yarn add dotenv

env ファイルはトップディレクトリに作成します。

├── src/
    └──index.tsx
└──.env
└──package.json
└──tsconfig.json

.env内のコードは以下のとおりです。

.env
USERNAME=・・・・・・・・・・・ //MongoDBに登録したユーザー名を指定
PASSWORD=・・・・・・・・・・・ //接続セキュリティ情報を作成時のパスワードをペースト

index.tsファイルは以下のように変更を加えます。

import express from "express";
+ import dotenv from "dotenv";

const app = express();
+ dotenv.config();


app.get('/',(req:Request,res:Response) => {
  res.send('Hello from node');
});

mongoose
  .connect(
-    'mongodb://~',//MongoDBのurl
+    `mongodb://${process.env.USERNAME}:${process.env.PASSWORD}@cluster0-shard-00-00.96zsr.mongodb.net:27017,cluster0-shard-00-01.96zsr.mongodb.net:27017,cluster0-shard-00-02.96zsr.mongodb.net:27017/myFirstDatabase?ssl=true&replicaSet=atlas-118son-shard-0&authSource=admin&retryWrites=true&w=majority`,
    {
      useNewUrlParser: true, 
      useUnifiedTopology: true,
      useFindAndModify: false,
    }
  )
  .then(() => console.log("mongodb connected!"))
  .catch((error) => console.log(error));

const PORT = process.env.PORT || 3000;// port番号を指定

//サーバー起動
app.listen(PORT, () => console.log(`Server run at port ${PORT}`));

Model 作成

モデルを作成するために、MongoDBのスキーマを利用します。スキーマの役割は、 JSON 形式で変数名と値のペアを定義することです。
ファイル構成を確認しておきましょう。

├── src/
   ├── models/    ← New
           └──User.ts  
   └──index.tsx
└──.env
└──package.json
└──tsconfig.json

それではコードを書いていきます。

Users.ts
import mongoose, { Schema } from "mongoose";

const UserSchema: Schema = new mongoose.Schema({
  username: {
    type: String,
    require: true,
  },
  email: {
    type: String,
    require: true,
  },
  date: {
    type: Date,
    default: Date.now,
  },
});

export default mongoose.model("Users", UserSchema);

RESTfulなAPIを作成

それでは本題の REST API を作成していきます。
ここではexpressの Router メソッドを利用します。

ファイル構成は以下のとおりです。

├── src/
   ├── models/    
           └──User.ts
   ├── routes/    ← New
           └──api
	       └──users.ts
   └──index.tsx
└──.env
└──package.json
└──tsconfig.json

ルーティングの設定は以下のとおりです。

users.ts
import express, { Request, Response } from "express";
import Posts from "../../models/Users";

const router = express.Router();

// データを生成(Create)
router.post("/", async (req: Request, res: Response) => {
  const newPost = new Posts(req.body); 

  try {
    const post = await newPost.save(); //変数名と値のペアを取得
    if (!post) throw Error("error");
    
    res.status(200).json(post); //JSON形式で変数名と値のペア
  } catch (error) {
    res.status(400).json({ msg: error });
  }
});

またindex.tsに変更を加えます。

index.ts
import express from "express";
import dotenv from "dotenv";
+ import postsRouter from "./routes/api/users";


const app = express();
dotenv.config();

+ app.use(express.json());

app.get('/',(req:Request,res:Response) => {
  res.send('Hello from node');
});

mongoose
  .connect(
    `mongodb://${process.env.USERNAME}:${process.env.PASSWORD}@cluster0-shard-00-00.96zsr.mongodb.net:27017,cluster0-shard-00-01.96zsr.mongodb.net:27017,cluster0-shard-00-02.96zsr.mongodb.net:27017/myFirstDatabase?ssl=true&replicaSet=atlas-118son-shard-0&authSource=admin&retryWrites=true&w=majority`,
    {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useFindAndModify: false,
    }
  )
  .then(() => console.log("mongodb connected!"))
  .catch((error) => console.log(error));
  
+//User routes
+app.use("/api/users", postsRouter); //http://localhost:3000/api/usersにリクエスト

const PORT = process.env.PORT || 3000;// port番号を指定

//サーバー起動
app.listen(PORT, () => console.log(`Server run at port ${PORT}`));

次にpostmanでリクエストを送ります。

yarn startでnodemanを起動しておく必要があります。

画像のように、スキーマ (modules/Users.ts) で定義した変数名と値のペアが JSON 形式で取得できています。
また、MongoDB(Atlas→Collections)を確認すると、データが取得できていることがわかります。

ただし、スキーマで取得した post の型がany型です。

これを型付けしていきます。


スキーマの型を定義

型はインターフェ-スで定義します。

Users.ts
- import mongoose, { Schema } from "mongoose";
+ import mongoose, { Schema, Document } from "mongoose";
+ export interface IUser extends Document {
+   username: string;
+   email: string;
+   date: Date;
+ }

const UserSchema: Schema = new mongoose.Schema({
  username: {
    type: String,
    require: true,
  },
  email: {
    type: String,
    require: true,
  },
  date: {
    type: Date,
    default: Date.now,
  },
});
​
- export default mongoose.model("Users", UserSchema);
+ export default mongoose.model<IUser>("Users", UserSchema);//ジェネリックで型を指定

以上の変更により、スキーマで取得した post の型付けができました。
routes/api/users.ts


CRUD 実装

最後に CRUD 処理を実装してこの記事を締めようと思います。

users.ts
import express, { Request, Response } from "express";
import Posts from "../../models/Users";

const router = express.Router();

+// データの読み込み(Read)
+router.get("/", async (req: Request, res: Response) => {
+  try {
+    const posts = await Posts.find();
+    if (!posts) throw Error("error");
+
+    res.status(200).json(posts);
+  } catch (error) {
+    res.status(400).json({ msg: error });
+  }
+});

+// 個々のデータを読み込み(Read)
+router.get("/:id", async (req: Request, res: Response) => {
+  try {
+    const posts = await Posts.findById(req.params.id);
+    if (!posts) throw Error("error");
+
+    res.status(200).json(posts);
+  } catch (error) {
+    res.status(400).json({ msg: error });
+  }
+});

// データを生成(Create)
router.post("/", async (req: Request, res: Response) => {
  const newPost = new Posts(req.body);

  try {
    const post = await newPost.save();
    if (!post) throw Error("error");

    res.status(200).json(post);
  } catch (error) {
    res.status(400).json({ msg: error });
  }
});

+//データを更新(Update)
+router.patch("/:id", async (req: Request, res: Response) => {
+  try {
+    const post = await Posts.findByIdAndUpdate(req.params.id, req.body);
+    if (!post) throw Error("error");
+
+    res.status(200).json({ success: true });
+  } catch (error) {
+    res.status(400).json({ msg: error });
+  }
+});

+//データを削除(Delete)
+router.delete("/:id", async (req: Request, res: Response) => {
+  try {
+    const post = await Posts.findByIdAndDelete(req.params.id);
+    if (!post) throw Error("error");
+
+    res.status(200).json({ success: true });
+  } catch (error) {
+    res.status(400).json({ msg: error });
+  }
+});

export default router;

findById ~というメソッドによって id を url に追加するだけで CRUD 処理を実行できます。
操作はシンプルなので、先程作ったデータを削除するだけにとどめておきます。

Postmanに戻り、メソッドをDELETEに変更します。
postman

送信後、
postman
画像のように、意図した挙動です。

メソッドをGETに変更し、送信すると

空の配列に変更されており、データを削除することができました。

以上になります。ここまで読んでいただきありがとうございました!!