Data Validation in the Next.js Prisma kit
Learn how to validate data in the Next.js Prisma kit.
Data Validation is a crucial aspect of building secure applications. In this section, we will look at how to validate data in the Next.js Prisma kit.
Steps to validate data
Learn how to validate data in the Next.js Prisma 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 Next.js Prisma kit uses Zod to validate data. You can use the z object to validate data in your application.
import { z } from "zod";const userSchema = z.object({ id: z.string(), name: z.string(), email: z.string().email(),});// Validate the dataconst validatedData = userSchema.parse(data);Validating payload data to Server Side API
We generally use Server Actions for sending data from a client to a server. The Next.js Prisma kit uses next-safe-action with pre-configured action clients for validation and authentication.
Server Actions: Using the action clients
The authenticatedActionClient is a pre-configured action client that validates input using Zod schemas and ensures the user is authenticated.
'use server';import { authenticatedActionClient } from "@kit/action-middleware";import { z } from "zod";const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.string().email(),});export const createUserAction = authenticatedActionClient .inputSchema(UserSchema) .action(async ({ parsedInput, ctx }) => { // parsedInput is now validated against the UserSchema // ctx.user is the authenticated user // do something with the validated data });When you define an action using authenticatedActionClient with inputSchema, the parsedInput argument is now validated against the schema automatically, without you having to do anything else. In addition, the ctx.user and ctx.session are available to you, which contain the authenticated user and session data.
Organization-scoped actions
For actions that require organization context, use organizationActionClient:
'use server';import { organizationActionClient } from "@kit/action-middleware";import { z } from "zod";const UpdateDocumentSchema = z.object({ documentId: z.string().uuid(), content: z.string(),});export const updateDocumentAction = organizationActionClient .inputSchema(UpdateDocumentSchema) .action(async ({ parsedInput, ctx }) => { // parsedInput is validated // ctx.user is the authenticated user // ctx.organizationId is the current organization // ctx.role is the user's role in the organization // do something with the validated data });Actions without input
For actions that don't require input validation:
'use server';import { authenticatedActionClient } from "@kit/action-middleware";export const getCurrentUserAction = authenticatedActionClient .action(async ({ ctx }) => { // No input needed, just return user data return ctx.user; });Role-protected actions
For actions that require a minimum role level:
'use server';import { authenticatedActionClient, withMinRole } from "@kit/action-middleware";import { z } from "zod";const DeleteMemberSchema = z.object({ memberId: z.string().uuid(),});export const deleteMemberAction = authenticatedActionClient .use(withMinRole('admin')) .inputSchema(DeleteMemberSchema) .action(async ({ parsedInput, ctx }) => { // Only admins and owners can reach this point // parsedInput is validated });Permission-protected actions
For actions that require specific permissions:
'use server';import { authenticatedActionClient, withFeaturePermission } from "@kit/action-middleware";import { z } from "zod";const UpdateBillingSchema = z.object({ planId: z.string(),});export const updateBillingAction = authenticatedActionClient .use(withFeaturePermission({ billing: ['update'] })) .inputSchema(UpdateBillingSchema) .action(async ({ parsedInput, ctx }) => { // Only users with billing:update permission can reach this point });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 { z } from "zod";import { cookies } from "next/headers";const cookieStore = await cookies();const cookie = z.coerce.number() .safeParse(cookieStore.get("my-cookie")?.value);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:
interface PageProps { searchParams: Promise<{ myParam: string }>;}async function Page({ searchParams }: PageProps) { const params = await searchParams; const param = z.coerce.number() .safeParse(params.myParam); if (!param.success) { // handle the error or provide a default value } // 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.