AgentStack
SKILL verified MIT Self-run

Backend Error Handling

skill-kensaurus-cursor-kenji-backend-error-handling · by kensaurus

Implement solid error handling patterns. Use when adding error handling, improving error UX, debugging error flows, standardizing error responses, or when user mentions "error boundary", "try/catch", "error state", "toast notification", "form validation error", or "API error handling".

No reviews yet
0 installs
13 views
0.0% view→install

Install

$ agentstack add skill-kensaurus-cursor-kenji-backend-error-handling

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Backend Error Handling? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Error Handling Skill

full error handling patterns for full-stack applications.

When to Use

  • Adding error handling to new features
  • Improving error user experience
  • Standardizing error responses
  • Debugging error propagation
  • Adding error monitoring

CRITICAL: Check Existing First

Before adding ANY error handling, verify:

  1. Check for existing error types:
rg "type.*Error|interface.*Error" --type ts
rg "ActionResult|ApiError" --type ts
  1. Check for existing error boundaries:
ls -la app/error.tsx app/global-error.tsx
rg "ErrorBoundary" --type tsx
  1. Check for existing error utilities:
rg "formatError|handleError|reportError" --type ts
ls -la src/lib/errors* src/lib/error* 2>/dev/null # @/lib/errors
  1. Check established error response patterns:
rg "success: false|error:" src/features/*/server/ --type ts | head -10

Why: Inconsistent error handling confuses users and complicates debugging. Always follow established patterns.

Error Handling Layers

┌─────────────────────────────────────────┐
│ UI Layer │
│ - Error boundaries │
│ - Form validation errors │
│ - Toast notifications │
├─────────────────────────────────────────┤
│ Application Layer │
│ - Server Action errors │
│ - API route errors │
│ - Business logic errors │
├─────────────────────────────────────────┤
│ Data Layer │
│ - Database errors │
│ - Validation errors (Zod) │
│ - External API errors │
└─────────────────────────────────────────┘

Standard Error Types

// types/errors.ts

// Base error shape
interface AppError {
 code: string // Machine-readable: VALIDATION_ERROR
 message: string // User-friendly message
 details?: unknown // Additional context
}

// Action result pattern
type ActionResult =
 | { success: true; data: T }
 | { success: false; error: AppError }

// Common error codes
const ErrorCode = {
 VALIDATION_ERROR: 'VALIDATION_ERROR',
 NOT_FOUND: 'NOT_FOUND',
 UNAUTHORIZED: 'UNAUTHORIZED',
 FORBIDDEN: 'FORBIDDEN',
 CONFLICT: 'CONFLICT',
 RATE_LIMITED: 'RATE_LIMITED',
 INTERNAL_ERROR: 'INTERNAL_ERROR',
} as const

Server Action Error Handling

// features/users/server/actions.ts
'use server'

import { z } from 'zod'
import { revalidatePath } from 'next/cache'

const CreateUserSchema = z.object({
 email: z.string().email('Invalid email address'),
 name: z.string().min(1, 'Name is required'),
})

export async function createUser(
 prevState: ActionResult,
 formData: FormData
): Promise> {
 try {
 // 1. Validate input
 const validated = CreateUserSchema.safeParse({
 email: formData.get('email'),
 name: formData.get('name'),
 })

 if (!validated.success) {
 return {
 success: false,
 error: {
 code: 'VALIDATION_ERROR',
 message: 'Invalid input',
 details: validated.error.flatten().fieldErrors,
 },
 }
 }

 // 2. Check authorization
 const session = await auth()
 if (!session) {
 return {
 success: false,
 error: {
 code: 'UNAUTHORIZED',
 message: 'Please sign in to continue',
 },
 }
 }

 // 3. Execute business logic
 const user = await db.user.create({
 data: validated.data,
 })

 revalidatePath('/users')

 return { success: true, data: user }

 } catch (error) {
 // 4. Handle known errors
 if (error instanceof Prisma.PrismaClientKnownRequestError) {
 if (error.code === 'P2002') {
 return {
 success: false,
 error: {
 code: 'CONFLICT',
 message: 'A user with this email already exists',
 },
 }
 }
 }

 // 5. Log unknown errors, return generic message
 console.error('createUser error:', error)

 return {
 success: false,
 error: {
 code: 'INTERNAL_ERROR',
 message: 'Something went wrong. Please try again.',
 },
 }
 }
}

Form Error Display (React 19+)

// components/UserForm.tsx
'use client'

import { useActionState } from 'react'
import { useFormStatus } from 'react-dom'
import { createUser } from '@/features/users/server/actions'

// Separate submit button to use useFormStatus
function SubmitButton() {
 const { pending } = useFormStatus()
 return (
 
 {pending ? 'Creating...' : 'Create User'}
 
 )
}

export function UserForm() {
 const [state, action, isPending] = useActionState(createUser, null)

 // Get field errors from validation
 const fieldErrors = state?.success === false
 ? state.error.details as Record
 : {}

 return (
 
 {/* Global error */}
 {state?.success === false && state.error.code !== 'VALIDATION_ERROR' && (
 
 {state.error.message}
 
 )}

 {/* Field with error */}
 
 Email
 
 {fieldErrors.email && (
 
 {fieldErrors.email[0]}
 
 )}
 

 
 
 )
}

