Fix: Express req.body Is undefined
Quick Answer
How to fix req.body being undefined in Express — missing body-parser middleware, wrong Content-Type header, middleware order issues, and multipart form data handling.
The Error
You access req.body in an Express route handler but get undefined:
app.post('/api/users', (req, res) => {
console.log(req.body); // undefined
const { name, email } = req.body; // TypeError: Cannot destructure property 'name' of undefined
});Or req.body is an empty object {} even though you sent data:
curl -X POST http://localhost:3000/api/users \
-d '{"name":"Alice","email":"[email protected]"}'
# Server logs: {} ← Empty, not undefined, but still missing dataOr you get a parse error:
SyntaxError: Unexpected token o in JSON at position 0Why This Happens
Express does not parse request bodies by default. req.body is undefined unless you add middleware that reads and parses the incoming request body before your route handler runs:
- Missing
express.json()orexpress.urlencoded()middleware — the most common cause. Without these, Express doesn’t touch the body. - Wrong
Content-Typeheader —express.json()only parses bodies withContent-Type: application/json. Sending form data withapplication/jsonmiddleware won’t work, and vice versa. - Middleware added after route definition — Express executes middleware in registration order. If you define routes before
app.use(express.json()), the body isn’t parsed when the route runs. - Using an old
body-parserpackage incorrectly —body-parserwas a separate package in older Express versions. Express 4.16+ includes it built-in viaexpress.json()andexpress.urlencoded(). - Router-level middleware not applied — if you use Express Router, body-parsing middleware on
appdoesn’t automatically apply to the router unless it’s registered before the router. - Multipart form data —
express.json()andexpress.urlencoded()don’t handlemultipart/form-data(file uploads). Those requiremulteror similar.
Fix 1: Add express.json() Middleware
For JSON request bodies (the most common case with REST APIs):
const express = require('express');
const app = express();
// Add BEFORE your route definitions
app.use(express.json());
// Now req.body is parsed
app.post('/api/users', (req, res) => {
console.log(req.body); // { name: 'Alice', email: '[email protected]' }
const { name, email } = req.body;
res.json({ name, email });
});
app.listen(3000);Test with curl — always set the Content-Type header:
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"[email protected]"}'Common Mistake: Sending JSON without the
Content-Type: application/jsonheader. Express’s JSON middleware checks the header first — if it’s missing or wrong, the body is not parsed andreq.bodystaysundefined.
Fix 2: Add express.urlencoded() for Form Data
HTML forms and some API clients send data as application/x-www-form-urlencoded. Use express.urlencoded() for this:
app.use(express.json()); // For JSON bodies
app.use(express.urlencoded({ extended: true })); // For form-encoded bodies
app.post('/api/users', (req, res) => {
console.log(req.body); // { name: 'Alice', email: '[email protected]' }
});Test with curl using form encoding:
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=Alice&email=alice%40example.com"extended: true vs extended: false:
extended: true— uses theqslibrary, supports nested objects:user[name]=Aliceextended: false— uses the built-inquerystringmodule, flat key-value pairs only
Use extended: true for modern applications.
Fix 3: Fix Middleware Order
Express applies middleware in the order it’s registered. If routes are defined before express.json(), the body won’t be parsed:
// WRONG — route defined before body-parser middleware
app.post('/api/users', (req, res) => {
console.log(req.body); // undefined — body-parser hasn't run yet
});
app.use(express.json()); // Too late for the route above// CORRECT — middleware registered before routes
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes come after
app.post('/api/users', (req, res) => {
console.log(req.body); // { name: 'Alice' } ✓
});Check that your middleware setup follows this pattern:
const express = require('express');
const app = express();
// 1. Logging middleware
app.use(morgan('dev'));
// 2. Body parsing middleware — before routes
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 3. Auth middleware
app.use(authMiddleware);
// 4. Routes — after all middleware
app.use('/api/users', userRouter);
app.use('/api/posts', postRouter);Fix 4: Fix Router-Level Middleware
When using express.Router(), body-parsing middleware must be applied before the router is mounted, or added to the router itself:
// app.js
const express = require('express');
const app = express();
const userRouter = require('./routes/users');
// Apply body-parser BEFORE mounting routers
app.use(express.json());
app.use('/api/users', userRouter); // Router will have req.body populated// routes/users.js
const express = require('express');
const router = express.Router();
// req.body is available here because express.json() runs in app.js first
router.post('/', (req, res) => {
const { name, email } = req.body;
res.json({ name, email });
});
module.exports = router;Alternatively, add middleware to the router itself:
// routes/users.js
const express = require('express');
const router = express.Router();
// Apply middleware at router level
router.use(express.json());
router.post('/', (req, res) => {
console.log(req.body); // Parsed ✓
});
module.exports = router;Fix 5: Handle Multipart Form Data (File Uploads)
express.json() and express.urlencoded() don’t parse multipart/form-data. Use multer for file uploads and multipart forms:
npm install multerconst multer = require('multer');
// In-memory storage (for processing the file as a buffer)
const upload = multer({ storage: multer.memoryStorage() });
// Disk storage (save directly to disk)
const diskUpload = multer({ dest: 'uploads/' });
// Single file upload — 'avatar' is the form field name
app.post('/api/avatar', upload.single('avatar'), (req, res) => {
console.log(req.file); // File metadata and buffer
console.log(req.body); // Other form fields
res.json({ filename: req.file.originalname });
});
// Multiple files
app.post('/api/photos', upload.array('photos', 10), (req, res) => {
console.log(req.files); // Array of file objects
console.log(req.body); // Other form fields
});Test with curl:
curl -X POST http://localhost:3000/api/avatar \
-F "avatar=@/path/to/photo.jpg" \
-F "username=alice"Note: When the request uses
multipart/form-data, don’t set theContent-Typeheader manually in curl — curl sets it automatically with the correct boundary. Setting it manually breaks the parsing.
Fix 6: Migrate from Standalone body-parser
Older Express code uses the separate body-parser package. Migrate to the built-in methods:
// Old way (body-parser package, still works but unnecessary in Express 4.16+)
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// New way (built-in Express 4.16+)
app.use(express.json());
app.use(express.urlencoded({ extended: true }));Both are functionally identical. The built-in methods delegate to body-parser internally.
Fix 7: Debug What the Client Is Actually Sending
If you’re unsure whether the client is sending the right Content-Type and body format, log the raw request:
app.use((req, res, next) => {
console.log('Content-Type:', req.headers['content-type']);
console.log('Method:', req.method);
next();
});
app.use(express.json());
app.post('/api/test', (req, res) => {
console.log('Body:', req.body);
res.json({ received: req.body });
});Common Content-Type mismatches:
| Client sends | Middleware needed |
|---|---|
application/json | express.json() |
application/x-www-form-urlencoded | express.urlencoded() |
multipart/form-data | multer |
text/plain | Custom middleware or express.text() |
application/octet-stream | express.raw() |
Use express.text() for plain text bodies:
app.use(express.text()); // Parses text/plain as a string
app.post('/webhook', (req, res) => {
console.log(req.body); // The raw string body
});Use express.raw() for binary data:
app.use(express.raw({ type: 'application/octet-stream' }));
app.post('/upload', (req, res) => {
console.log(req.body); // Buffer
});Still Not Working?
Check if a conflicting middleware is consuming the body stream. Some middleware (like raw body parsing for Stripe webhooks) reads the body stream, leaving nothing for express.json() to parse:
// Stripe webhook needs the raw body — must be before express.json()
app.post(
'/webhook/stripe',
express.raw({ type: 'application/json' }), // Raw body for signature verification
(req, res) => {
const rawBody = req.body; // Buffer
// verify Stripe signature, then process
}
);
// All other routes use parsed JSON
app.use(express.json());
app.post('/api/users', (req, res) => {
console.log(req.body); // Normal parsed JSON ✓
});Check for a proxy stripping headers — if Express sits behind an nginx or load balancer that strips the Content-Type header, the body won’t be parsed. Check req.headers['content-type'] to confirm it’s arriving correctly.
Set the body size limit — if the request body exceeds the default limit (100kb for JSON), Express rejects it with a 413 Payload Too Large error before req.body is populated:
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));For related Express issues, see Fix: Express Cannot GET Route and Fix: Express CORS Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Node.js Crashing with UnhandledPromiseRejection (--unhandled-rejections)
How to fix Node.js UnhandledPromiseRejectionWarning and process crashes — why unhandled promise rejections crash Node.js 15+, how to add global handlers, find the source of the rejection, and fix async error handling.
Fix: TypeScript Decorators Not Working (experimentalDecorators)
How to fix TypeScript decorators not applying — experimentalDecorators not enabled, emitDecoratorMetadata missing, reflect-metadata not imported, and decorator ordering issues.
Fix: Socket.IO Not Connecting (CORS, Transport, and Namespace Errors)
How to fix Socket.IO connection failures — CORS errors, transport fallback issues, wrong namespace, server not emitting to the right room, and how to debug Socket.IO connections in production.
Fix: Stripe Webhook Signature Verification Failed
How to fix Stripe webhook signature verification errors — why Stripe-Signature header validation fails, how to correctly pass the raw request body, and how to debug webhook delivery in the Stripe dashboard.