Input Processing and Validation

By Syncfusion®, Inc. · Last updated

Reading JSON, form data and headers, converting values, the Post-Redirect-Get pattern, validation rules, validation middleware, 400 versus 422, sanitization with trimming and allowlists, and validation libraries like Zod.

What you will learn

  • Read JSON, form data and headers safely
  • Convert strings to numbers, booleans and defaults
  • Use the Post-Redirect-Get pattern
  • Write reusable validation middleware
  • Sanitize input with trimming, normalising and allowlists

Input Processing Introduction

Input = any data a client sends: params, query, body and headers.

  • Input can be missing, wrong, too big or harmful.
  • Validation = checking that input follows your rules. Bad input is rejected.
  • Sanitization = cleaning input, such as trimming spaces. It changes the value.
  • Validate on the server, even if the web page already checks.
  • Anyone can skip your web page and send requests with curl.

Simple lesson: Never trust input. Check it, clean it, then use it.

A Real-World Example

The canteen accepts written order slips.

  1. A slip says "Dosa x 500". Nobody orders 500 dosas.
  2. Another slip has no name on it.
  3. Another has the name written with extra spaces and capital letters.
  4. The counter staff check each slip before the kitchen sees it.

Rejecting the first two is validation. Tidying the name is sanitization.

In this lesson, you will learn:

  1. How to read JSON, form data and headers.
  2. How to convert strings into numbers, booleans and defaults.
  3. The Post-Redirect-Get pattern for forms.
  4. How to write validation rules and validation middleware.
  5. When to answer 400 and when to answer 422.
  6. How to sanitize input, and the limits of sanitization.

Reading JSON Input

  • req.body holds the parsed JSON. See Body Parsers and Third-Party Middleware.
  • Nested objects: req.body.student.name.
  • Arrays: req.body.items may be a list.
  • Missing values: a field may be undefined. Use ?. and ??.
  • Unexpected values: a number may arrive as a string, or a list as a single value.
  • Malformed JSON: the parser rejects it with 400 before your route runs.
  • ?. stops a crash when student is missing.
  • [].concat(...) turns one item, many items or nothing into a list.

Simple lesson: Expect missing and odd values. Read them carefully.

Reading Form Data

An HTML form sends each field as name=value. Different fields behave differently.

  • Text input: a string. Empty boxes send "".
  • Checkbox: sends its value only when ticked. Unticked boxes send nothing.
  • Several checkboxes with one name: an array when two or more are ticked.
  • Radio buttons: one value, the chosen one.
  • Select: one value. A multiple select works like checkboxes.
  • action = the URL the form posts to. method = post for changes.
<form action="/feedback" method="post">
  <input name="name">
  <input type="checkbox" name="likes" value="idli"> Idli
  <input type="checkbox" name="likes" value="vada"> Vada
  <input type="radio" name="rating" value="5"> 5 stars
  <select name="meal"><option>Breakfast</option><option>Lunch</option></select>
  <input type="checkbox" name="subscribe" value="yes"> Send me the menu
  <button>Send</button>
</form>
  • In the second request, likes is one string, not a list. [].concat fixes it.
  • subscribe is missing, so it becomes false.

Simple lesson: Forms send only strings, and unticked boxes send nothing.

Reading Header Input

  • Content-Type: what format the body is in. req.is("json") checks it.
  • Accept: what format the client wants back. req.accepts("json") checks it.
  • Authorization: login details, like Bearer abc123.
  • Custom headers: names your app chooses, like X-Student-Id.
  • Read any header with req.get(name). Missing headers are undefined.

Simple lesson: Headers are input too. Split, check and look up before trusting.

Converting Values

Most input arrives as text. Convert it on purpose.

  • Strings: String(value ?? "").trim().
  • Numbers: Number(value), then check Number.isFinite or Number.isInteger.
  • Booleans: compare text: value === "true" or value === "on".
  • Defaults: use ?? so 0 and "" are not replaced by mistake.
  • Empty values: decide if "" means "not given" or "blank on purpose".

Easy way to remember: ?? keeps 0 and "". || replaces them. Use ?? for defaults.

Simple lesson: Convert first, then check the converted value.

The Post-Redirect-Get Pattern

