Skip to content

Form Submissions in MPAs — POST/GET Patterns, Validation, and Server Handling

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Form Submissions in MPAs. We cover key concepts, practical examples, and best practices to help you master this topic.

MPA form submissions use POST and GET methods with server-side validation, error handling, CSRF protection, and the Post-Redirect-Get pattern for reliable and secure data processing.

What You'll Learn

By the end of this tutorial, you will understand how form submission works in MPAs, the difference between GET and POST methods, server-side validation with error feedback, the PRG (Post-Redirect-Get) pattern, CSRF protection for forms, and progressive enhancement with JavaScript.

Why It Matters

Forms are how users interact with MPAs — logging in, searching, submitting comments, placing orders. Poorly handled forms cause duplicate submissions, lost data, and security vulnerabilities. Proper form handling is essential for data integrity and user trust.

Real-World Use

An e-commerce MPA processed 10,000 orders per day. Without the PRG pattern, users who refreshed the order confirmation page were charged twice. After implementing PRG, duplicate orders dropped to zero. The fix changed one line of code — replacing a render with a redirect.

Form Submission Flow (PRG Pattern)
    ┌──────────┐        ┌──────────┐        ┌──────────┐
    │  Browser │        │  Server  │        │ Database │
    └────┬─────┘        └────┬─────┘        └────┬─────┘
         │                   │                    │
         │  POST /submit     │                    │
         │  (form data)      │                    │
         │──────────────────>│                    │
         │                   │  Validate data     │
         │                   │                    │
         │                   │  ┌─ Valid? ──────┐ │
         │                   │  │    │          │ │
         │                   │  │    ├─ Yes ────┼─│────> Save data
         │                   │  │    │          │ │      │
         │                   │  │    │          │ │<───── OK
         │                   │  │    │          │ │      │
         │                   │  │    │  Redirect │ │      │
         │  302 Redirect     │  │    │  303     │ │      │
         │  /success         │  │    │          │ │      │
         │<──────────────────│  │    │          │ │      │
         │                   │  │    │          │ │      │
         │  GET /success     │  │    ├─ No ─────┼─│      │
         │──────────────────>│  │    │          │ │      │
         │                   │  │    │  Re-render       │
         │  HTML page (200)  │  │    │  with errors      │
         │<──────────────────│  │    │                   │
         │                   │  └────┴───────────────────┘

Think of form submission like mailing a letter. Filling out the form is writing the letter. POST is sending it through the mail system. Server validation is the postal service checking the address. PRG is like getting a confirmation receipt — refreshing the page just shows the receipt again instead of sending the letter twice.

Server-Side Form Validation

// Express.js form validation
const express = require('express');
const router = express.Router();

router.get('/register', (req, res) => {
    res.render('register', {
        title: 'Create Account',
        formData: {},
        errors: {}
    });
});

router.post('/register', async (req, res) => {
    const { username, email, password, confirmPassword } = req.body;
    const errors = {};

    // Server-side validation
    if (!username || username.trim().length < 3) {
        errors.username = 'Username must be at least 3 characters';
    }
    if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        errors.email = 'Please enter a valid email address';
    }
    if (!password || password.length < 8) {
        errors.password = 'Password must be at least 8 characters';
    }
    if (password !== confirmPassword) {
        errors.confirmPassword = 'Passwords do not match';
    }

    // Check for existing user
    if (!errors.email) {
        const existing = await db.users.findByEmail(email);
        if (existing) {
            errors.email = 'An account with this email already exists';
        }
    }

    if (Object.keys(errors).length > 0) {
        return res.status(422).render('register', {
            title: 'Create Account',
            formData: { username, email },
            errors
        });
    }

    // Create user, then redirect (PRG)
    const user = await db.users.create({ username, email, password });
    req.session.userId = user.id;

    // Flash message for success feedback
    req.flash('success', 'Account created successfully!');
    res.redirect(303, '/dashboard');
});

// View template shows errors:
// <% if (errors.username) { %>
//   <span class="error"><%= errors.username %></span>
// <% } %>
// <input name="username" value="<%= formData.username %>">

CSRF Protection

// CSRF token middleware
const csrf = require('csrf');
const tokens = new csrf();

// Generate token on form render
app.use((req, res, next) => {
    if (req.session) {
        if (!req.session.csrfSecret) {
            req.session.csrfSecret = tokens.secretSync();
        }
        res.locals.csrfToken = tokens.create(req.session.csrfSecret);
    }
    next();
});

