Skip to main content

Express Content Types

When building web applications with Express.js, understanding how to handle different types of content is essential. In HTTP communication, the Content-Type header specifies the format of the data being sent in a request or response. This guide will help you understand how to work with various content types in Express.js applications.

Introduction to Content Types

In web development, content types (also known as MIME types) tell the client or server what kind of data is being transmitted. Express.js provides built-in middleware and methods to handle different content types efficiently.

Some common content types include:

  • application/json - JSON data
  • application/x-www-form-urlencoded - Form data
  • multipart/form-data - Form data with file uploads
  • text/plain - Plain text
  • text/html - HTML content

Built-in Middleware for Content Parsing

Express.js provides middleware functions that automatically parse incoming request bodies in different formats:

1. Parsing JSON Data

To handle JSON data in requests, use the express.json() middleware:

javascript
const express = require('express');
const app = express();

// Add middleware to parse JSON requests
app.use(express.json());

app.post('/api/users', (req, res) => {
// req.body now contains the parsed JSON data
console.log(req.body);
res.status(201).json({ message: 'User created', user: req.body });
});

app.listen(3000, () => {
console.log('Server running on port 3000');
});

Example JSON request:

POST /api/users HTTP/1.1
Host: localhost:3000
Content-Type: application/json

{
"name": "John Doe",
"email": "[email protected]",
"age": 30
}

2. Parsing URL-encoded Forms

For traditional form submissions (without file uploads), use express.urlencoded():

javascript
// Add middleware to parse URL-encoded requests
app.use(express.urlencoded({ extended: true }));

app.post('/login', (req, res) => {
const { username, password } = req.body;
console.log(`Login attempt: ${username}`);

// Process login logic here
res.send(`Welcome, ${username}!`);
});

The extended: true option allows parsing rich objects and arrays in the URL-encoded format using the qs library.

Example form submission:

html
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">Login</button>
</form>

3. Handling File Uploads with Multer

For handling multipart/form-data (forms with file uploads), Express doesn't have built-in middleware. Instead, you can use the popular multer package:

javascript
const express = require('express');
const multer = require('multer');
const app = express();

// Configure storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, Date.now() + '-' + file.originalname);
}
});

const upload = multer({ storage: storage });

// Single file upload
app.post('/upload', upload.single('profileImage'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded');
}

res.send({
message: 'File uploaded successfully',
file: req.file
});
});

// Multiple files upload
app.post('/upload-multiple', upload.array('images', 5), (req, res) => {
res.send({
message: 'Files uploaded successfully',
files: req.files
});
});

Example HTML form for file upload:

html
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="profileImage">
<button type="submit">Upload</button>
</form>

Setting Content Types in Responses

Just as you need to handle content types in requests, it's equally important to set the correct content type in responses:

javascript
// JSON response
app.get('/api/data', (req, res) => {
const data = {
name: "Express App",
version: "1.0.0"
};

// Express automatically sets Content-Type: application/json
res.json(data);
});

// HTML response
app.get('/html-page', (req, res) => {
res.set('Content-Type', 'text/html');
res.send('<html><body><h1>Hello World</h1></body></html>');
});

// Plain text response
app.get('/text', (req, res) => {
res.set('Content-Type', 'text/plain');
res.send('This is plain text content');
});

// PDF response
app.get('/download-pdf', (req, res) => {
// Assuming `report.pdf` exists in the server
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="report.pdf"'
});
res.sendFile('/path/to/report.pdf');
});

Content Negotiation

Content negotiation allows a client to specify which content type they prefer to receive. Express can use the Accept header to determine the appropriate response format:

javascript
app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
const user = { id: userId, name: 'Alice', email: '[email protected]' };

// Check what format the client wants to receive
res.format({
'application/json': () => {
res.json(user);
},
'text/html': () => {
res.send(`
<html>
<body>
<h1>User Details</h1>
<p>ID: ${user.id}</p>
<p>Name: ${user.name}</p>
<p>Email: ${user.email}</p>
</body>
</html>
`);
},
'text/plain': () => {
res.send(`User ${user.id}: ${user.name} (${user.email})`);
},
'default': () => {
// If no formats match, send as JSON
res.status(406).json({ error: 'Not Acceptable' });
}
});
});

Real-World Example: REST API with Multiple Content Types

Let's create a more comprehensive example: a product API that can handle different content types for both requests and responses:

javascript
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();

// Set up middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Configure multer for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'product-images/');
},
filename: (req, file, cb) => {
cb(null, `product-${Date.now()}${path.extname(file.originalname)}`);
}
});
const upload = multer({ storage });

// In-memory database
const products = [];

// Create a product (supports JSON and form submissions)
app.post('/api/products', (req, res) => {
const product = {
id: Date.now().toString(),
name: req.body.name,
price: parseFloat(req.body.price),
description: req.body.description,
createdAt: new Date()
};

products.push(product);
res.status(201).json(product);
});

// Add product image
app.post('/api/products/:id/image', upload.single('productImage'), (req, res) => {
const productId = req.params.id;
const product = products.find(p => p.id === productId);

if (!product) {
return res.status(404).json({ error: 'Product not found' });
}

if (!req.file) {
return res.status(400).json({ error: 'No image uploaded' });
}

product.imageUrl = `/images/${req.file.filename}`;
res.json(product);
});

// Get products with content negotiation
app.get('/api/products', (req, res) => {
res.format({
'application/json': () => {
res.json(products);
},
'text/html': () => {
let html = '<html><body><h1>Products</h1><ul>';
products.forEach(product => {
html += `<li>${product.name}: $${product.price}</li>`;
});
html += '</ul></body></html>';
res.send(html);
},
'text/csv': () => {
let csv = 'ID,Name,Price,Description\n';
products.forEach(product => {
csv += `${product.id},${product.name},${product.price},"${product.description}"\n`;
});
res.set('Content-Type', 'text/csv');
res.send(csv);
},
'default': () => {
res.status(406).json({ error: 'Not Acceptable' });
}
});
});

app.listen(3000, () => {
console.log('Server running on port 3000');
});

Security Considerations

When handling different content types, keep these security best practices in mind:

  1. Always set content size limits to prevent denial-of-service attacks:
javascript
// Limit JSON payloads to 1MB
app.use(express.json({ limit: '1mb' }));

// Limit URL-encoded payloads to 1MB
app.use(express.urlencoded({ extended: true, limit: '1mb' }));
  1. Validate content types to prevent unexpected behavior:
javascript
app.post('/api/data', (req, res, next) => {
const contentType = req.headers['content-type'];

if (!contentType || contentType.indexOf('application/json') !== 0) {
return res.status(415).json({ error: 'Unsupported Media Type' });
}

next();
});
  1. Sanitize inputs to prevent injection attacks when rendering content.

Summary

In this guide, you've learned:

  • How to parse different request body formats with Express middleware
  • How to handle file uploads using multer
  • How to set appropriate content types for responses
  • How to implement content negotiation for different response formats
  • Security considerations when working with different content types

Understanding how to work with various content types is essential for building robust Express.js applications that can communicate effectively with different clients and services.

Additional Resources

Exercises

  1. Create an Express application that accepts a text file upload and counts the number of words in it.
  2. Implement an API endpoint that can return user data in either JSON or XML format based on the Accept header.
  3. Create a form submission handler that validates different types of data (numbers, emails, dates) and returns appropriate error messages.
  4. Build a simple file conversion service that accepts an image and can convert it to different formats.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)