Skip to main content

API Response Standardization

BuildAppsWith implements a comprehensive API response standardization system to ensure consistency, type safety, and predictable error handling across all API endpoints.

Core Standardization Pattern

All APIs follow the StandardApiResponse pattern, which provides a consistent structure across all domains:
interface StandardApiResponse<T = unknown> {
  success: boolean;
  data?: T;
  error?: ApiError;
  pagination?: PaginationInfo;
  message?: string;
}

Key Benefits

  • Consistent Response Structure: All APIs return the same response format
  • Type Safety: Full TypeScript support with generic data types
  • Predictable Error Handling: Standardized error codes and messages
  • Marketplace Compatibility: Aligned with proven marketplace API patterns
  • Component Integration: UI components can rely on consistent data shapes

Success Response Example

// Successful API response
{
  "success": true,
  "data": {
    "userId": "user_123",
    "email": "[email protected]",
    "profile": {
      "id": "profile_456",
      "displayName": "John Doe",
      "bio": "AI Application Builder"
    }
  },
  "message": "Profile retrieved successfully"
}

Error Response Example

// Error API response
{
  "success": false,
  "error": {
    "code": "not_found",
    "message": "Builder profile not found",
    "statusCode": 404,
    "details": {
      "profileId": "invalid_id"
    }
  }
}

Paginated Response Example

// Paginated API response (marketplace pattern)
{
  "success": true,
  "data": [
    { "id": "1", "name": "Builder 1" },
    { "id": "2", "name": "Builder 2" }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 25,
    "totalPages": 3,
    "hasMore": true
  }
}

Standardized Error Codes

All APIs use consistent error codes from the ApiErrorCode enum:
enum ApiErrorCode {
  BAD_REQUEST = 'bad_request',
  UNAUTHORIZED = 'unauthorized', 
  FORBIDDEN = 'forbidden',
  NOT_FOUND = 'not_found',
  VALIDATION_ERROR = 'validation_error',
  INTERNAL_ERROR = 'internal_error',
  SERVICE_UNAVAILABLE = 'service_unavailable',
  CONFLICT = 'conflict',
  RATE_LIMITED = 'rate_limited'
}

Utility Functions

toStandardResponse()

Convert data to standardized response format:
import { toStandardResponse, ApiErrorCode } from '@/lib/types/api-types';

// Success response
const successResponse = toStandardResponse(userData, {
  message: "User retrieved successfully"
});

// Error response  
const errorResponse = toStandardResponse(null, {
  error: {
    code: ApiErrorCode.NOT_FOUND,
    message: "User not found",
    statusCode: 404
  }
});

convertToMarketplaceFormat()

Convert legacy responses to marketplace-compatible format:
import { convertToMarketplaceFormat } from '@/lib/types/api-types';

// Convert legacy response
const standardResponse = convertToMarketplaceFormat(legacyApiResponse);

normalizeNullToUndefined()

Transform database null values to undefined for consistent API responses:
import { normalizeNullToUndefined } from '@/lib/types/api-types';

const cleanedData = normalizeNullToUndefined(databaseResult);

Implementation Examples

API Route Implementation

// app/api/profiles/builder/[id]/route.ts
import { toStandardResponse, ApiErrorCode } from '@/lib/types/api-types';

export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
  try {
    const { id } = params;

    if (!id) {
      return NextResponse.json(
        toStandardResponse(null, {
          error: {
            code: ApiErrorCode.BAD_REQUEST,
            message: 'Builder ID is required',
            statusCode: 400
          }
        }),
        { status: 400 }
      );
    }

    const profile = await getBuilderProfile(id);
    
    if (!profile) {
      return NextResponse.json(
        toStandardResponse(null, {
          error: {
            code: ApiErrorCode.NOT_FOUND,
            message: 'Builder profile not found', 
            statusCode: 404
          }
        }),
        { status: 404 }
      );
    }

    return NextResponse.json(toStandardResponse(profile));
  } catch (error) {
    return NextResponse.json(
      toStandardResponse(null, {
        error: {
          code: ApiErrorCode.INTERNAL_ERROR,
          message: 'Failed to fetch builder profile',
          statusCode: 500
        }
      }),
      { status: 500 }
    );
  }
}

Client-side API Function

// lib/profile/api.ts
export async function getBuilderProfileById(id: string): Promise<BuilderProfileResponse> {
  try {
    const response = await fetch(`/api/profiles/builder/${id}`);
    
    if (!response.ok) {
      const error = await response.json();
      return { 
        success: false, 
        error: error.error || { message: 'Failed to fetch profile' }
      };
    }

    return await response.json(); // Already in StandardApiResponse format
  } catch (error) {
    return { 
      success: false, 
      error: { 
        code: 'network_error',
        message: error instanceof Error ? error.message : 'Network error occurred' 
      }
    };
  }
}