// Validate CSRF on POST requests
app.post('/contact', (req, res, next) => {
    if (!tokens.verify(req.session.csrfSecret, req.body._csrf)) {
        return res.status(403).render('error', {
            title: 'Invalid Form',
            message: 'Form submission expired. Please try again.'
        });
    }
    next();
});

// In the form template:
// <form method="POST" action="/contact">
//     <input type="hidden" name="_csrf" value="<%= csrfToken %>">
//     <input name="email" type="email" required>
//     <button type="submit">Submit</button>
// </form>

Progressive Enhancement with Fetch

// Enhance form with JavaScript (optional)
document.addEventListener('DOMContentLoaded', () => {
    const forms = document.querySelectorAll('form[data-enhanced]');

    forms.forEach(form => {
        form.addEventListener('submit', async (e) => {
            // Only enhance if JavaScript is available
            e.preventDefault();

            const formData = new FormData(form);
            const submitButton = form.querySelector('[type="submit"]');
            submitButton.disabled = true;
            submitButton.textContent = 'Saving...';

            try {
                const response = await fetch(form.action, {
                    method: form.method,
                    body: formData,
                    headers: {
                        'Accept': 'application/json'
                    }
                });

                const result = await response.json();

                if (response.ok) {
                    // Success — show message without page reload
                    showSuccess(form, result.message);
                    form.reset();
                } else {
                    // Validation errors — show inline
                    showErrors(form, result.errors);
                }
            } catch (error) {
                showErrors(form, { _general: 'Network error. Please try again.' });
            } finally {
                submitButton.disabled = false;
                submitButton.textContent = 'Submit';
            }
        });
    });
});

Common Mistakes

  1. Missing CSRF protection. Every state-changing form (POST, PUT, DELETE) must include CSRF protection. Without it, attackers can forge requests on behalf of authenticated users.
  2. Client-side validation only. Client-side validation is for UX, not security. Attackers can bypass it. Always validate on the server.
  3. Not using PRG pattern. Without Post-Redirect-Get, refreshing the page after form submission resubmits the data, causing duplicate orders, comments, or database entries.
  4. Showing raw validation errors. Displaying error messages directly from libraries can leak information. Use user-friendly messages that do not reveal internal details.
  5. Not preserving form data on validation failure. When validation fails, re-render the form with the user's submitted data. Forcing users to retype everything is frustrating.

Practice Questions

  1. What is the Post-Redirect-Get pattern and why is it important?
  2. How do you implement server-side form validation with error feedback?
  3. What is CSRF and how do you protect forms from CSRF Attacks?
  4. How do you progressively enhance a form with JavaScript while keeping it functional without JS?
  5. Why should you validate on the server even if you validate on the client?

Challenge: Build a contact form MPA with the following features: server-side validation (name, email, message with minimum lengths), CSRF protection with hidden token, PRG pattern (redirect to thank-you page on success), preserved form data on validation failure, flash messages for success feedback, and progressive enhancement with fetch that submits the form as JSON.

FAQ

What is the difference between 302 and 303 redirects for PRG?

302 is the standard redirect. 303 (See Other) explicitly tells the browser to change POST to GET for the redirect. Use 303 for PRG to match the HTTP specification.

How do I handle file uploads in MPA forms?

Set enctype='multipart/form-data' on the form. The server receives files in req.files (multer middleware for Express). Process and store files, then redirect to a confirmation page.

Should I use GET or POST for search forms?

Use GET for search forms. GET requests are bookmarkable, shareable, and can be cached. The search query goes in the URL query string: /search?q=keyword.

How do I prevent double form submission?

Disable the submit button on first click using JavaScript. The PRG pattern also ensures that refreshing shows a GET page rather than resubmitting POST data.

Can I use AJAX with MPA forms?

Yes. Progressive enhancement means forms work without JavaScript and are enhanced with AJAX when JS is available. This is the best of both worlds.

Mini Project

Build a registration form MPA with: username, email, password, and confirm password fields. Implement server-side validation with error messages shown next to each field, CSRF protection with hidden input, PRG pattern with redirect to a welcome page, flash messages for success, and progressive enhancement that submits via fetch and shows errors inline without page reload.

What's Next

You understand form submissions. Now learn about Session Management in MPAs to handle user authentication and state across requests.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro