Hireable LogoHireable
Backend API

Error Handling

API error responses, status codes, and error handling patterns

Overview

The API uses standard HTTP status codes and consistent error response formats for all endpoints.

Error Response Format

All error responses follow this structure:

{
  "message": "Human-readable error message",
  "code": "ERROR_CODE",
  "details": {}
}
FieldTypeDescription
messagestringHuman-readable error description
codestringMachine-readable error code (optional)
detailsobjectAdditional error details (optional)

HTTP Status Codes

Success Codes

CodeStatusDescription
200OKRequest successful
201CreatedResource created successfully
204No ContentRequest successful, no content returned

Client Error Codes

CodeStatusDescription
400Bad RequestInvalid request data
401UnauthorizedAuthentication required
403ForbiddenInsufficient permissions
404Not FoundResource not found
409ConflictResource already exists
422Unprocessable EntityValidation failed
429Too Many RequestsRate limit exceeded

Server Error Codes

CodeStatusDescription
500Internal Server ErrorUnexpected server error
502Bad GatewayUpstream service error
503Service UnavailableService temporarily unavailable

Common Error Responses

400 Bad Request

Invalid or missing request data:

{
  "message": "Email is required"
}
{
  "message": "Invalid email format"
}

401 Unauthorized

Authentication required or invalid token:

{
  "message": "Authentication required",
  "code": "AUTH_REQUIRED"
}
{
  "message": "Invalid or expired token",
  "code": "INVALID_TOKEN"
}

403 Forbidden

Insufficient permissions:

{
  "message": "You do not have permission to perform this action",
  "code": "FORBIDDEN"
}

404 Not Found

Resource not found:

{
  "message": "Job not found",
  "code": "NOT_FOUND"
}

409 Conflict

Resource already exists:

{
  "message": "This email is already on the waitlist.",
  "code": "DUPLICATE_ENTRY"
}

422 Unprocessable Entity

Validation errors with details:

{
  "message": "Validation failed",
  "code": "VALIDATION_ERROR",
  "details": {
    "email": "Invalid email format",
    "password": "Password must be at least 8 characters"
  }
}

429 Too Many Requests

Rate limit exceeded:

{
  "message": "Too many requests, please try again later",
  "code": "RATE_LIMIT_EXCEEDED",
  "details": {
    "retryAfter": 60
  }
}

500 Internal Server Error

Unexpected server error:

{
  "message": "An unexpected error occurred",
  "code": "INTERNAL_ERROR"
}

Validation Errors

The API uses SecurityValidator from @hireable/shared for input validation:

import { SecurityValidator } from "@hireable/shared";
 
// Validate email
const emailResult = SecurityValidator.validateEmail(email);
if (!emailResult.isValid) {
  return res.status(400).json({ 
    message: emailResult.errors[0] 
  });
}
 
// Validate name
const nameResult = SecurityValidator.validateFirstName(firstName);
if (!nameResult.isValid) {
  return res.status(400).json({ 
    message: nameResult.errors[0] 
  });
}

Validation Rules

ValidatorError Messages
validateEmail"Email is required", "Invalid email format", "Email is too long"
validateFirstName"First name is required", "First name must be at least 2 characters", "First name contains invalid characters"
validateLastName"Last name is required", "Last name must be at least 2 characters"
validateCompany"Company is required", "Company must be at least 2 characters"
validatePassword"Password is required", "Password must be at least 8 characters"
validateAgree"Agreement is required", "Agreement must be a boolean"

Frontend Error Handling

API Client Error

import { ApiClientError } from "@/services/api";
 
try {
  await apiClient.post("/login", credentials);
} catch (error) {
  if (error instanceof ApiClientError) {
    // Handle specific status codes
    switch (error.status) {
      case 400:
        showValidationError(error.message);
        break;
      case 401:
        redirectToLogin();
        break;
      case 403:
        showPermissionDenied();
        break;
      case 404:
        showNotFound();
        break;
      case 409:
        showConflictError(error.message);
        break;
      default:
        showGenericError();
    }
  }
}

useApi Hook Error Handling

import { useApi } from "@/hooks";
 
function MyComponent() {
  const { data, error, isError, execute } = useApi(apiFunction);
 
  if (isError) {
    return <ErrorMessage message={error} />;
  }
 
  return <DataDisplay data={data} />;
}

Form Error Handling

import { useForm } from "@/hooks";
 
function LoginForm() {
  const { errors, setFieldError, handleSubmit } = useForm({
    onSubmit: async (values) => {
      try {
        await authService.login(values);
      } catch (error) {
        if (error.status === 401) {
          setFieldError("email", "Invalid email or password");
        }
      }
    },
  });
 
  return (
    <form onSubmit={handleSubmit}>
      <Input name="email" />
      {errors.email && <Error>{errors.email}</Error>}
    </form>
  );
}

Error Boundary

Use React Error Boundaries for unexpected errors:

import { ErrorBoundary } from "@/components/ErrorBoundary";
 
function App() {
  return (
    <ErrorBoundary fallback={<ErrorPage />}>
      <MainContent />
    </ErrorBoundary>
  );
}

Logging

Server-side errors are logged for debugging:

try {
  // Operation
} catch (error) {
  console.error("Error joining waitlist:", error);
  return res.status(500).json({ message: "Error saving data" });
}

Best Practices

  1. Always return consistent error format - Use the standard error response structure
  2. Use appropriate status codes - Match the error type to the correct HTTP status
  3. Provide helpful messages - Error messages should help users understand what went wrong
  4. Don't expose internal details - Avoid leaking stack traces or internal errors to clients
  5. Log errors server-side - Log detailed errors for debugging while returning safe messages to clients
  6. Handle errors at the right level - Use try/catch at appropriate boundaries