Skip to main content

API Error Handling

BuildAppsWith implements comprehensive error handling across all API endpoints using standardized error codes, consistent response formats, and type-safe error interfaces.

Error Response Structure

All API errors follow the StandardApiResponse pattern with a structured error object:
interface ApiError {
  code: string;
  message: string;
  details?: Record<string, any>;
  statusCode?: number;
}

interface ErrorResponse {
  success: false;
  error: ApiError;
  data?: undefined;
}

Standard Error Codes

Client Errors (4xx)

bad_request
400
Request is malformed, missing required parameters, or contains invalid data
{
  "success": false,
  "error": {
    "code": "bad_request",
    "message": "Builder ID is required",
    "statusCode": 400,
    "details": {
      "parameter": "id",
      "expected": "string"
    }
  }
}
unauthorized
401
Authentication is required but not provided or invalid
{
  "success": false,
  "error": {
    "code": "unauthorized", 
    "message": "Authentication required",
    "statusCode": 401
  }
}
forbidden
403
User is authenticated but lacks permission for the requested resource
{
  "success": false,
  "error": {
    "code": "forbidden",
    "message": "Insufficient permissions to edit this profile",
    "statusCode": 403,
    "details": {
      "requiredRole": "BUILDER",
      "userRole": "CLIENT"
    }
  }
}
not_found
404
The requested resource does not exist
{
  "success": false,
  "error": {
    "code": "not_found",
    "message": "Builder profile not found",
    "statusCode": 404,
    "details": {
      "resourceType": "BuilderProfile",
      "resourceId": "invalid_id"
    }
  }
}
validation_error
422
Request data fails validation rules
{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "Input validation failed",
    "statusCode": 422,
    "details": {
      "errors": [
        {
          "field": "email",
          "message": "Invalid email format"
        },
        {
          "field": "hourlyRate", 
          "message": "Must be a positive number"
        }
      ]
    }
  }
}
conflict
409
Request conflicts with current resource state
{
  "success": false,
  "error": {
    "code": "conflict",
    "message": "Profile slug already exists",
    "statusCode": 409,
    "details": {
      "conflictingField": "slug",
      "conflictingValue": "existing-slug"
    }
  }
}
rate_limited
429
Rate limit exceeded for the requesting client
{
  "success": false,
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded",
    "statusCode": 429,
    "details": {
      "limit": 100,
      "windowMs": 900000,
      "retryAfter": 60
    }
  }
}

Server Errors (5xx)

internal_error
500
Unexpected server error occurred
{
  "success": false,
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred",
    "statusCode": 500
  }
}
service_unavailable
503
Service temporarily unavailable
{
  "success": false,
  "error": {
    "code": "service_unavailable",
    "message": "Database connection unavailable",
    "statusCode": 503,
    "details": {
      "service": "database",
      "estimatedRecovery": "2024-01-15T10:30:00Z"
    }
  }
}

Error Handling in API Routes

Basic Error Response

import { toStandardResponse, ApiErrorCode } from '@/lib/types/api-types';

export async function GET(request: NextRequest) {
  try {
    // API logic
  } catch (error) {
    console.error('API Error:', error);
    
    return NextResponse.json(
      toStandardResponse(null, {
        error: {
          code: ApiErrorCode.INTERNAL_ERROR,
          message: 'Failed to process request',
          statusCode: 500
        }
      }),
      { status: 500 }
    );
  }
}

Validation Error Response

import { z } from 'zod';

const profileSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email format'),
  hourlyRate: z.number().positive('Hourly rate must be positive')
});

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const validationResult = profileSchema.safeParse(body);
    
    if (!validationResult.success) {
      return NextResponse.json(
        toStandardResponse(null, {
          error: {
            code: ApiErrorCode.VALIDATION_ERROR,
            message: 'Input validation failed',
            statusCode: 422,
            details: {
              errors: validationResult.error.errors.map(err => ({
                field: err.path.join('.'),
                message: err.message
              }))
            }
          }
        }),
        { status: 422 }
      );
    }
    
    // Continue with validated data
  } catch (error) {
    // Handle unexpected errors
  }
}

Not Found Error Response

export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
  try {
    const { id } = params;
    const profile = await db.builderProfile.findUnique({ where: { id } });
    
    if (!profile) {
      return NextResponse.json(
        toStandardResponse(null, {
          error: {
            code: ApiErrorCode.NOT_FOUND,
            message: 'Builder profile not found',
            statusCode: 404,
            details: {
              resourceType: 'BuilderProfile',
              resourceId: id
            }
          }
        }),
        { status: 404 }
      );
    }
    
    return NextResponse.json(toStandardResponse(profile));
  } catch (error) {
    // Handle unexpected errors
  }
}

Client-Side Error Handling

API Client Functions

import type { StandardApiResponse } from '@/lib/types/api-types';

export async function getBuilderProfile(id: string): Promise<StandardApiResponse<BuilderProfileData>> {
  try {
    const response = await fetch(`/api/profiles/builder/${id}`);
    
    // Always parse JSON first
    const apiResponse: StandardApiResponse<BuilderProfileData> = await response.json();
    
    // Return the parsed response (success or error)
    return apiResponse;
  } catch (error) {
    // Network or parsing errors
    return {
      success: false,
      error: {
        code: 'network_error',
        message: error instanceof Error ? error.message : 'Network request failed',
        statusCode: 0
      }
    };
  }
}

