What this usually means
Express does not parse request bodies by default. You need middleware to read the request stream, parse the body according to the Content-Type header, and populate `req.body`. If the middleware is missing, applied after your route, or configured for a different content type than what the client sends, `req.body` will be undefined or empty.
The first ten minutes \u2014 establish facts before touching code.
- 1Check if body parsing middleware is added. `app.use(express.json())` for JSON, `app.use(express.urlencoded({ extended: true }))` for form data.
- 2Check the order of middleware. Body parsing must come BEFORE your routes. `app.use(express.json())` then `app.use('/api', router)`.
- 3Check the Content-Type header of the incoming request. `application/json` needs `express.json()`. `application/x-www-form-urlencoded` needs `express.urlencoded()`. `multipart/form-data` needs a library like multer.
- 4Check if the request body size exceeds the limit. Express JSON parser defaults to 100kb. A larger body is rejected silently (req.body is empty).
- 5Check for other middleware that might consume the request stream before the body parser gets it.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchExpress app setup — `app.use(express.json())` and `app.use(express.urlencoded())`
- searchMiddleware order — body parsers must be registered before route handlers
- searchRequest Content-Type header — check in DevTools or with `req.headers['content-type']`
- searchBody size — is the payload larger than the parser's limit?
- searchRoute definition — is the route registered after the body parser middleware?
- searchCustom middleware — any middleware that reads `req.body` or the request stream before the parser?
Practical causes, not theory. These are the things you will actually find.
- warning`express.json()` middleware is not added to the app
- warningBody parser middleware is added after route definitions
- warningContent-Type header is missing or does not match the parser (sending JSON with `text/plain`)
- warningRequest body exceeds the parser's size limit
- warningAnother middleware reads the request body stream before the parser
- warningUsing `express.raw()` or a custom parser that conflicts with the standard parsers
- warningRoute uses a different Express app instance or router that does not have the body parser
Concrete fix directions. Pick the one that matches your root cause.
- buildAdd `app.use(express.json())` and `app.use(express.urlencoded({ extended: true }))` at the top of middleware, before routes
- buildIncrease the body size limit if needed: `app.use(express.json({ limit: '10mb' }))`
- buildEnsure the client sets the correct Content-Type header: `'Content-Type': 'application/json'`
- buildFor multipart/form-data (file uploads), use `multer` or `busboy` — Express built-in parsers do not handle multipart
- buildAdd error handling for body parse failures: `app.use((err, req, res, next) => { if (err.type === 'entity.too.large') ... })`
A fix you cannot prove is a guess. Close the loop.
- verifiedSend a POST request with a JSON body and `Content-Type: application/json`. `req.body` should contain the parsed object.
- verifiedSend a request with a body larger than the limit. The server should return a 413 error, not silently ignore the body.
- verifiedSend a request with the wrong Content-Type. The server should still handle it gracefully (empty body or error).
- verifiedCheck that all routes — including error-handling routes — have access to parsed body data.
- verifiedTest with an empty body and confirm `req.body` is `{}` not `undefined`.
Things that make this bug worse or harder to find.
- warningForgetting to add body parsing middleware — it is not built into Express 4+ by default
- warningAdding body parser after routes — Express processes middleware in order
- warningNot setting a reasonable body size limit — default 100kb is small for many use cases
- warningAssuming `req.body` works for multipart/form-data without a library like multer
- warningNot handling body parse errors — the client gets a generic 500 instead of a helpful 400 or 413