できない.dev

Express で req.body が undefined になり POST の値を受け取れない

req.body はボディを解析するミドルウェアを通ったときだけ値が入る。
express.json() の登録漏れや登録順、Content-Type の不一致、multipart の送信が主な原因で、Express 5 では解析されないと {} ではなく undefined になる。

公開: 更新:

実行例あり(2026-09-30 に実環境で検証)

要約

Express の req.body は、ボディを解析するミドルウェアを通ったときだけ値が入る。公式リファレンス(新しいタブで開く)のとおり既定は undefined で、express.json() や express.urlencoded() を登録して初めて中身が入る。

const express = require("express");
const app = express();
 
app.use(express.json()); // これが無いと req.body は undefined のまま
 
app.post("/users", (req, res) => {
  res.json({ received: req.body });
});

ミドルウェアを登録していても、リクエストの Content-Type がパーサーの対象と一致しなければ解析されない。
このとき Express 4 では req.body が {} になり、Express 5 では undefined のままになる。

実行例

Express 5 のサーバーで express.json() を登録しないまま JSON を POST すると Internal Server Error が返り、サーバーのログには TypeError: Cannot read properties of undefined (reading 'name') が出た。express.json() を登録すると Content-Type: application/json 付きの送信は値を受け取れたが、curl の -d だけで送ると同じ TypeError に戻り、req.body ?? {} で読む形に書き換えると 400 Bad Request を返せた。

$ NODE_ENV=production node server.js > server.log 2>&1 &
$ curl -s -X POST -H "Content-Type: application/json" -d '{"name":"taro"}' http://localhost:3000/users
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Internal Server Error</pre>
</body>
</html>
$ grep -m1 TypeError server.log
TypeError: Cannot read properties of undefined (reading 'name')
$ NODE_ENV=production node server.js > server.log 2>&1 &
$ curl -s -X POST -H "Content-Type: application/json" -d '{"name":"taro"}' http://localhost:3000/users
{"name":"taro"}
$ curl -s -X POST -d '{"name":"taro"}' http://localhost:3000/users
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Internal Server Error</pre>
</body>
</html>
$ grep -m1 TypeError server.log
TypeError: Cannot read properties of undefined (reading 'name')
$ NODE_ENV=production node server.js > server.log 2>&1 &
$ curl -s -i -X POST -d '{"name":"taro"}' http://localhost:3000/users
HTTP/1.1 400 Bad Request
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 28
ETag: W/"1c-Rot2BI1sDts2rb2R9pfKSqROplo"
Date: Wed, 30 Sep 2026 01:03:46 GMT
Connection: keep-alive
Keep-Alive: timeout=5
 
{"error":"name is required"}

— 2026-09-30 時点の出力

検証環境

検証日
実行環境
node:20Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. パーサーを登録していない: Express は最小構成で、ボディを読む処理は自分で差し込む。express.json() と express.urlencoded() は Express 4.16.0 から組み込まれているので、body-parser を別途入れる必要はない。
  2. 登録の順番が遅い: ミドルウェアは登録した順に実行される。app.post() の後に app.use(express.json()) を書くと、そのルートに届いた時点ではまだ解析されていない。
  3. Content-Type が一致していない: express.json() の type オプションの既定は application/json。
    ヘッダーを付けずに fetch() へ文字列を渡すと text/plain;charset=UTF-8、curl の -d だけなら application/x-www-form-urlencoded で送られ、どちらも対象から外れる。
  4. multipart/form-data を送っている: FormData を body に渡すと multipart で送られるが、express.json() も express.urlencoded() も multipart は解析しない。
  5. Express 5 への移行: 移行ガイド(新しいタブで開く)にあるとおり、解析されなかったときの req.body は Express 4 の {} から Express 5 では undefined に変わった。
    パーサーを登録していれば 4 系では undefined を返すだけだった req.body.name が、5 系では TypeError: Cannot read properties of undefined (reading 'name') で落ちる。

解決策

1. パーサーをルートより前で登録する

const express = require("express");
const app = express();
 
app.use(express.json()); // application/json
app.use(express.urlencoded({ extended: false })); // HTML フォーム
 
app.post("/users", (req, res) => {
  res.json(req.body);
});

Express 5 では express.urlencoded() の extended の既定が false になった。user[name]=taro のような入れ子のキーをオブジェクトとして受け取りたいときは { extended: true } を明示する。

2. クライアントで Content-Type を付けて送る

await fetch("/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "taro" }),
});

curl で試すときも同じヘッダーを付ける。

curl -X POST -H "Content-Type: application/json" -d '{"name":"taro"}' http://localhost:3000/users

3. multipart は multer で解析する

const multer = require("multer");
const upload = multer();
 
app.post("/profile", upload.none(), (req, res) => {
  res.json(req.body); // テキストのフィールドが入る
});

ファイルも受け取るなら upload.single("avatar") などを使い、ファイルは req.file に入る。

4. 解析されていない場合を考えてから読む

app.post("/users", (req, res) => {
  const { name } = req.body ?? {};
  if (!name) return res.status(400).json({ error: "name is required" });
  res.json({ name });
});

この記事は役立ちましたか?