🐈

express × Mongo DBでCRUDするweb APIを作る(その2 webAPIの作成と実行編)

2022/04/17に公開

はじめに

本稿では自作webAPIの作成から実行、活用するところまでを3編に分けて、紹介します。

その1概要(MongoDBでCRUD)

MongoDBをインストールする〜MongoDBでCRUDするところまでを説明します。

その2概要(webAPIの作成と実行) ☆今回

expressとMongoDBでwebAPIの作成〜Postmanで自作webAPIを実行するところまでを紹介します。

その3概要(webAPIの活用)

実際にNuxt.jsを使用して画面から自作APIを呼び出して、Mongo DBに反映させる&画面に表示をするところまでを紹介します。

対象

expressを使ってwebアプリケーションの基礎であるCRUDできるAPIを作ってみたいという方
APIとはどんな流れで使用されているのか知りたい方

前回のお話

前回の記事では、Mongo DBのインストールからコレクション/ドキュメントなどの各CRUDの操作を紹介しました。
https://zenn.dev/polycome_toto/articles/2ac6899100786f

今回は、前回のお話を踏まえて、expressとMongoDBでwebAPIを作成し、Postmanで自作webAPIを実行するところまでを紹介していきます。

今回の記事での事前準備

node.js
homebrew
MongoDB

※mac環境を想定しています。
以下、私の環境

// node.jsのバージョン
$ node -v
v14.17.6

// homebrewのバージョン
$ brew -v
Homebrew 3.3.4
Homebrew/homebrew-core (git revision 4567f25b438; last commit 2021-11-20)
Homebrew/homebrew-cask (git revision fbb42995b4; last commit 2021-11-20)

// Mongo DBのバージョン確認(私の場合は以下のバージョン)
$ mongod --version
db version v5.0.3

ディレクトリの作成

以下は、CUIで作成していますが、GUIで作成されても問題ないです。

console
$mkdir express_crud
$cd express_crud

npm init と 必要なライブラリのインストール

今回、expressとMongoDBでwebAPIを作成するにあたって、以下3つのライブラリをインストールします。

  • express
    Node.js上で開発をスピードアップするWebアプリケーション向けのフレームワークです。
    Node.jsを使ったWebアプリ開発に王道として長く利用されています。
    また、express自体には必要最低限の機能だけを提供しており、必要にあるミドルウェアを追加しながら使っていきます。

  • nodemon
    本来、app.jsファイルを更新後、手動でnodeコマンドを再実行する必要があるのですが、nodemonライブラリを入れることで、コードの変更時にプロセスが自動で再起動します。なので、コードを更新してもnodeコマンドを再実行する必要はありません。

  • mongoose
    MongooseはNode.jsからMongoDBにアクセスするライブラリで、Node.js→Mongoose→MongoDBという流れのように、Node.jsからMongoDBの操作を簡単にしてくれます。

