Skip to main content

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:

  1. URL-encoded forms - the traditional form submission method (application/x-www-form-urlencoded)
  2. Multipart forms - forms that include file uploads (multipart/form-data)
  3. 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:

bash
npm install express

For parsing form data, Express provides built-in middleware:

bash
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:

javascript
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:

  1. express.urlencoded({ extended: true }) is middleware that parses incoming request bodies with URL-encoded form data.
  2. When the form is submitted, the data is available in the req.body object.
  3. 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:

javascript
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:

  1. express.json() is middleware that parses incoming requests with JSON payloads.
  2. The client uses fetch API to send JSON data to the server.
  3. The JSON data is available in the req.body object.
  4. 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:

bash
npm install multer

Here's how to use it:

javascript
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:

  1. multer is middleware that handles multipart/form-data, which is needed for file uploads.
  2. We configure multer with a storage strategy that specifies where files should be saved.
  3. upload.single('profile') processes a single file with the field name 'profile'.
  4. The uploaded file information is available in req.file.
  5. 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:

javascript
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:

  1. We define validation rules for each field (username length, email format, password strength).
  2. When the form is submitted, we check the input against these rules.
  3. If any validation fails, we collect the errors and display them to the user.
  4. 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:

bash
npm install express-validator

Here's how to use it:

javascript
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:

  1. express-validator provides middleware for validating and sanitizing input.
  2. We define validation chains for each field using the body function.
  3. Each validation chain can include multiple validation and sanitization methods.
  4. 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:

bash
npm install csurf cookie-parser
javascript
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:

  1. csurf middleware generates CSRF tokens for each request.
  2. We include the token in the form as a hidden field using req.csrfToken().
  3. When the form is submitted, csurf validates the token.
  4. If the token is invalid (possible CSRF attack), an error is thrown.
  5. 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

  1. Express.js Documentation
  2. Multer Documentation
  3. Express Validator Documentation
  4. CSRF Protection

Exercises

  1. Basic Form Processing: Create a registration form that collects username, email, and password, then displays the submitted data.

  2. Form Validation Challenge: Build a user profile form that validates various fields (name, date of birth, phone number, etc.) with appropriate error messages.

  3. File Upload Portfolio: Create a simple portfolio page where users can upload images with descriptions.

  4. Multi-page Form: Implement a multi-step form that saves data between steps using sessions.

  5. 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! :)