After a form post, refreshing the page can send the form again. Two orders!

  • POST: the browser sends the form.
  • Redirect: the server saves it and answers 303 See Other with a new URL.
  • GET: the browser loads that URL. Refresh now only repeats the GET.
  • If the form has errors, do not redirect. Show the form again with the user's values and the error messages.
  • A good order gets 303 with a Location header. A browser follows it.
  • A bad order gets the form again, with the quantity still filled in.

Simple lesson: Redirect after a successful POST. Re-show the form after a failed one.

Validation Rules

Common rules for each field:

  • Required: the value must be present and not empty.
  • String: a type check plus a length range, like 2 to 50 characters.
  • Number: a real number in a range, like a quantity from 1 to 10.
  • Boolean: exactly true or false.
  • Date: a real date, maybe not in the past.
  • Enum: one of a fixed list, like ["breakfast", "lunch"].
  • Array and nested object: check the shape, then each item.
  • Cross-field: two fields together, like "pickup time must be after order time".

Validate every place input comes from: params, query, body and headers.

Simple lesson: Write each rule down. If it is not checked, it is not true.

Validation Middleware

Keep validation out of your route handlers.

  • Write a reusable function that checks the body against rules.
  • Wrap it in middleware that runs before the route.
  • Valid: call next(). The route can trust req.body.
  • Invalid: answer with a list of errors.
  • 400 Bad Request: the request is broken, such as malformed JSON or the wrong type.
  • 422 Unprocessable Content: the format is fine, but the values break your rules.
  • The second request gets 422 with all three problems at once.
  • The third is an array, not an object, so it gets 400.

Easy way to remember: 400 = "I can't read this". 422 = "I read it, and it's wrong".

Simple lesson: Validate in middleware, list every error, and pick 400 or 422 correctly.

Sanitization

Sanitization cleans input before you store or use it.

  • Trim: remove spaces at the start and end.
  • Normalise: make one form, such as lowercase emails.
  • Allowlist fields: copy only the fields you expect.
  • Remove unexpected fields: never pass req.body straight to the database.
  • Context matters: escape for HTML in templates, use query parameters for SQL.
  • isAdmin is dropped. A student cannot make themselves an admin.
  • name and email are tidy.

Sanitizing is not enough on its own. Still validate after cleaning, and still escape output. A trimmed value can still be wrong or harmful.

Simple lesson: Clean what you keep, and keep only what you expect.

Validation Libraries

Hand-written rules get long. Libraries describe rules as a schema.

  • Schema = one description of the shape and rules of your data.
  • Zod: schemas in plain JavaScript. Also gives you clean, converted data.
  • express-validator: a chain of checks written as middleware.
  • Joi: an older, mature schema library.
  • Pick one per project. Zod is a popular choice for new projects.
import { z } from "zod";

const Order = z.object({
  item: z.string().trim().min(1),
  qty: z.coerce.number().int().min(1).max(10),
  meal: z.enum(["breakfast", "lunch"]),
});

app.post("/orders", (req, res) => {
  const result = Order.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({ errors: result.error.issues });
  }
  res.status(201).json(result.data); // trimmed and converted
});

Simple lesson: A schema library replaces many if checks with one clear description.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Only checking in the browserThe page already validatedAnyone can skip the pageBad data reaches the server
if (req.body.qty)It checks the number0 and "0" behave oddlyWrong orders pass or fail
Saving req.body directlyThe client sends only expected fieldsClients can add any fieldUsers set isAdmin: true
Showing the first error onlyOne message is enoughList every errorUsers fix one, then hit the next
200 for invalid inputThe body explains itUse 400 or 422Clients think it worked
No redirect after a form POSTRendering is fineUse Post-Redirect-GetRefresh creates duplicate orders

Check Yourself

Exercises

What to Remember

  • All input is untrusted: params, query, body and headers.
  • Forms send strings, and unticked checkboxes send nothing.
  • Convert values on purpose, then check them.
  • Validate in middleware. Answer 400 for unreadable input, 422 for broken rules.
  • Sanitize with trim, normalise and allowlist, and still validate after.
  • Use Post-Redirect-Get after successful form posts.

Next: Cookies and Sessions. You will remember who a user is between requests.