Actions and forms

Validate FormData

Convert unknown form values into a trusted shape and return field-level errors instead of assuming TypeScript checked runtime input.

formData.get() can return a string, a File, or null. Type annotations do not validate any of those runtime values.

Normalize the input, validate required fields and bounds, and return a serializable result for expected errors. A schema library helps on a larger form, but the important boundary is the same: do not perform the write until server-side validation succeeds.

Authentication answers who made the request. Validation answers whether the submitted shape is acceptable. Authorization answers whether that person may perform this operation. Run all three before the write. Client constraints improve feedback but can be removed or bypassed.

const rawTitle = formData.get('title')
if (typeof rawTitle !== 'string' || rawTitle.trim().length < 3) {
  return { errors: { title: 'Use at least 3 characters' } }
}
const title = rawTitle.trim()

Submit "ab" and the action should return { errors: { title: 'Use at least 3 characters' } } with no database write. Submit a File under the title field and the same guard should catch it because typeof rawTitle !== 'string'.

Do not echo raw values or internal exception messages in the returned state. Preserve safe user-entered values separately when the form should repopulate. Use a general message for an unexpected storage failure.

Build FormData in a small test or curl request. Reject missing, File, whitespace-only, too-short, and very long titles. Each expected problem should return a field message and perform no write.

If you skip server validation because TypeScript looks happy, a crafted POST can still insert garbage.

Whitelisting allowed fields helps too. If the form only sends title, reject unexpected keys like role=admin before they reach persistence code.

Return the same error shape for every validation failure so the client can render messages without a switch on ten different string formats.

Lesson completed