json-server

json-serverのDELETEで全てのアイテムや予期しないアイテムが削除される問題

json-serverは非常に便利なのですが、あるアイテムを削除しようとしたときに、なぜか全てのアイテムが削除されてしまう現象に出くわしました。

TL;DR

デフォルトで設定されている外部キーのサフィックスIdと自動的に実行されるcascading deleteの組み合わせで起きる現象なので、外部キーのサフィックスを変えてやれば(一応は)解決します。

json-server db.json --fks _id

問題の再現

db.json
{
  "customers": [
    {
      "id": 1,
      "customerId": "顧客ID1",
      "name": "顧客1"
    },
    {
      "id": 2,
      "customerId": "顧客ID2",
      "name": "顧客2"
    },
    {
      "id": 3,
      "customerId": "顧客ID3",
      "name": "顧客3"
    }
  ]
}

何の変哲もないDBです。サロゲートキーidを自動生成、ナチュラルキーcustomerIdを手入力することを想定した、よくある設計です。

json-server db.json

普通にjson-serverを立ち上げます。

curl -XDELETE http://localhost:3000/customers/1

普通にid=1のアイテムを削除します。

db.json
{
  "customers": []
}

なんということでしょう、全アイテムが削除されました

原因

自動的に外部キーを設定し、自動的にcascading deleteしてしまう仕様が原因でした。

  1. デフォルトでは、Idが自動的に外部キーのサフィックスとみなされるようになっており、
  2. customerIdcustomerへの外部キーとみなされ、
  3. 任意のcustomerを削除すると、
  4. customerIdによって参照されていないcustomerはcascading deleteによって全て削除されてしまう…

ということのようです。

試しに、customerIdを外部キーと考え、以下のようにid=1id=2のアイテムがお互いを参照するようにcustomerIdを設定し、id=3を削除すると、無事にid=3のみが削除されます。

db.json
{
  "customers": [
    {
      "id": 1,
      "customerId": 2,
      "name": "顧客1"
    },
    {
      "id": 2,
      "customerId": 1,
      "name": "顧客2"
    },
    {
      "id": 3,
      "customerId": 1,
      "name": "顧客3"
    }
  ]
}

対策

外部キーのサフィックスはオプションで設定できるので、適当な値に変えてやれば解決します。

json-server db.json --fks _id

ただし、実際の外部キーのサフィックスとしてIdを利用することはできなくなってしまうので、外部参照をせずアイテムを埋め込んでおくか、apiのpayloadの設計を変える必要があります。
しかし、モックサーバーの制限に合わせて本体の設計を変えるのは、控えめに言ってもおかしな話です。
個人的には、埋め込みによる対応でモックサーバーとしての役目は十分に果たせるので、問題ないといえばないのですが。

例外的なユースケースというわけでもないと思うので、公式に対策してほしいところです。しかし、現状ではcascading deleteはドキュメントにも記載されておらず、制御する方法もありません。
issueも上がっており、従属的なアイテムのみを削除+カスケードしないフラグを追加するプルリクも上がっているのですが、もう半年近く放置されています…。