import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
headers: {
'Content-Type': 'application/json',
},
})
// Request interceptor - add auth token
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// Add request ID for debugging
config.headers['X-Request-ID'] = uuidv4()
return config
},
(error) => Promise.reject(error)
)
// Response interceptor - handle errors
api.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
localStorage.removeItem('authToken')
window.location.href = '/login'
}
const errorMessage =
(error.response?.data as any)?.message ||
'An unexpected error occurred'
return Promise.reject({
message: errorMessage,
status: error.response?.status,
data: error.response?.data,
})
}
)
export default api
A centralized API client provides a single place to configure authentication, error handling, and request/response transformations. I use axios for its interceptor support and automatic JSON transformation. Request interceptors attach the JWT token from localStorage to every request's Authorization header. Response interceptors handle 401 errors by redirecting to login and parse server errors into a consistent format. I also add a request ID header for debugging and set reasonable timeouts to prevent hanging requests. The base URL points to the Rails API, configured via environment variables so it works across dev, staging, and production. This abstraction means controllers never deal with auth headers or error parsing directly.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
import graphene
from graphene_django import DjangoObjectType
from blog.models import Post, Comment
class PostType(DjangoObjectType):
Django GraphQL with Graphene
Share this code
Here's the card — post it anywhere.