上記3つのライブラリ/フレームワークをインストールするにあたり、まずはpackage.jsonを作成します。(npm init

terminal
// init後はエンター連打で大丈夫です。
$npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (express_crud) [ここでエンター]
version: (1.0.0)  [ここでエンター]
description: [ここでエンター]
entry point: (index.js)  [ここでエンター]
test command:  [ここでエンター]
git repository: [ここでエンター]
keywords:  [ここでエンター]
author:  [ここでエンター]
license: (ISC)  [ここでエンター]
About to write to /Users/[ユーザー名が表示されている]/Desktop/express_crud/package.json:

{
  "name": "express_crud",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)  [ここでエンター]

// express install
$npm i express
....[割愛します。]


// nodeman install(ホットリロード的なやつ)
$npm i -D nodemon
....[割愛します。]

// mongoose install(ODM。ORMのmongo版的なやつ)
$npm i mongoose 
....[割愛します。]

以上が今回必要なライブラリです。
ライブラリをインストールしてpackage.jsonファイルを見てみると以下のようになっていれば大丈夫です。

package.json
{
  "name": "express_crud",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon ./api/index.js" // この行を追加します。
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.2",
    "mongoose": "^6.1.3"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

ディレクトリやファイルの作成

次に、ディレクトリやファイルを以下のように作成します。(よりwebアプリケーションを作成する実際の構成に近づけるためにこの構成にしております。)

appの設定

express_crud/api/index.js
// express_crud/api/index.jsファイルにて

// expressを読み込む(使えるようにする)
const express = require('express')

//expressの関数をappで実行できるようにする(通常、appで実行できるようにすることが多い)
const app = express()

// 先ほど作成したroutes/index.jsファイルを読み込む(使えるようにする)
// この時点ではroutes/index.jsには何も書いていないです。
const router = require('./routes')

// ポートの指定(環境変数(process.env.PORT)か3000番)
const port = process.env.PORT || 3000

// mongooseを読み込む(使えるようにする)
const mongoose = require('mongoose')

// ローカルで実行しているcompanyデータベースに接続(デフォルトのポートが27017)
mongoose.connect('mongodb://localhost:27017/company')

// bodyParserは非推奨になったので、この形で書く
// 入力されたデータを文字列または配列として認識する
// Content-Typeがapplication/x-www-form-urlencodedのデータを取得するためにはexpress.urlencoded()を追加する必要がある
// extendedオプションはデフォルトでfalseに設定されており、配列型のフォームデータを受け取る際にはtrueに変更する必要がある
app.use(express.urlencoded({ extended: true }))

// 受け取ったデータをJSONオブジェクトとして認識する
// Content-Typeがapplication/jsonのデータを取得するためにはexpress.json()を追加する必要がある
app.use(express.json())
app.use('/api', router)

// 先ほど指定したport番号でlistenする。
app.listen(port)

//ログを出力
console.info('listen on port: ' + port)

一旦、この時点でnode server.jsが実行されプロジェクトが実行されるかを確認しておきましょう。
ただし、上述のまま実行してしまうと、エラーが起きてしまうので、以下のように3行ほどコメントアウトしておきます。

express_crud/api/index.js
const express = require('express')
const app = express()

// const router = require('./routes')

const port = process.env.PORT || 3000

const mongoose = require('mongoose')

mongoose.connect('mongodb://localhost:27017/company')

app.use(express.urlencoded({ extended: true }))

// app.use(express.json())
// app.use('/api', router)

app.listen(port)
console.info('listen on port: ' + port)

ここまでできたら、consoleに戻り、以下の操作を行い、最終行にlisten on port: 3000と表示されればOKです。

terminal
//package.jsonの"scripts"に"start"を変更したため、再度package.json の内容をインストールする
$ npm i
...[割愛]
$ npm run start
> express_crud@1.0.0 start /Users/[私のユーザー名]/Desktop/express_crud
> nodemon index.js

[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
listen on port: 3000

見ていただければわかりますが、npm run startをすると、package.jsonに追加した"start": "nodemon index.js"が実行され、先ほど作成したexpress_crud/api/index.jsが走り、console.info('listen on port: ' + port)がterminalに表示された、という流れになります。

ここまでできれば、以下のコメントアウトは外しておきましょう。

express_crud/api/index.js
// const router = require('./routes')
// app.use(express.json())
// app.use('/api', router)

おそらく、コメントアウトを外すとターミナルにエラーが出てしまいます。出ていてもおかしくないので一旦 control + Cで実行キャンセルしておきましょう。

ルートの設定

express_crud/api/routes/index.js
// express_crud/api/routes/index.jsファイルにて

// expressを読み込む(使えるようにする)
const express = require('express')

// expressのRouter関数をrouterという名前で定義する
const router = express.Router()

// express_crud/api/routes/user.jsファイルを読み込む(使えるようにする)
// この時点ではこのファイルの中身は空です。(次で作成します)
const users = require('./user')

// routerに/userの中身を/usersというURLがリクエストされた時に呼び出すものとして定義する
router.use("/users", users)

// routerをexportする
module.exports = router

express_crud/api/routes/index.jsファイルで、express_crud/api/routes/user.jsファイルを呼び出していますが、中身が空のため以下のように記載します。
ここでは、リンクとリクエストの組み合わせでどんな処理を実行するのかを設定します。(厳密にはcontrollerファイルで定義している関数を呼ぶ)

express_crud/api/routes/user.js
// express_crud/api/routes/user.jsファイルにて

///controllers/userController.jsを読み込む(使えるようにする)
const userController = require('../controllers/userController')

// expressを読み込む(使えるようにする)
const express = require('express')

// expressのRouter関数をrouterという名前で定義する
const router = express.Router()

// localhost:3000/api/users/ でgetリクエストが飛んできた時に実行されるコード
// ここでは、userControllerファイルに記載されているfind_users関数を実行して、結果を返す処理を実施
router.get('/', async (req, res) => {
  return await userController.find_users(req, res)
})

// localhost:3000/api/users/ でpostリクエストが飛んできた時に実行されるコード
router.post('/', async (req, res) => {
  return await userController.create_user(req, res)
})

// localhost:3000/api/users/id番号/ でgetリクエストが飛んできた時に実行されるコード
router.get('/:id', async (req, res) => {
  return await userController.find_user(req, res)
})

// localhost:3000/api/users/id番号/ でputリクエストが飛んできた時に実行されるコード
router.put('/:id', async (req, res) => {
  return await userController.update_user(req, res)
})

// localhost:3000/api/users/id番号/ でdeleteリクエストが飛んできた時に実行されるコード
router.delete('/:id', async (req, res) => {
  return await userController.delete_user(req, res)
})

module.exports = router

コントローラーの設定

ルートの設定で、const userController = require('../controllers/userController')と記載しましたが、ここで使用されるものです。
ルートの設定では、呼び出す処理(関数)しか記載していないので、こちらでどんな処理をするのかを定義します。

express_crud/api/controllers/userController.js
// express_crud/api/controllers/userController.jsファイルにて

// express_crud/api/models/user.jsファイルを読み込む
// この時点ではこのファイルには何も記述はありません。
const User = require('../models/user')

module.exports = {
  // 全てのユーザーを取得する。(json形式でUser.find({})の結果を返す)
  find_users : async (req, res) => {
    const users = await User.find({}).catch(err => {
      res.send(err)
      console.error(err)
    })
    return res.json(users)
  },

  // 新しいユーザーを作成する。
  create_user : async (req, res) => {
    const user = new User()
    user.name = req.body.name
    user.age = req.body.age
    user.permanent_staff = req.body.permanent_staff

    await user.save().catch(err => {
      res.send(err)
      console.error(err)
    })
    return res.json(user)
  },


  // 特定のユーザーを取得する。
  find_user : async (req, res) => {
    const user = await User.findById(req.params.id).catch(err => {
      res.send(err)
      console.error(err)
    })
    return res.json(user)
  },

  // 特定のユーザーを更新する。
  update_user : async (req, res) => {
    const user = await User.findById(req.params.id).catch(err => {
      res.send(err)
      console.error(err)
    })
    user.name = req.body.name
    user.age = req.body.age
    user.permanent_staff = req.body.permanent_staff
    await user.save()
    return res.json(user)
  },

  // 特定のユーザーを削除する。
  delete_user : async (req, res) => {
    await User.deleteOne({
      _id: req.params.id
    }).catch(err => {
      res.send(err)
      console.error(err)
    })
    return res.json({ message: "Successfully deleted"})
  }
}

DB関連の設定

こちらでは、スキーマを作成致します。
スキーマとはどのようなデータをDBに格納するのかを定義するものです。
コントローラーの設定で、const User = require('../models/user')と記載しましたが、ここで使用されるものです。

今回はUserSchemaというスキーマではnameagepermanent_staffを指定しておきます。

express_crud/api/models/user.js
// express_crud/api/models/user.jsファイルにて

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const UserSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  age: {
    type: Number,
    default: 0
  },
  permanent_staff: {
    type: Boolean,
    default: true
  }
})

// スキーマからUserモデルを生成する
module.exports = mongoose.model('User', UserSchema)

ここまできたら、一度、terminalに戻って、npm run startしてみましょう。

terminal
$ npm run start

> express_crud@1.0.0 start /Users/[私のユーザー名]/Desktop/express_crud
> nodemon index.js

[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
listen on port: 3000

このように、listen on port: 3000が出ればOKです。
以上が、expressとMongoDBを使ったwebAPIの作成です。
では、次にpostmanを使って、今作ったwebAPIを実行してみましょう。
先ほど、npm run startでローカルのnodeサーバーが動いているので、この状態のまま今ローカルで動いているMonogo DBにアクセスして、ユーザーの作成や読み込み、更新、削除をしていきます。

APIの実行

今回、自作したwebAPIを実行するのに用いるのは、PostmanというWeb APIのクライアントアプリケーションです。簡単に紹介すると、PostmanとはURLやリクエスト、トークンなどを設定してAPIにRequestを送り、Responseを確認することができます。
https://www.postman.com/

具体的な説明は省きますが、簡単に以下の設定を行なって、リクエストをしてみましょう。
まだ一件のデータもDBには入っていないため、先にデータを追加する必要があります。
routes/index.jsにて、飛んできたリクエストのURLに/usersが存在すれば、./user.jsファイルに記載された関数を発火させるロジックになっているため、今回のリクエストのURLには毎回、/api/users/〇〇入ることになります。リクエストのタイプ(GET/POST/PUT/DELETE)と〇〇にあたる部分が合致したものがuserController.jsで定義されている関数が呼ばれる形となります。

データの新規追加(POSTリクエスト)

① Collectionの作成

以下のように、Collectionタブから左上の十字マークを押して、Collectionを新規作成し、名前を変更します。

② Requestの追加

画像のようにRequestを作成します。

③ 作成したRequestの設定

作成したRequestの名前やリクエストタイプ(今回はPOST)、Headers、Bodyを以下のように設定します。

Bodyでは、json形式で記述します。

④ Requestの実行

最後に、Sendボタンを押して、画面下部のBodyにデータが表示されていることを確認します。

データの確認(GETリクエスト)

① Requestの作成 ~ 設定

データの新規追加と同じように、Requestの新規作成から設定を行います。

② Requestの実行

最後に、Sendボタンを押して、画面下部のBodyに先ほど追加したデータが表示されていることを確認します。

データの更新(PUTリクエスト)

① Requestの作成 ~ 設定

これまでと同じように、Requestの新規作成から設定を行います。

② Requestの実行

最後に、Sendボタンを押して、画面下部のBodyに先ほど追加したデータが更新されていることを確認します。

データの削除(DELETEリクエスト)

① Requestの作成 ~ 設定

これまでと同じように、Requestの新規作成から設定を行います。

② Requestの実行

最後に、Sendボタンを押して、画面下部のBodyに、{ message: "Successfully deleted"}と表示されていることを確認します。

次に、データの確認(GETリクエスト)のリクエストを実行して、データが存在していないか確認してみましょう。

最後に

この記事ではexpressとMongoDBでwebAPIの作成〜Postmanで自作webAPIを実行するところまでを説明しました。
次回は、実際にNuxt.jsのプロジェクトを立ち上げて、画面から自作APIを呼び出して、Mongo DBに反映させる&画面に表示をするところまでを紹介します。

もし、どうしてもうまくいかないなどありましたら、今回、こちらで使用したコードを公開するので参考になさってください。

Discussion