Svelte / Intermediate: Kit routes and server data policy / Lesson 9
Server actions, progressive forms and replay
Use Kit form actions as explicit authenticated mutation boundaries and keep enhancement separate from domain guarantees.
On this page
Lesson goals
- Explain +page.server actions, fail results and redirect control flow.
- Validate FormData and preserve canonical outcomes across repeated submissions.
Every action needs its own current authority
A form action lives in +page.server and handles a mutation request independently of the earlier page render. A hidden ID, bound browser value or disabled submit button is still caller-controlled and does not authorize the requested object. Resolve trusted locals and apply shared domain policy before any effect. Kitโs action machinery does not make the database transaction atomic with external delivery or prevent duplicate intent after a lost response. Keep expected revisions and logical operation identity separate when the workflow requires them. The server result should describe confirmed state or an explicit unresolved outcome rather than merely echoing the clientโs optimistic draft.
Return expected field problems before source work
The default action reads FormData, rejects a nontext or blank title and calls a policy-aware service with the current principal. fail returns a safe expected action failure rather than a raw exception. The source service and session verification are assumed; no form or action runs here. The example uses one default action only. Named and default action conventions have specific composition rules, so do not combine copied snippets without checking the actual route contract. A successful response should expose canonical public fields and must not include passwords, internal notes or raw source error objects.
import {fail} from "@sveltejs/kit";
export const actions={
default:async({request,locals})=>{
const form=await request.formData();
const title=form.get("title");
if(typeof title!=="string" || !title.trim())return fail(400,{code:"INVALID_TITLE"});
const course=await catalog.createFor(locals.principal,{title:title.trim()});
return {course:{id:course.id,title:course.title}};
}
};
The principal and catalog are unexecuted reviewed dependencies. No HTTP request, persistence or action failure was actually produced.
Progressive enhancement changes interaction, not permission
A native method=POST form targeting the appropriate page action can work without client enhancement. use:enhance adds the documented client behavior for Kit form actions; it is not a generic wrapper for every +server endpoint or arbitrary method. Custom enhancement callbacks can change updating, invalidation and reset behavior, so preserve defaults deliberately when needed. Show pending and field feedback accessibly and retain useful draft values after expected rejection. Kit 3 retains the redirect/error control flow introduced in Kit 2; a broad catch can swallow that signal. Keep navigation and revalidation after a confirmed mutation separate from whether the source committed.
Preserve omitted versus explicit clearing locally
The local patch function preserves an omitted summary, clears explicit null and accepts a supplied empty string under this synthetic policy. It copies the source instead of mutating it. The function does not parse FormData or implement an action; an actual adapter must preserve member presence and reject disallowed types before using it. A different domain may forbid null or empty text, so document the chosen rule. Do not use a truthiness shortcut that turns every empty value into omission or convert all missing fields into null and silently erase data the caller intended to preserve.
function patch(source,input){return Object.hasOwn(input,"summary")?{...source,summary:input.summary}:{...source};}
const old={summary:"Original"};
console.assert(patch(old,{}).summary==="Original");
console.assert(patch(old,{summary:null}).summary===null);
console.assert(patch(old,{summary:""}).summary==="");
console.assert(old.summary==="Original");
All four assertions follow the declared policy. No FormData conversion, action or source write is tested.
Test progressive flow and uncertain writes independently
Use separate fixtures for JavaScript-disabled submission, enhanced submission, expected validation failure, denied object, conflict and commit before lost response. A pending button cannot establish durable uniqueness. Reconcile repeated logical operations through the domain protocol and return the original recorded outcome where appropriate. Verify the actual action result signature and custom enhancement handling rather than assuming visible success means every load reran. This course executes only the patch model; native form submission, enhancement, fail serialization, redirect handling, source atomicity and browser recovery remain unexecuted. Real integration needs the selected Kit/Svelte versions, safe principals and an authorized isolated environment.
Common errors and fixes
A hidden field grants access to another course
- Why it happens
- Client-controlled form values were trusted as authority.
- How to fix it
- Resolve current principal and enforce object policy inside the action/service.
- Prevent it next time
- Test direct forged submissions with an ineligible principal.
Custom enhancement drops expected update behavior
- Why it happens
- The callback changed the default action-result lifecycle without preserving it.
- How to fix it
- Follow the documented update/apply contract deliberately.
- Prevent it next time
- Test validation, success, reset and redirect paths with enhancement enabled.
A response failure triggers a second committed effect
- Why it happens
- Missing confirmation was interpreted as definite nonexecution.
- How to fix it
- Reconcile through stable logical operation identity before replay.
- Prevent it next time
- Include commit-before-response-loss fixtures.
Check your understanding
1. Does use:enhance authorize the action or guarantee one write?
Show worked solution
No. It changes supported form interaction and result handling in the browser. Callers can submit directly or retry after network loss. The server must validate input, resolve current authority and use a domain replay/transaction protocol. Test enhanced and unenhanced flows without treating either as an authorization mechanism.
2. Why can fail be preferable to a raw exception for an invalid title?
Show worked solution
An invalid title is an expected input outcome that can carry safe field feedback. A raw source exception may disclose private details and obscure the distinction from an outage. Return the documented action failure shape, preserve useful draft state and still assert that no protected write happened before rejection.
Key takeaways
- Actions are independent server mutation boundaries.
- Enhancement, canonical outcomes and durable replay need separate contracts.