Skip to content

Fix: Express req.body Is undefined

FixDevs ·

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 data

Or you get a parse error:

SyntaxError: Unexpected token o in JSON at position 0

Why 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() or express.urlencoded() middleware — the most common cause. Without these, Express doesn’t touch the body.
  • Wrong Content-Type headerexpress.json() only parses bodies with Content-Type: application/json. Sending form data with application/json middleware 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-parser package incorrectlybody-parser was a separate package in older Express versions. Express 4.16+ includes it built-in via express.json() and express.urlencoded().
  • Router-level middleware not applied — if you use Express Router, body-parsing middleware on app doesn’t automatically apply to the router unless it’s registered before the router.
  • Multipart form dataexpress.json() and express.urlencoded() don’t handle multipart/form-data (file uploads). Those require multer or 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/json header. Express’s JSON middleware checks the header first — if it’s missing or wrong, the body is not parsed and req.body stays undefined.

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 the qs library, supports nested objects: user[name]=Alice
  • extended: false — uses the built-in querystring module, 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 multer
const 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 the Content-Type header 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 sendsMiddleware needed
application/jsonexpress.json()
application/x-www-form-urlencodedexpress.urlencoded()
multipart/form-datamulter
text/plainCustom middleware or express.text()
application/octet-streamexpress.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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles