Express Form Handling
Forms are a fundamental part of web applications, allowing users to submit data to your server. Express.js provides several ways to handle form submissions efficiently. In this guide, we'll cover everything you need to know about processing form data in your Express applications.
Introduction to Form Handling
When a user submits a form on a website, the browser packages the form data and sends it to the server. In Express applications, you need middleware to parse this data and make it available for your route handlers.
Forms can be submitted in different formats:
- URL-encoded forms - the traditional form submission method (
application/x-www-form-urlencoded
) - Multipart forms - forms that include file uploads (
multipart/form-data
) - JSON data - modern applications often submit forms as JSON
Let's explore how to handle each of these in Express.
Setting Up Express for Form Handling
Before we start, make sure you have Express installed in your project:
npm install express
For parsing form data, Express provides built-in middleware:
npm install express body-parser
However, for modern Express applications (4.16.0+), the body-parser functionality is included in Express itself.
Handling URL-encoded Form Data
URL-encoded forms are the most common type of form submission. Here's how to set up your Express app to handle them:
const express = require('express');
const app = express();
// Middleware to parse URL-encoded form data
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => {
// Serve a simple form
res.send(`
<h1>Basic Form</h1>
<form method="POST" action="/submit-form">
<div>
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<button type="submit">Submit</button>
</form>
`);
});
app.post('/submit-form', (req, res) => {
// Access form data from req.body
const { name, email } = req.body;
// Process the data
console.log('Form submission received:');
console.log('Name:', name);
console.log('Email:', email);
// Respond to the user
res.send(`
<h1>Form Submission Received</h1>
<p>Thank you, ${name}!</p>
<p>We've received your submission with email: ${email}</p>
`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
How It Works:
express.urlencoded({ extended: true })
is middleware that parses incoming request bodies with URL-encoded form data.- When the form is submitted, the data is available in the
req.body
object. - The
extended: true
option allows for rich objects and arrays to be encoded into the URL-encoded format.
Handling JSON Form Data
Modern web applications, especially single-page applications (SPAs), often send form data as JSON. Here's how to handle JSON data in Express:
const express = require('express');
const app = express();
// Middleware to parse JSON data
app.use(express.json());
app.get('/api-form', (req, res) => {
// Serve a page with JavaScript that will submit JSON data
res.send(`
<h1>API Form Example</h1>
<div>
<label for="jsonName">Name:</label>
<input type="text" id="jsonName">
</div>
<div>
<label for="jsonEmail">Email:</label>
<input type="email" id="jsonEmail">
</div>
<button onclick="submitForm()">Submit</button>
<script>
function submitForm() {
const name = document.getElementById('jsonName').value;
const email = document.getElementById('jsonEmail').value;
fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, email })
})
.then(response => response.json())
.then(data => {
alert('Success: ' + data.message);
})
.catch(error => {
alert('Error: ' + error);
});
}
</script>
`);
});
app.post('/api/submit', (req, res) => {
// Access JSON data from req.body
const { name, email } = req.body;
console.log('API submission received:');
console.log('Name:', name);
console.log('Email:', email);
// Send a JSON response
res.json({
success: true,
message: `Thank you, ${name}! Your submission was received.`,
data: { name, email }
});
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
How It Works:
express.json()
is middleware that parses incoming requests with JSON payloads.- The client uses
fetch
API to send JSON data to the server. - The JSON data is available in the
req.body
object. - We send back a JSON response using
res.json()
.
Handling File Uploads
To handle file uploads, we need additional middleware. The most popular option is multer
:
npm install multer
Here's how to use it:
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// Configure multer for file storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/'); // Files will be saved in the 'uploads' directory
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname)); // Unique filename
}
});
const upload = multer({ storage: storage });
app.get('/file-upload', (req, res) => {
res.send(`
<h1>File Upload Form</h1>
<form method="POST" action="/upload-file" enctype="multipart/form-data">
<div>
<label for="name">Your name:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="profile">Profile picture:</label>
<input type="file" id="profile" name="profile" accept="image/*" required>
</div>
<button type="submit">Upload</button>
</form>
`);
});
// 'profile' is the name attribute of the file input in the form
app.post('/upload-file', upload.single('profile'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file was uploaded.');
}
// Access form data
const name = req.body.name;
// Access file information
const fileInfo = {
originalName: req.file.originalname,
filename: req.file.filename,
mimetype: req.file.mimetype,
size: req.file.size,
path: req.file.path
};
console.log('File upload received:');
console.log('Name:', name);
console.log('File:', fileInfo);
res.send(`
<h1>File Uploaded Successfully</h1>
<p>Thank you, ${name}!</p>
<p>Your file "${fileInfo.originalName}" has been saved as ${fileInfo.filename}</p>
<p>File size: ${Math.round(fileInfo.size / 1024)} KB</p>
<p>File type: ${fileInfo.mimetype}</p>
`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
console.log('Make sure the "uploads" directory exists!');
});
How It Works:
multer
is middleware that handlesmultipart/form-data
, which is needed for file uploads.- We configure
multer
with a storage strategy that specifies where files should be saved. upload.single('profile')
processes a single file with the field name 'profile'.- The uploaded file information is available in
req.file
. - Other form fields are still available in
req.body
.
Form Validation
Form validation is critical for security and data integrity. Here's how to implement basic validation using Express:
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.get('/validation-form', (req, res) => {
res.send(`
<h1>Form with Validation</h1>
<form method="POST" action="/validate">
<div>
<label for="username">Username (3-20 characters):</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="password">Password (min 8 characters):</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Register</button>
</form>
`);
});
app.post('/validate', (req, res) => {
const { username, email, password } = req.body;
const errors = [];
// Validate username
if (!username || username.length < 3 || username.length > 20) {
errors.push('Username must be between 3 and 20 characters');
}
// Validate email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email || !emailRegex.test(email)) {
errors.push('Please provide a valid email address');
}
// Validate password
if (!password || password.length < 8) {
errors.push('Password must be at least 8 characters long');
}
// If there are validation errors
if (errors.length > 0) {
return res.send(`
<h1>Validation Failed</h1>
<ul>
${errors.map(error => `<li>${error}</li>`).join('')}
</ul>
<a href="/validation-form">Try Again</a>
`);
}
// If validation passes
res.send(`
<h1>Registration Successful</h1>
<p>Username: ${username}</p>
<p>Email: ${email}</p>
<p>Your account has been created successfully!</p>
`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
How It Works:
- We define validation rules for each field (username length, email format, password strength).
- When the form is submitted, we check the input against these rules.
- If any validation fails, we collect the errors and display them to the user.
- If all validations pass, we proceed with the success path.
Advanced Form Handling with Express Validator
For more complex validation, the express-validator
package is very helpful:
npm install express-validator
Here's how to use it:
const express = require('express');
const { body, validationResult } = require('express-validator');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.get('/advanced-form', (req, res) => {
res.send(`
<h1>Advanced Form Validation</h1>
<form method="POST" action="/advanced-validate">
<div>
<label for="name">Full Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="age">Age:</label>
<input type="number" id="age" name="age" min="18" required>
</div>
<div>
<label for="website">Website (optional):</label>
<input type="url" id="website" name="website">
</div>
<button type="submit">Submit</button>
</form>
`);
});
app.post('/advanced-validate', [
// Validation middleware
body('name')
.trim()
.isLength({ min: 2 })
.withMessage('Name must be at least 2 characters long')
.escape(),
body('email')
.isEmail()
.withMessage('Please enter a valid email address')
.normalizeEmail(),
body('age')
.isInt({ min: 18 })
.withMessage('You must be at least 18 years old'),
body('website')
.optional({ checkFalsy: true })
.isURL()
.withMessage('Please enter a valid URL')
], (req, res) => {
// Check for validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.send(`
<h1>Validation Failed</h1>
<ul>
${errors.array().map(error => `<li>${error.msg}</li>`).join('')}
</ul>
<a href="/advanced-form">Try Again</a>
`);
}
// Sanitized data is available in req.body
const { name, email, age, website } = req.body;
res.send(`
<h1>Form Submission Successful</h1>
<p>Name: ${name}</p>
<p>Email: ${email}</p>
<p>Age: ${age}</p>
<p>Website: ${website || 'Not provided'}</p>
`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
How It Works:
express-validator
provides middleware for validating and sanitizing input.- We define validation chains for each field using the
body
function. - Each validation chain can include multiple validation and sanitization methods.
- The validation results are collected and can be checked using
validationResult
.
Real-world Example: Contact Form with CSRF Protection
Let's create a contact form with CSRF protection using the csurf
package:
npm install csurf cookie-parser
const express = require('express');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const app = express();
// Middleware
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser()); // Required for CSRF
const csrfProtection = csrf({ cookie: true });
// Routes
app.get('/contact', csrfProtection, (req, res) => {
// Pass the CSRF token to the form
res.send(`
<h1>Contact Us</h1>
<form method="POST" action="/send-message">
<input type="hidden" name="_csrf" value="${req.csrfToken()}">
<div>
<label for="name">Your Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">Your Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="subject">Subject:</label>
<input type="text" id="subject" name="subject" required>
</div>
<div>
<label for="message">Message:</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit">Send Message</button>
</form>
`);
});
app.post('/send-message', csrfProtection, (req, res) => {
// The CSRF token is automatically validated
// If invalid, an error will be thrown
const { name, email, subject, message } = req.body;
// In a real app, you would save to a database or send an email
console.log('Contact message received:');
console.log('From:', name, email);
console.log('Subject:', subject);
console.log('Message:', message);
res.send(`
<h1>Message Sent Successfully</h1>
<p>Thank you for contacting us, ${name}.</p>
<p>We'll get back to you at ${email} as soon as possible.</p>
<a href="/contact">Back to Contact Form</a>
`);
});
// Error handler for CSRF errors
app.use((err, req, res, next) => {
if (err.code === 'EBADCSRFTOKEN') {
res.status(403).send(`
<h1>Security Error</h1>
<p>The form has been tampered with. Please try again.</p>
<a href="/contact">Back to Contact Form</a>
`);
} else {
next(err);
}
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
How It Works:
csurf
middleware generates CSRF tokens for each request.- We include the token in the form as a hidden field using
req.csrfToken()
. - When the form is submitted,
csurf
validates the token. - If the token is invalid (possible CSRF attack), an error is thrown.
- Our error handler catches CSRF errors and responds appropriately.
Summary
In this guide, we covered essential aspects of form handling in Express applications:
- Setting up middleware to handle different types of form data
- Parsing URL-encoded form data with
express.urlencoded()
- Handling JSON data with
express.json()
- Managing file uploads with
multer
- Implementing basic form validation
- Advanced validation with
express-validator
- Protecting against CSRF attacks with
csurf
Form handling is a fundamental skill for building web applications, and Express provides a flexible, powerful foundation for processing user input securely and efficiently.
Additional Resources and Exercises
Additional Resources
Exercises
-
Basic Form Processing: Create a registration form that collects username, email, and password, then displays the submitted data.
-
Form Validation Challenge: Build a user profile form that validates various fields (name, date of birth, phone number, etc.) with appropriate error messages.
-
File Upload Portfolio: Create a simple portfolio page where users can upload images with descriptions.
-
Multi-page Form: Implement a multi-step form that saves data between steps using sessions.
-
API Form Integration: Build a form that submits data to a third-party API and handles the response.
By mastering form handling in Express, you'll be able to create robust, interactive web applications that securely process user input.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)