React 19 Form Patterns:

  • useActionState - Form state with Server Actions
  • useFormStatus - Pending state in child components
  • useOptimistic - Optimistic UI updates

## React Error Boundaries

```tsx
// app/error.tsx (Next.js page error boundary)
'use client'

import { useEffect } from 'react'

export default function Error({
 error,
 reset,
}: {
 error: Error & { digest?: string }
 reset: () => void
}) {
 useEffect(() => {
 // Log to error reporting service
 console.error('Page error:', error)
 }, [error])

 return (
 
 Something went wrong
 
 We're sorry, but something unexpected happened.
 
 
 Try again
 
 
 )
}

// app/global-error.tsx (root error boundary)
'use client'

export default function GlobalError({
 error,
 reset,
}: {
 error: Error & { digest?: string }
 reset: () => void
}) {
 return (
 
 
 Something went wrong!
 Try again
 
 
 )
}

API Route Error Handling

// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

export async function POST(request: NextRequest) {
 try {
 const body = await request.json()

 const validated = ProductSchema.safeParse(body)
 if (!validated.success) {
 return NextResponse.json(
 {
 error: {
 code: 'VALIDATION_ERROR',
 message: 'Invalid request body',
 details: validated.error.flatten().fieldErrors,
 },
 },
 { status: 400 }
 )
 }

 const product = await db.product.create({ data: validated.data })

 return NextResponse.json({ data: product }, { status: 201 })

 } catch (error) {
 if (error instanceof SyntaxError) {
 return NextResponse.json(
 { error: { code: 'INVALID_JSON', message: 'Invalid JSON body' } },
 { status: 400 }
 )
 }

 console.error('POST /api/products error:', error)

 return NextResponse.json(
 { error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } },
 { status: 500 }
 )
 }
}

TanStack Query Error Handling

// hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'

export function useProducts() {
 return useQuery({
 queryKey: ['products'],
 queryFn: async () => {
 const res = await fetch('/api/products')
 if (!res.ok) {
 const error = await res.json()
 throw new Error(error.error?.message || 'Failed to fetch products')
 }
 return res.json()
 },
 retry: (failureCount, error) => {
 // Don't retry on 4xx errors
 if (error.message.includes('401') || error.message.includes('403')) {
 return false
 }
 return failureCount  {
 const res = await fetch('/api/products', {
 method: 'POST',
 body: JSON.stringify(data),
 })
 if (!res.ok) {
 const error = await res.json()
 throw new Error(error.error?.message || 'Failed to create product')
 }
 return res.json()
 },
 onSuccess: () => {
 queryClient.invalidateQueries({ queryKey: ['products'] })
 toast.success('Product created')
 },
 onError: (error) => {
 toast.error(error.message)
 },
 })
}

Error State UI Components

// components/ErrorState.tsx
import { AlertCircle, RefreshCw } from 'lucide-react'

interface ErrorStateProps {
 title?: string
 message: string
 onRetry?: () => void
}

export function ErrorState({
 title = 'Error',
 message,
 onRetry
}: ErrorStateProps) {
 return (
 
 
 {title}
 {message}
 {onRetry && (
 
 
 Try again
 
 )}
 
 )
}

// Usage with TanStack Query
function ProductList() {
 const { data, error, isLoading, refetch } = useProducts()

 if (error) {
 return (
  refetch()}
 />
 )
 }

 // ...
}

Further reading

  • [Error Logging & Monitoring and more](references/details.md)

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.