# server-actions > Create Next.js Server Actions with Supabase, Zod validation, and standardized error handling. Use when the user asks to create a server action, API endpoint, or backend mutation. - Author: Ayberk - Repository: ayberkaya/sharktank-dubai - Version: 20260128211937 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/ayberkaya/sharktank-dubai - Web: https://mule.run/skillshub/@@ayberkaya/sharktank-dubai~server-actions:20260128211937 --- --- name: server-actions description: Create Next.js Server Actions with Supabase, Zod validation, and standardized error handling. Use when the user asks to create a server action, API endpoint, or backend mutation. --- # Server Actions ## Structure Every server action must follow this pattern: ```typescript 'use server' import { createClient } from '@/lib/supabase/server' import { z } from 'zod' const inputSchema = z.object({ // Define input validation }) type ActionResponse = { success: boolean message: string data?: T } export async function actionName(input: unknown): Promise> { const parsed = inputSchema.safeParse(input) if (!parsed.success) { return { success: false, message: 'Invalid input' } } try { const supabase = await createClient() // Database operations return { success: true, message: 'Success', data: result } } catch (error) { console.error('actionName error:', error) return { success: false, message: 'An unexpected error occurred' } } } ``` ## Rules 1. **Directive**: Always start with `'use server'` 2. **Validation**: Validate all inputs with Zod before any database operation 3. **Error handling**: Wrap all logic in try-catch 4. **Response format**: Return `{ success: boolean, message: string, data?: T }` 5. **Security**: Never expose raw database errors to the client—log them server-side only ## Example ```typescript 'use server' import { createClient } from '@/lib/supabase/server' import { z } from 'zod' import { revalidatePath } from 'next/cache' const createPitchSchema = z.object({ title: z.string().min(1).max(100), description: z.string().min(10).max(5000), askAmount: z.number().positive(), equityOffered: z.number().min(0).max(100), }) type Pitch = { id: string title: string description: string ask_amount: number equity_offered: number created_at: string } type ActionResponse = { success: boolean message: string data?: T } export async function createPitch(input: unknown): Promise> { const parsed = createPitchSchema.safeParse(input) if (!parsed.success) { return { success: false, message: 'Invalid pitch data' } } try { const supabase = await createClient() const { data: user, error: authError } = await supabase.auth.getUser() if (authError || !user.user) { return { success: false, message: 'Authentication required' } } const { data, error } = await supabase .from('pitches') .insert({ user_id: user.user.id, title: parsed.data.title, description: parsed.data.description, ask_amount: parsed.data.askAmount, equity_offered: parsed.data.equityOffered, }) .select() .single() if (error) { console.error('createPitch DB error:', error) return { success: false, message: 'Failed to create pitch' } } revalidatePath('/pitches') return { success: true, message: 'Pitch created successfully', data } } catch (error) { console.error('createPitch error:', error) return { success: false, message: 'An unexpected error occurred' } } } ``` ## Response Type Helper Add this to a shared types file: ```typescript // @/types/actions.ts export type ActionResponse = { success: boolean message: string data?: T } ``` Then import it in actions: ```typescript import type { ActionResponse } from '@/types/actions' ```