json-serverで複数項目の登録・更新・削除のAPIを定義する
モチベーション
json-server
を使ってモックサーバーを建てるとき、公式のREADMEにもあるようにリソースに対する基本的な操作はデフォルトで用意されている。つまり、特定のリソースの取得や単項目のリソースの登録、そしてパスでリソースを指定した上での更新・削除などは、自分で定義せずとも db.json
をもとに自動でREST APIが用意される。
ただ、中にはデフォルトで用意されないものもある。例えば、配列で定義されたリソースに対して、複数項目を一括で登録・更新・削除するためのREST APIはデフォルトでは存在していない。今回はこれについてやり方をまとめておく。
以下の情報をベースにしている。
server.js の書き方
ベース部分
ありがたいことに、json-serve
のREST APIは、server.js
を作成すれば簡単に拡張することができる。server.js
は以下のように書く。
server.jsconst 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.json
と server.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());