React Component Error Handling

import { getBuilderProfile } from '@/lib/profile/api';

export function BuilderProfile({ id }: { id: string }) {
  const [profile, setProfile] = useState<BuilderProfileData | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function loadProfile() {
      try {
        const response = await getBuilderProfile(id);
        
        if (!response.success) {
          setError(response.error?.message || 'Failed to load profile');
          return;
        }
        
        setProfile(response.data);
      } catch (error) {
        setError('An unexpected error occurred');
      } finally {
        setLoading(false);
      }
    }
    
    loadProfile();
  }, [id]);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div className="error">Error: {error}</div>;
  if (!profile) return <div>Profile not found</div>;
  
  return (
    <div>
      <h1>{profile.displayName}</h1>
      <p>{profile.bio}</p>
    </div>
  );
}

Global Error Handling Hook

import { toast } from 'sonner';

export function useApiError() {
  const handleApiError = useCallback((response: StandardApiResponse<any>) => {
    if (response.success) return;
    
    const error = response.error;
    if (!error) return;
    
    switch (error.code) {
      case 'unauthorized':
        toast.error('Please sign in to continue');
        // Redirect to sign in
        break;
        
      case 'forbidden':
        toast.error('You do not have permission for this action');
        break;
        
      case 'not_found':
        toast.error('The requested resource was not found');
        break;
        
      case 'validation_error':
        if (error.details?.errors) {
          error.details.errors.forEach((err: any) => {
            toast.error(`${err.field}: ${err.message}`);
          });
        } else {
          toast.error(error.message);
        }
        break;
        
      case 'rate_limited':
        toast.error('Too many requests. Please try again later');
        break;
        
      default:
        toast.error(error.message || 'An unexpected error occurred');
    }
  }, []);
  
  return { handleApiError };
}

Error Monitoring and Logging

Server-Side Error Logging

import { captureException } from '@sentry/nextjs';
import { logger } from '@/lib/logger';

export async function handleApiError(error: unknown, context: Record<string, any>) {
  // Structure error information
  const errorInfo = {
    message: error instanceof Error ? error.message : String(error),
    stack: error instanceof Error ? error.stack : undefined,
    context
  };
  
  // Log to application logger
  logger.error('API Error:', errorInfo);
  
  // Report to error monitoring service  
  captureException(error, {
    tags: {
      component: 'api',
      endpoint: context.endpoint
    },
    extra: context
  });
}

Client-Side Error Reporting

import { captureException } from '@sentry/react';

export function reportClientError(error: ApiError, context?: Record<string, any>) {
  // Log to console in development
  if (process.env.NODE_ENV === 'development') {
    console.error('API Error:', error, context);
  }
  
  // Report to monitoring service in production
  if (process.env.NODE_ENV === 'production') {
    captureException(new Error(error.message), {
      tags: {
        errorCode: error.code,
        statusCode: error.statusCode
      },
      extra: {
        apiError: error,
        context
      }
    });
  }
}

Testing Error Responses

Unit Testing API Routes

import { GET } from '@/app/api/profiles/builder/[id]/route.ts';

describe('Builder Profile API', () => {
  it('should return not_found error for invalid ID', async () => {
    const request = new NextRequest('http://localhost/api/profiles/builder/invalid');
    const response = await GET(request, { params: { id: 'invalid' } });
    
    expect(response.status).toBe(404);
    
    const data = await response.json();
    expect(data.success).toBe(false);
    expect(data.error.code).toBe('not_found');
    expect(data.error.message).toBe('Builder profile not found');
  });
  
  it('should return validation_error for missing ID', async () => {
    const request = new NextRequest('http://localhost/api/profiles/builder/');
    const response = await GET(request, { params: { id: '' } });
    
    expect(response.status).toBe(400);
    
    const data = await response.json();
    expect(data.success).toBe(false);
    expect(data.error.code).toBe('bad_request');
  });
});

Integration Testing

describe('API Error Handling Integration', () => {
  it('should handle database connection errors', async () => {
    // Mock database failure
    jest.spyOn(db.builderProfile, 'findUnique').mockRejectedValue(
      new Error('Database connection failed')
    );
    
    const response = await fetch('/api/profiles/builder/123');
    const data = await response.json();
    
    expect(response.status).toBe(500);
    expect(data.success).toBe(false);
    expect(data.error.code).toBe('internal_error');
  });
});

Best Practices

1. Consistent Error Responses

  • Always use toStandardResponse() for error responses
  • Include appropriate HTTP status codes
  • Provide meaningful error messages for users

2. Security Considerations

  • Don’t expose sensitive information in error messages
  • Sanitize error details in production environments
  • Use generic messages for internal server errors

3. Error Recovery

  • Implement retry logic for transient failures
  • Provide clear actions users can take to resolve errors
  • Use fallback data when appropriate

4. Monitoring and Alerting

  • Log all errors for debugging and monitoring
  • Set up alerts for high error rates
  • Track error trends and patterns
This comprehensive error handling system ensures reliable, user-friendly error experiences across the entire BuildAppsWith platform while providing developers with the tools needed to debug and resolve issues effectively.