【Express】シンプルなREST APIを作る(CRUD実装)

Expressjs

こんにちは、かつコーチです。

ルーティングやミドルウェアを学んだら、次はいよいよ実践です。

この記事では、データベースを使わずに、配列(インメモリデータ)を使ったシンプルなREST APIを作りながら、CRUDの基本を身につけます。

CRUDとは、Create(作成)、Read(取得)、Update(更新)、Delete(削除)の頭文字を取った言葉で、Webアプリの基本操作を表します。

DB接続は登場しないので、Node.jsとExpressの基礎だけで手を動かして理解できる内容になっています。

基本の書き方 / 実装手順

準備:プロジェクトのセットアップ

まずはプロジェクトを作成し、Expressをインストールします。

mkdir todo-api
cd todo-api
npm init -y
npm install express

今回は、シンプルな「ToDoリストAPI」を題材にします。

手順1:インメモリデータを用意する

データベースの代わりに、配列でタスクのデータを保持します。

// server.js
const express = require('express');
const app = express();

app.use(express.json());

// インメモリのデータストア(サーバーを再起動すると消える)
let todos = [
  { id: 1, title: '牛乳を買う', done: false },
  { id: 2, title: 'レポートを提出する', done: false },
];
let nextId = 3; // 次に採番するID

手順2:GET(一覧取得・詳細取得)

まずは、Read(取得)のエンドポイントから作ります。

// 一覧取得:GET /todos
app.get('/todos', (req, res) => {
  res.json(todos);
});

// 詳細取得:GET /todos/:id
app.get('/todos/:id', (req, res) => {
  const todo = todos.find((t) => t.id === Number(req.params.id));

  if (!todo) {
    return res.status(404).json({ error: 'タスクが見つかりません' });
  }

  res.json(todo);
});

req.params.idはURLのパスパラメータのため、常に文字列型で渡されます。

配列内のデータは数値型のIDなので、Number()で変換してから比較する必要がある点に注意してください。

手順3:POST(新規作成)

次に、Create(作成)のエンドポイントを作ります。

// 新規作成:POST /todos
app.post('/todos', (req, res) => {
  const { title } = req.body;

  if (!title) {
    return res.status(400).json({ error: 'titleは必須です' });
  }

  const newTodo = { id: nextId++, title, done: false };
  todos.push(newTodo);

  // 作成成功時は201 Createdを返すのが慣例
  res.status(201).json(newTodo);
});

作成が成功したときのステータスコードは、200ではなく201(Created)を返すのがREST APIの慣例です。

手順4:PUT(更新)

続いて、Update(更新)のエンドポイントです。

// 更新:PUT /todos/:id
app.put('/todos/:id', (req, res) => {
  const todo = todos.find((t) => t.id === Number(req.params.id));

  if (!todo) {
    return res.status(404).json({ error: 'タスクが見つかりません' });
  }

  const { title, done } = req.body;

  if (title !== undefined) todo.title = title;
  if (done !== undefined) todo.done = done;

  res.json(todo);
});

done !== undefinedのようにチェックしているのは、req.bodyに含まれていない項目まで上書きしないようにするためです。

手順5:DELETE(削除)

最後に、Delete(削除)のエンドポイントです。

// 削除:DELETE /todos/:id
app.delete('/todos/:id', (req, res) => {
  const index = todos.findIndex((t) => t.id === Number(req.params.id));

  if (index === -1) {
    return res.status(404).json({ error: 'タスクが見つかりません' });
  }

  todos.splice(index, 1);

  // 削除成功時はボディなしの204 No Contentを返すのが一般的
  res.status(204).send();
});

app.listen(3000, () => {
  console.log('ToDo APIを起動しました: http://localhost:3000');
});

削除に成功したときは、返すデータがないため204 No Contentを返すのが一般的です。

これで、GET・POST・PUT・DELETEがそろった、最小限のCRUD APIが完成しました。

よくあるつまずきポイント・エラー対処

IDの型がずれて検索がヒットしない

CRUD APIを初めて作ったとき、一番ハマりやすいのがID比較の型不一致です。

// ❌ Before:型変換せずに厳密等価で比較する
app.get('/todos/:id', (req, res) => {
  const todo = todos.find((t) => t.id === req.params.id); // 常にfalseになる
  // ...
});

req.params.idは文字列("1")ですが、配列内のidは数値(1)です。

===は型まで含めて比較するため、1 === "1"falseとなり、データが存在するのに404が返り続けてしまいます。

私も実際にこのバグに遭遇し、「配列には確かにデータがあるのに、なぜ見つからないんだ」と原因を探すのに時間を使いました。

// ✅ After:Number()で型を揃えてから比較する
app.get('/todos/:id', (req, res) => {
  const todo = todos.find((t) => t.id === Number(req.params.id));
  // ...
});

パスパラメータを使って配列やDBのIDと照合するときは、必ず型変換を意識してください。

POSTしたのにtitleがundefinedになる

もう1つのよくある失敗が、express.json()の登録忘れです。

// ❌ Before:express.json()を登録していない
const express = require('express');
const app = express();

app.post('/todos', (req, res) => {
  console.log(req.body.title); // undefined
  // ...
});

このコードでPOSTリクエストを送ると、req.body自体がundefinedになり、req.body.titleを読み取ろうとした瞬間に以下のエラーが発生します。

TypeError: Cannot read properties of undefined (reading 'title')
// ✅ After:app.use(express.json())を先に登録する
const express = require('express');
const app = express();

app.use(express.json());

app.post('/todos', (req, res) => {
  console.log(req.body.title); // 正しく取得できる
  // ...
});

POST・PUTのエンドポイントを作る前には、必ずapp.use(express.json())を書いたか確認する習慣をつけましょう。

まとめ

この記事のポイント

  • CRUDは、Create・Read・Update・Deleteの4つの基本操作
  • GET(一覧・詳細)、POST(作成)、PUT(更新)、DELETE(削除)でエンドポイントを構成する
  • パスパラメータのreq.params.idは文字列なので、比較前にNumber()で変換する
  • 作成成功は201、削除成功は204など、ステータスコードの慣例を意識する
  • express.json()の登録忘れは、req.bodyundefinedになる典型的な原因

次に読むべき記事

  • ルーターを分割して整理する(express.Router()の使い方)
  • エラーハンドリングミドルウェアで一元的にエラーを処理する
  • ExpressアプリをRenderにデプロイする方法

タグ: Express, 中級者向け, 実践Tips

タイトルとURLをコピーしました