Skip to main content

backend-error-handling

Implement error-handling patterns (boundaries, toasts, API error shape). Use when "error boundary", "error toast", or "standardize API errors". Plan-only observability audit → plan-error-handling. Live Sentry triage → debug-sentry-monitor.

معلومات المصدر

المستودع
kensaurus/cursor-kenji
آخر نشاط في المصدر
١٢ سبتمبر ٢٠٢٦ في ١٤:٢٠
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٩
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
2 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
backend-error-handling
description
Implement error-handling patterns (boundaries, toasts, API error shape). Use when "error boundary", "error toast", or "standardize API errors". Plan-only observability audit → plan-error-handling. Live Sentry triage → debug-sentry-monitor.
license
MIT
# Error Handling Skill **Degree of freedom: MIXED.** Which layer and message `[HIGH freedom]`; existing-type/boundary probes `[LOW freedom — run exactly]`. ## How to reason 1. **Observe** — existing error types, boundaries, `ActionResult` / `ApiError` 2. **Interpret** — missing layer vs inconsistent shape vs swallowed catch 3. **Classify** — reuse-existing / extend-codes / add-boundary / toast-only 4. **Severity** — unhandled 500 on a mutation outranks a missing toast ## Worked example > **Observe:** `createUser` throws Prisma `P2002`; UI shows a blank catch; `ActionResult` already lives in `types/errors.ts`. > **Interpret:** known conflict is unmapped; no field/toast path. > **Classify:** reuse `ActionResult` + `CONFLICT`; toast the message; do not add a second error type. > **Verify:** duplicate email returns `{ success: false, error: { code: 'CONFLICT' } }`; form alert shown. ## Self-critique before reporting - **Reuse shape** — searched `ActionResult` / `ApiError` / `error.tsx` before adding a type - **User-safe** — `INTERNAL_ERROR` is generic; PII is not in the client message - **Layered** — boundary + action result + toast, not toast-only - **Right owner** — plan-only observability audit → `plan-error-handling`; live Sentry triage → `debug-sentry-monitor` 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 ## Check existing first [LOW freedom — run exactly] **Before adding ANY error handling, verify:** 1. **Check for existing error types:** ```bash rg "type.*Error|interface.*Error" --type ts rg "ActionResult|ApiError" --type ts ``` 2. **Check for existing error boundaries:** ```bash ls -la app/error.tsx app/global-error.tsx rg "ErrorBoundary" --type tsx ``` 3. **Check for existing error utilities:** ```bash rg "formatError|handleError|reportError" --type ts ls -la src/lib/errors* src/lib/error* 2>/dev/null # @/lib/errors ``` 4. **Check established error response patterns:** ```bash 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 [HIGH freedom] ``` ┌─────────────────────────────────────────┐ │ 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 [HIGH freedom] ```typescript // 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<T> = | { 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 [HIGH freedom] ```typescript // 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<User>, formData: FormData ): Promise<ActionResult<User>> { 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+) [HIGH freedom] ```tsx // 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 ( <button type="submit" disabled={pending}> {pending ? 'Creating...' : 'Create User'} </button> ) } 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<string, string[]> : {} return ( <form action={action}> {/* Global error */} {state?.success === false && state.error.code !== 'VALIDATION_ERROR' && ( <div role="alert" className="bg-red-50 text-red-700 p-3 rounded-lg mb-4"> {state.error.message} </div> )} {/* Field with error */} <div> <label htmlFor="email">Email</label> <input id="email" name="email" type="email" aria-invalid={!!fieldErrors.email} aria-describedby={fieldErrors.email ? 'email-error' : undefined} className={fieldErrors.email ? 'border-red-500' : ''} /> {fieldErrors.email && ( <p id="email-error" className="text-red-600 text-sm mt-1"> {fieldErrors.email[0]} </p> )} </div> <SubmitButton /> </form> ) } ``` **React 19 Form Patterns:** - `useActionState` - Form state with Server Actions - `useFormStatus` - Pending state in child components - `useOptimistic` - Optimistic UI updates ``` ## React Error Boundaries [HIGH freedom] ```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 ( <div className="flex flex-col items-center justify-center min-h-[400px]"> <h2 className="text-xl font-semibold mb-4">Something went wrong</h2> <p className="text-muted-foreground mb-6"> We're sorry, but something unexpected happened. </p> <button onClick={reset} className="px-4 py-2 bg-primary text-primary-foreground rounded-lg" > Try again </button> </div> ) } // app/global-error.tsx (root error boundary) 'use client' export default function GlobalError({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return ( <html> <body> <h2>Something went wrong!</h2> <button onClick={reset}>Try again</button> </body> </html> ) } ``` ## API Route Error Handling [HIGH freedom] ```typescript // 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 [HIGH freedom] ```tsx // 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 < 3 }, }) } export function useCreateProduct() { const queryClient = useQueryClient() return useMutation({ mutationFn: async (data: ProductInput) => { 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 [HIGH freedom] ```tsx // 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 ( <div className="flex flex-col items-center justify-center py-12 text-center"> <AlertCircle className="h-12 w-12 text-red-500 mb-4" /> <h3 className="font-semibold text-lg mb-2">{title}</h3>
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub