For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: text/markdown.
Protected calls
Validate server function inputs, resolve the caller from its session, enforce record ownership, and guard raw HTTP handlers on the Worker.
Authorization belongs next to the data access. Require a session, validate the input, and constrain the database operation to that session's user. None of those checks can be replaced by a hidden button or a route redirect.
Record ownership
This example expects the new notes table from schema changes. The schema stays client-safe:
import * as z from "zod";
export const noteIdSchema = z.object({ id: z.string().uuid() });The server helper checks both the record ID and its owner:
import { and, eq } from "drizzle-orm";
import { db } from "@/db";
import { note } from "@/db/schema";
export async function findNote(userId: string, id: string) {
const [record] = await db
.select()
.from(note)
.where(and(eq(note.id, id), eq(note.userId, userId)));
return record ?? null;
}Wire the helper through a protected function:
import { createServerFn } from "@tanstack/react-start";
import { enforceAuth } from "@/lib/auth/middleware";
import { noteIdSchema } from "./notes.schema";
import { findNote } from "./notes.server";
export const getNote = createServerFn({ method: "GET" })
.middleware([enforceAuth])
.validator(noteIdSchema)
.handler(({ data, context }) => findNote(context.user.id, data.id));The client calls getNote({ data: { id } }). It cannot choose another owner because no userId is accepted as input. Apply the same owner predicate to updates and deletes, not only reads.
The snippets use .validator, matching the installed kit's function wrappers. When upgrading TanStack Start, check the installed guidance for changes to its input validation API.
HTTP endpoints
For an endpoint that returns raw HTTP, attach the shared request middleware under server:
import { createFileRoute } from "@tanstack/react-router";
import { enforceAuth } from "@/lib/auth/middleware";
export const Route = createFileRoute("/api/current-user")({
server: {
middleware: [enforceAuth],
handlers: {
GET: ({ context }) => Response.json({ id: context.user.id }),
},
},
});This is a new example route, not a shipped endpoint. Create its file and let route generation register it. The existing storage and chat routes use this middleware pattern.
Access policies
enforceAuth accepts anonymous users with valid sessions. If your action requires a registered or verified account, check context.user.isAnonymous and context.user.emailVerified on the server. Use plan middleware for paid access. Captcha proves a bot challenge was checked, not who owns a record.
Return only fields the caller needs. Treat serializable output as browser-visible even when it comes from a .server.ts helper.
How is this guide?
Last updated on
Overview
Type-safe server functions with TanStack Start, plus HTTP endpoints for webhooks, streaming, and files, all running in your Edge Worker.
Data fetching
Page loading, TanStack Query caching, mutations, and UI states, with a clear boundary between customer data in the browser and server operations.