Data Validation in the Tanstack Start Supabase Turbo kit
Learn how to validate data in the Tanstack Start Supabase Turbo kit.
Data Validation is a crucial aspect of building secure applications. In this section, we will look at how to validate data in the Tanstack Start Supabase Turbo kit.
Steps to validate data
Learn how to validate data in the Tanstack Start Supabase Turbo kit.
What are we validating?
A general rule, is that all client-side provided data should be validated/sanitized. This includes:
- URL parameters
- Search params
- Form data
- Cookies
- Any data provided by the user
Using Zod to validate data
The Tanstack Start Supabase Turbo kit uses Zod to validate data. You can use the z object to validate data in your application.
import * as z from "zod";const userSchema = z.object({ id: z.string(), name: z.string(), email: z.email(),});// Validate the dataconst validatedData = userSchema.parse(data);Validating payload data to Server Side API
We generally use two ways for sending data from a client to a server:
- Server functions
- API server routes
Let's look at how we can validate data for both of these cases.
Server functions: Using createServerFn
Server functions created with createServerFn accept a .validator() that runs a Zod schema over the input, so the data argument in the handler is already validated. Composing authFunctionMiddleware adds the authenticated user to context.
import { createServerFn } from "@tanstack/react-start";import { authFunctionMiddleware } from "@kit/function-middleware/functions";import * as z from "zod";const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.email(),});export const createUserFunction = createServerFn({ method: "POST" }) .middleware(authFunctionMiddleware) .validator(UserSchema) .handler(async ({ data, context: { user } }) => { // data is now validated against the UserSchema // do something with the validated data });When you define a server function with a .validator(UserSchema), the data argument is validated against the schema automatically. The context.user provides the authenticated user.
API server routes: validating the request body
TanStack Start API routes are defined with createFileRoute under apps/web/src/routes/api/.... Validate the request body with your Zod schema inside the handler.
import { createFileRoute } from "@tanstack/react-router";import * as z from "zod";const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.email(),});export const Route = createFileRoute("/api/users")({ server: { handlers: { POST: async ({ request }) => { const body = UserSchema.parse(await request.json()); // body is now validated against the UserSchema // do something with the validated data return new Response(null, { status: 201 }); }, }, },});Just like the .validator() on a server function, parsing the request body with your schema guarantees the data is validated before you use it. Use getSupabaseServerClient() inside the handler if you need the authenticated user.
Validating Cookies
Whenever you use a cookie in your application, you should validate the cookie data.
Let's assume you receive a cookie with the name my-cookie, and you expect it to be a number. You can validate the cookie data as follows:
import * as z from "zod";import { getCookie } from "@tanstack/react-start/server";const cookie = z.coerce.number() .safeParse(getCookie("my-cookie"));if (!cookie.success) { // handle the error or provide a default value}Validating URL parameters
Whenever you receive a URL parameter, you should validate the parameter data. Let's assume you receive a URL parameter with the name my-param, and you expect it to be a number. You can validate the parameter data as follows:
import { createFileRoute } from "@tanstack/react-router";import * as z from "zod";const SearchSchema = z.object({ myParam: z.coerce.number(),});export const Route = createFileRoute("/my-page")({ // validateSearch parses and coerces the raw search params, rejecting // invalid input before the component or loader runs. validateSearch: SearchSchema, component: Page,});function Page() { // Fully typed and validated search params. const { myParam } = Route.useSearch(); // render the page with the validated data}Going forward, remember to validate all data that you receive from the client, and never trust anything the client provides you with. Always have a default value ready to handle invalid data, which can prevent potential security issues or bugs in your application.