json-serverで複数項目の登録・更新・削除のAPIを定義する

投稿日: August 3, 2022

モチベーション

json-server を使ってモックサーバーを建てるとき、公式のREADMEにもあるようにリソースに対する基本的な操作はデフォルトで用意されている。つまり、特定のリソースの取得や単項目のリソースの登録、そしてパスでリソースを指定した上での更新・削除などは、自分で定義せずとも db.json をもとに自動でREST APIが用意される。

ただ、中にはデフォルトで用意されないものもある。例えば、配列で定義されたリソースに対して、複数項目を一括で登録・更新・削除するためのREST APIはデフォルトでは存在していない。今回はこれについてやり方をまとめておく。

以下の情報をベースにしている。

server.js の書き方

ベース部分

ありがたいことに、json-serve のREST APIは、server.js を作成すれば簡単に拡張することができる。server.js は以下のように書く。

server.js
const path = require('path'); const jsonServer = require('json-server'); const server = jsonServer.create(); const router = jsonServer.router(path.join(__dirname, 'db.json')); const middlewares = jsonServer.defaults(); const _ = require('lodash'); server.use(middlewares); server.use(jsonServer.bodyParser); // ここに独自のAPIを定義する server.use(router); server.listen(8080, () => { console.log('JSON Server is running'); });

上記のコードは db.jsonserver.js が同一ディレクトリに置かれることを前提としている。このAPIサーバーは次のコマンドで起動する。

node path/to/server.js

配列のリソースに対する複数件の一括操作

上記の // ここに独自のAPIを定義する の箇所にAPIを実装する。ここではTodoリストのようなものを想定し、todos というリソースが配列であることを想定している。

つまり、db.json が以下のような形の場合である。

db.json
{ "todos": [] }

登録

/todo のエンドポイントに POST を定義する。リクエストボディに入っているTodoの配列を使ってDBを更新する。

server.post('/todos', (req, res) => {
  const db = router.db;
  if (Array.isArray(req.body)) {
    req.body.forEach((element) => {
      insertTodo(db, 'todos', element);
    });
  } else {
    insertTodo(db, 'todos', req.body);
  }
  res.sendStatus(200);

  function insertTodo(db, collection, data) {
    const table = db.get(collection);

    // data.idのtodoが存在しなければ新たに作成する
    if (_.isEmpty(table.find({ id: data.id }).value())) {
      table.push(data).write();
    } else {
      table
        .find({ id: data.id })
        .assign(_.omit(data, ['id']))
        .write();
    }
  }
});

更新

/todo のエンドポイントに PUT を定義する。リクエストボディに入っているTodoの配列を使ってDBを更新する。

server.put('/todos', (req, res) => {
  const db = router.db;
  if (Array.isArray(req.body)) {
    req.body.forEach((element) => {
      updateTodo(db, 'todos', element);
    });
  } else {
    updateTodo(db, 'todos', req.body);
  }
  res.sendStatus(200);

  function updateTodo(db, collection, data) {
    const table = db.get(collection);

    // data.idのtodoが存在する場合のみ、todoの中身を更新する
    if (_.isEmpty(table.find({ id: data.id }).value())) {
      return;
    } else {
      table
        .find({ id: data.id })
        .assign(_.omit(data, ['id']))
        .write();
    }
  }
});

削除

/todo のエンドポイントに DELETE を定義する。以下は削除対象の id をクエリパラメータで渡すことを想定している。

例: /todo?id=foo&id=bar&id=baz

server.delete('/todos', (req, res) => {
  if (!req.query.id) {
    res.sendStatus(400);
    return;
  }
  const db = router.db;
  if (Array.isArray(req.query.id)) {
    req.query.id.forEach((id) => {
      deleteTodo(db, 'todos', id);
    });
  } else {
    deleteTodo(db, 'todos', req.query.id);
  }
  res.sendStatus(200);

  function deleteTodo(db, collection, id) {
    const table = db.get(collection);

    // data.idのtodoが存在する場合のみ、todoを削除する
    if (_.isEmpty(table.find({ id: id }).value())) {
      return;
    } else {
      table.remove({ id: id }).write();
    }
  }
});

補足

json-serverにおけるDB周りの処理は lowdb に依存しているので、その他の操作についてはlowdbのREADMEを確認するのが良い。

また、上記で挙げたREST APIはレスポンスでステータスコードのみ返しているが、もし例えば更新後のTodo全量をレスポンスボディで返したいという場合は、res.sendStatus() を以下のように書きかえれば良い。

res.status(200).jsonp(db.get('todos').value());

share on...