Component Usage

// components/profile/builder-profile.tsx
import { getBuilderProfileById } from '@/lib/profile/api';

export async function BuilderProfile({ id }: { id: string }) {
  const profileResponse = await getBuilderProfileById(id);
  
  if (!profileResponse.success || !profileResponse.data) {
    return (
      <div className="error-container">
        <p>Error: {profileResponse.error?.message || 'Failed to load profile'}</p>
      </div>
    );
  }

  const profile = profileResponse.data;
  
  return (
    <div className="profile-container">
      <h1>{profile.displayName}</h1>
      <p>{profile.bio}</p>
    </div>
  );
}

Migration Guidelines

From Legacy APIs

For existing APIs that don’t follow the standardized pattern:
  1. Wrap existing responses with toStandardResponse():
    // Before
    return NextResponse.json(userData);
    
    // After  
    return NextResponse.json(toStandardResponse(userData));
    
  2. Update error handling to use standardized error codes:
    // Before
    return NextResponse.json({ error: "Not found" }, { status: 404 });
    
    // After
    return NextResponse.json(
      toStandardResponse(null, {
        error: {
          code: ApiErrorCode.NOT_FOUND,
          message: "Resource not found",
          statusCode: 404
        }
      }),
      { status: 404 }
    );
    
  3. Update client functions to handle StandardApiResponse:
    // Before
    const data = await response.json();
    return data;
    
    // After
    const apiResponse = await response.json();
    if (!apiResponse.success) {
      throw new Error(apiResponse.error?.message || 'API error');
    }
    return apiResponse.data;
    

Domain-Specific Implementations

Profile APIs

  • BuilderProfileResponse: Flattened response structure for component compatibility
  • Session type handling: Proper null/undefined property normalization
  • Validation tier alignment: Consistent enum usage across marketplace and profile domains

Marketplace APIs

  • Reference implementation: Marketplace APIs serve as the proven foundation
  • Pagination support: Built-in pagination following marketplace patterns
  • Filter handling: Standardized query parameter processing

Scheduling APIs

  • Session type compatibility: Database property alignment (color: string | null)
  • Booking confirmation: Standardized booking response formats
  • Calendar integration: Consistent Calendly API response handling

Best Practices

1. Always Use Standardized Responses

// ✅ Good
return NextResponse.json(toStandardResponse(data));

// ❌ Avoid
return NextResponse.json(data);

2. Consistent Error Handling

// ✅ Good
error: {
  code: ApiErrorCode.VALIDATION_ERROR,
  message: "Invalid input provided",
  statusCode: 400,
  details: { field: "email", reason: "Invalid format" }
}

// ❌ Avoid  
error: "Invalid email"

3. Type-Safe Client Functions

// ✅ Good
async function fetchData(): Promise<StandardApiResponse<DataType>> {
  // Implementation
}

// ❌ Avoid
async function fetchData(): Promise<any> {
  // Implementation  
}

4. Component Error Boundaries

// ✅ Good
if (!response.success || !response.data) {
  return <ErrorComponent message={response.error?.message} />;
}

// ❌ Avoid
if (!response.data) {
  return <div>Error</div>;
}

Testing Standardized APIs

Unit Tests

describe('Standardized API Response', () => {
  it('should return success response', async () => {
    const response = await api.getProfile('123');
    
    expect(response.success).toBe(true);
    expect(response.data).toBeDefined();
    expect(response.error).toBeUndefined();
  });
  
  it('should return error response', async () => {
    const response = await api.getProfile('invalid');
    
    expect(response.success).toBe(false);
    expect(response.error).toBeDefined();
    expect(response.error.code).toBe(ApiErrorCode.NOT_FOUND);
  });
});

Integration Tests

describe('API Route Integration', () => {
  it('should follow standard response format', async () => {
    const response = await fetch('/api/profiles/builder/123');
    const data = await response.json();
    
    expect(data).toHaveProperty('success');
    expect(data).toHaveProperty('data');
    expect(typeof data.success).toBe('boolean');
  });
});

Impact and Results

The API standardization implementation has delivered significant improvements:
  • Error Reduction: 27+ TypeScript errors resolved
  • Type Safety: Consistent interfaces across all APIs
  • Component Reliability: Predictable data structures for UI components
  • Developer Experience: Clear error messages and consistent patterns
  • Marketplace Alignment: All APIs follow proven marketplace patterns
This standardization provides a solid foundation for future API development and ensures consistent, reliable interactions across the entire BuildAppsWith platform.