Express.js Handling Form Data
HTML forms send data to the server when users submit them. A login form sends a username and password. A signup form sends name, email, and password. A contact form sends a message. Express reads this data from the request body and uses it to perform actions like saving to a database, authenticating users, or sending emails. This topic covers everything you need to handle form submissions correctly.
How Forms Send Data to Express
When a user fills in a form and clicks Submit, the browser packages the form fields into an HTTP request and sends it to your server. The way the data gets packaged depends on the form's enctype attribute:
┌──────────────────────────────────────────────────────────────┐ │ Form Encoding Types │ ├────────────────────────────┬─────────────────────────────────┤ │ application/x-www-form- │ Default form encoding. │ │ urlencoded │ Fields sent as key=value pairs │ │ │ in the request body │ ├────────────────────────────┼─────────────────────────────────┤ │ multipart/form-data │ Used for file uploads. │ │ │ Required when form includes │ │ │ <input type="file"> │ └────────────────────────────┴─────────────────────────────────┘
Step 1: Add Body-Parsing Middleware
Express does not read the request body automatically. You must add middleware to parse it. For standard HTML form submissions, use the built-in express.urlencoded() middleware:
const express = require('express');
const app = express();
// Parses URL-encoded form data (standard HTML forms)
app.use(express.urlencoded({ extended: true }));
// Parses JSON bodies (for API clients and fetch requests)
app.use(express.json());
Add these lines before your routes. Without them, req.body returns undefined.
Step 2: Create an HTML Form
Place this HTML file in your public folder so Express serves it as a static file:
<!-- public/signup.html -->
<!DOCTYPE html>
<html>
<head>
<title>Sign Up</title>
</head>
<body>
<h2>Create an Account</h2>
<form action="/signup" method="POST">
<label>Name:</label>
<input type="text" name="name" required />
<br>
<label>Email:</label>
<input type="email" name="email" required />
<br>
<label>Password:</label>
<input type="password" name="password" required />
<br>
<button type="submit">Sign Up</button>
</form>
</body>
</html>
The action="/signup" tells the browser where to send the data. The method="POST" tells it to use a POST request. Each input's name attribute becomes the key in req.body.
Step 3: Handle the Form Submission
// Serve the form page
app.use(express.static('public'));
// Process the form submission
app.post('/signup', (req, res) => {
const { name, email, password } = req.body;
console.log('Form data received:');
console.log('Name:', name);
console.log('Email:', email);
console.log('Password:', password);
// In a real app: validate, hash password, save to database
res.send(`Account created for ${name}! Please log in.`);
});
When the user submits the form, Express reads the body, extracts the fields, and runs your handler function.
Visualizing the Form Submission Flow
User fills in form and clicks Submit
│
▼
Browser creates POST request:
URL: /signup
Method: POST
Body: name=Alice&email=alice@ex.com&password=secret
│
▼
Express receives the request
express.urlencoded() parses the body
│
▼
req.body = {
name: 'Alice',
email: 'alice@ex.com',
password: 'secret'
}
│
▼
Your handler runs the logic
│
▼
res.send('Account created!') → browser shows result
Validating Form Data
Always validate form inputs before processing them. Never trust user input:
app.post('/signup', (req, res) => {
const { name, email, password } = req.body;
const errors = [];
if (!name || name.trim().length < 2) {
errors.push('Name must be at least 2 characters');
}
if (!email || !email.includes('@')) {
errors.push('Valid email is required');
}
if (!password || password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
// Validation passed — process the signup
res.status(201).json({ message: `Welcome, ${name}!` });
});
Handling a Login Form
// Fake user database for demonstration
const users = [
{ email: 'alice@example.com', password: 'password123' }
];
app.post('/login', (req, res) => {
const { email, password } = req.body;
// Find user by email
const user = users.find(u => u.email === email);
if (!user || user.password !== password) {
return res.status(401).json({ error: 'Invalid email or password' });
}
res.json({ message: `Welcome back, ${email}!` });
});
Note: Real applications never store or compare plain text passwords. Topic 18 covers proper password hashing with bcrypt.
Handling Checkboxes and Radio Buttons
Checkboxes and radio buttons behave differently than text inputs:
<form action="/preferences" method="POST"> <!-- Checkbox: sends value only when checked --> <input type="checkbox" name="newsletter" value="yes" /> Subscribe <!-- Radio: only one value per group --> <input type="radio" name="plan" value="basic" /> Basic <input type="radio" name="plan" value="pro" /> Pro <button type="submit">Save</button> </form>
app.post('/preferences', (req, res) => {
const newsletter = req.body.newsletter === 'yes'; // true or false
const plan = req.body.plan; // 'basic' or 'pro'
res.json({ newsletter, plan });
});
If a checkbox is unchecked, the browser sends nothing for that field, so req.body.newsletter is undefined. Always handle this case.
Handling Multiple Select and Arrays
<select name="skills[]" multiple> <option value="html">HTML</option> <option value="css">CSS</option> <option value="js">JavaScript</option> </select>
app.post('/profile', (req, res) => {
let skills = req.body['skills[]'];
if (!Array.isArray(skills)) {
skills = skills ? [skills] : []; // Wrap single value in array
}
res.json({ skills });
});
The Redirect After POST Pattern
After processing a form submission, redirect the user to another page instead of rendering a response directly. This prevents the "form resubmission" warning that browsers show when users refresh the page:
app.post('/signup', (req, res) => {
const { name, email } = req.body;
// Process the signup (save to database, etc.)
console.log(`New user: ${name}, ${email}`);
// Redirect to a success page instead of sending a response here
res.redirect('/signup-success');
});
app.get('/signup-success', (req, res) => {
res.send('Account created successfully! Please check your email.');
});
Summary
Use express.urlencoded({ extended: true }) to parse standard HTML form submissions. Access submitted values through req.body. Set each input's name attribute — that name becomes the key in req.body. Always validate inputs before processing and return clear error messages for invalid data. Use res.redirect() after processing a POST form to prevent browser resubmission prompts. Handle special inputs like checkboxes (which send nothing when unchecked) and multi-selects (which may send a single value or an array) with explicit checks in your handler.
