Skip to main content

Overview

The API client provides a set of functions for making HTTP requests to the backend API. It includes automatic authentication, error handling, timeout management, retry logic, and session management.

Import

import { request, apiGET, apiPOST, apiPATCH, apiDELETE } from '@app/api/client'

Configuration

The API base URL is configured via environment variable:
const API_BASE_URL = import.meta.env.VITE_API_URL
Ensure VITE_API_URL is set in your .env file:
VITE_API_URL=http://localhost:3000/api

Core Function

request()

Low-level HTTP request function with automatic authentication, timeout, and retry capabilities.
function request(
  endpoint: string,
  options?: RequestInit & { retry?: number, timeOut?: number }
): Promise<any>

Parameters

endpoint
string
required
API endpoint path (without base URL). Example: "/auth/login" or "/users/123"
options
RequestInit & { retry?: number, timeOut?: number }
Request configuration options:Standard RequestInit options:
  • method: HTTP method (GET, POST, etc.)
  • headers: Additional headers
  • body: Request body (will be sent with Content-Type: application/json)
Extended options:
  • retry (number): Number of retry attempts on failure. Default: 0
  • timeOut (number): Request timeout in milliseconds. Default: 30000 (30 seconds)

Returns

Returns a Promise that resolves to:
  • Parsed JSON response for successful requests
  • null for 204 No Content responses
  • Throws an error for failed requests

Behavior

Authentication:
  • Automatically includes credentials (cookies) with credentials: "include"
  • On 401 Unauthorized, automatically calls useAuthStore.getState().logout()
Headers:
  • Automatically sets Content-Type: application/json when a body is present
  • Merges custom headers from options
Timeout Management:
  • Uses AbortController to cancel requests that exceed the timeout
  • Throws “Request timeout” error if timeout is reached
Error Handling:
  • Extracts error message from response JSON when available
  • Falls back to generic “API request failed” message
  • Preserves error types for proper error handling
Retry Logic:
  • Automatically retries failed requests up to retry count
  • Does NOT retry on 401 (Unauthorized) or 403 (Forbidden)
  • Does NOT retry on timeout errors

Example

import { request } from '@app/api/client'

try {
  const data = await request('/users', {
    method: 'POST',
    body: JSON.stringify({ name: 'John Doe' }),
    retry: 2,
    timeOut: 5000
  })
  console.log('User created:', data)
} catch (error) {
  console.error('Request failed:', error.message)
}

Convenience Methods

apiGET()

Make a GET request to the API.
function apiGET(url: string, options?: RequestInit): Promise<any>
url
string
required
API endpoint path
options
RequestInit
Optional request configuration (merged with method: "GET")
Example:
import { apiGET } from '@app/api/client'

const user = await apiGET('/auth/me')
console.log('Current user:', user)

const users = await apiGET('/users?page=1&limit=10')
console.log('Users:', users)

apiPOST()

Make a POST request to the API.
function apiPOST(url: string, options?: RequestInit): Promise<any>
url
string
required
API endpoint path
options
RequestInit
Request configuration (merged with method: "POST"). Typically includes body property.
Example:
import { apiPOST } from '@app/api/client'

const newUser = await apiPOST('/users', {
  body: JSON.stringify({
    email: 'user@example.com',
    password: 'secure123',
    role: 'ORGANIZER'
  })
})

const loginResult = await apiPOST('/auth/login', {
  body: JSON.stringify({ email, password })
})

apiPATCH()

Make a PATCH request to the API.
function apiPATCH(url: string, options?: RequestInit): Promise<any>
url
string
required
API endpoint path
options
RequestInit
Request configuration (merged with method: "PATCH"). Typically includes body property.
Example:
import { apiPATCH } from '@app/api/client'

const updatedUser = await apiPATCH('/users/123', {
  body: JSON.stringify({
    email: 'newemail@example.com'
  })
})

const onboardingComplete = await apiPATCH('/auth/onboard', {
  body: JSON.stringify({ is_onboarded: true })
})

apiDELETE()

Make a DELETE request to the API.
function apiDELETE(url: string, options?: RequestInit): Promise<any>
url
string
required
API endpoint path
options
RequestInit
Optional request configuration (merged with method: "DELETE")
Example:
import { apiDELETE } from '@app/api/client'

await apiDELETE('/users/123')

await apiDELETE('/sessions/abc-def')

Error Handling

The API client throws errors in the following scenarios:

Unauthorized (401)

try {
  await apiGET('/protected-resource')
} catch (error) {
  // Error message: "Unauthorized"
  // useAuthStore.logout() is called automatically
}

Request Timeout

try {
  await apiGET('/slow-endpoint', { timeOut: 1000 })
} catch (error) {
  // Error message: "Request timeout"
}

API Errors

try {
  await apiPOST('/users', {
    body: JSON.stringify({ email: 'invalid' })
  })
} catch (error) {
  // Error message from API response or "API request failed"
}

Best Practices

import { apiPOST } from '@app/api/client'

async function createUser(userData) {
  try {
    const user = await apiPOST('/users', {
      body: JSON.stringify(userData)
    })
    return { success: true, user }
  } catch (error) {
    if (error.message === 'Unauthorized') {
      // User was logged out automatically
      return { success: false, error: 'Session expired' }
    }
    if (error.message === 'Request timeout') {
      return { success: false, error: 'Server is taking too long' }
    }
    return { success: false, error: error.message }
  }
}

Advanced Usage

Custom Headers

import { apiGET } from '@app/api/client'

const data = await apiGET('/data', {
  headers: {
    'X-Custom-Header': 'value',
    'Accept-Language': 'en-US'
  }
})

Retry Configuration

import { request } from '@app/api/client'

// Retry up to 3 times on failure
const data = await request('/unreliable-endpoint', {
  method: 'GET',
  retry: 3
})

Custom Timeout

import { apiGET } from '@app/api/client'

// 10 second timeout
const data = await apiGET('/slow-endpoint', {
  timeOut: 10000
})

File Upload

import { request } from '@app/api/client'

const formData = new FormData()
formData.append('file', fileInput.files[0])

const result = await request('/upload', {
  method: 'POST',
  body: formData,
  // Don't set Content-Type, browser will set it with boundary
  headers: {}
})

Source

Implemented in: src/app/api/client.ts:5

Build docs developers (and LLMs) love