createServerFn from @tanstack/react-start, they execute exclusively on the Nitro v3 server — never in the browser bundle — and are called directly from components or route loaders as if they were ordinary async functions. Every server function in the boilerplate follows the same four-step security pattern: validate, authenticate, authorize, execute.
The four-step security pattern
1
Validate input with Zod
Parse the incoming
data argument through a Zod schema before touching anything else. This catches type mismatches and missing fields at the boundary, before any database calls happen.z.parse() throws a ZodError if validation fails. TanStack Start surfaces this as a rejected promise that you can catch on the client.2
Authenticate — read the session from cookies
Call
auth.api.getSession() with the current request headers. The session is stored in an encrypted JWE cookie — no database hit when the cache is warm.3
Authorize — verify org membership and role
Query the
member table to confirm the authenticated user actually belongs to the target organization. Optionally enforce a minimum role.4
Execute business logic
With input validated and identity confirmed, run your database queries, storage operations, or external API calls. Return a plain serializable value — TanStack Start serializes it for the client automatically.
GET and POST examples
Usemethod: 'GET' for read operations and method: 'POST' for writes, deletions, and anything with side effects. This matches HTTP semantics and allows TanStack Query to cache GET results correctly.
Calling server functions from a component
Server functions are called with a{ data: ... } argument. On the client, they behave like any other async function:
Server function files in src/server/
Each file groups functions by domain. Keep new functions in the appropriate existing file, or create a new file for a distinct domain.
Updating the query key registry
After writing a new server function, add a correspondingqueryOptions entry in src/server/query-keys.ts. This keeps cache keys consistent between SSR loaders and client-side useQuery calls:
