API Client
@granit/api-client is the HTTP foundation for all Granit frontend packages. It creates
pre-configured Axios instances with automatic Bearer token injection, optional X-Tenant-Id
header for multi-tenant applications, and a 401 response interceptor for back-channel logout.
@granit/idempotency extends it with automatic Idempotency-Key header injection on mutation
requests, pairing with Granit.Idempotency on the .NET backend.
Peer dependencies: axios ^1.0.0
Package structure
Section titled “Package structure”Directory@granit/api-client/ Axios factory, global setters, error classes, RFC 7807 types
- @granit/idempotency Automatic Idempotency-Key header injection
| Package | Role | Depends on |
|---|---|---|
@granit/api-client | createApiClient(), createMutator(), global interceptor wiring | axios |
@granit/idempotency | enableIdempotency() for mutation request deduplication | @granit/api-client, axios |
import { createApiClient } from '@granit/api-client';
export const api = createApiClient({ baseURL: import.meta.env.VITE_API_URL, timeout: 15_000, // optional, default: 10_000 ms});import { createApiClient, createMutator } from '@granit/api-client';
const api = createApiClient({ baseURL: import.meta.env.VITE_API_URL });
// orval-compatible mutator — reuses interceptors, returns response.data directlyexport const customInstance = createMutator(api);export default customInstance;// src/main.ts — call once before any API requestimport { enableIdempotency } from '@granit/idempotency';
enableIdempotency();// Every POST, PUT, PATCH, DELETE now includes:// Idempotency-Key: <crypto.randomUUID()>API client
Section titled “API client”createApiClient(config)
Section titled “createApiClient(config)”Creates an Axios instance with request interceptors for Bearer token and tenant header, plus a response interceptor for 401 handling.
interface ApiClientConfig { baseURL: string; timeout?: number; // default: 10_000 ms}
function createApiClient(config: ApiClientConfig): AxiosInstance;The instance automatically:
- Injects
Authorization: Bearer <token>if a token getter is registered - Injects
X-Tenant-Id: <id>if a tenant getter is registered - Calls the
onUnauthorizedcallback on HTTP 401, then re-throws the error
createMutator(instance)
Section titled “createMutator(instance)”Creates an orval-compatible mutator from an
existing Axios instance. The mutator reuses all interceptors and returns response.data
directly.
function createMutator( instance: AxiosInstance): <T>(config: AxiosRequestConfig, options?: AxiosRequestConfig) => Promise<T>;Global setters
Section titled “Global setters”These functions wire global behavior into every Axios instance created by createApiClient.
Most are called automatically by companion packages — manual use is only needed when those
packages are not installed.
| Function | Called automatically by | Purpose |
|---|---|---|
setTokenGetter(getter) | @granit/react-authentication | Async Keycloak token getter |
setTenantGetter(getter) | @granit/react-multi-tenancy | Sync tenant ID for X-Tenant-Id |
setOnUnauthorized(callback) | @granit/react-authentication | HTTP 401 handler (back-channel logout) |
setIdempotencyKeyGenerator(generator) | @granit/idempotency | Mutation request key generator |
function setTokenGetter(getter: () => Promise<string | undefined>): void;function setTenantGetter(getter: () => string | undefined): void;function setOnUnauthorized(callback: () => void): void;function setIdempotencyKeyGenerator( generator: (config: InternalAxiosRequestConfig) => string | undefined): void;Error classes
Section titled “Error classes”All error classes are structured for programmatic handling. They pair with the RFC 7807 Problem Details returned by the Granit .NET backend.
HttpError
Section titled “HttpError”Thrown when the backend returns a non-2xx response with a ProblemDetails body.
class HttpError extends Error { readonly name = 'HttpError'; readonly status: number; readonly problemDetails?: ProblemDetailsPayload;}ValidationError
Section titled “ValidationError”Thrown for HTTP 422 responses with field-level validation errors.
class ValidationError extends Error { readonly name = 'ValidationError'; readonly details?: ValidationDetails;}
interface ValidationDetails { readonly field?: string; readonly constraint?: string; readonly fieldErrors?: Readonly<Record<string, readonly string[]>>;}TimeoutError
Section titled “TimeoutError”Thrown when a request exceeds the configured timeout.
class TimeoutError extends Error { readonly name = 'TimeoutError'; readonly timeoutMs: number;}Response types
Section titled “Response types”// RFC 7807 Problem Details — mirrors Granit.ExceptionHandling outputinterface ProblemDetails { type?: string; title: string; status: number; detail?: string; instance?: string; traceId?: string; // OpenTelemetry trace ID for Grafana correlation errorCode?: string; // Domain error code, e.g. "Appointment:SlotUnavailable"}
// Paginated list — mirrors Granit.Querying PagedResult<T>interface PaginatedResponse<T> { items: T[]; total: number; page: number; pageSize: number;}Idempotency
Section titled “Idempotency”@granit/idempotency injects a unique Idempotency-Key header on every mutation request
(POST, PUT, PATCH, DELETE). The .NET backend (Granit.Idempotency) uses this key
to deduplicate requests — same key + same payload returns the cached response, same key +
different payload returns HTTP 409.
interface IdempotencyOptions { methods?: string[]; // default: ['post', 'put', 'patch', 'delete'] headerName?: string; // default: 'Idempotency-Key' keyGenerator?: (config: InternalAxiosRequestConfig) => string | undefined;}
function enableIdempotency(options?: IdempotencyOptions): void;function disableIdempotency(): void;The default key generator uses crypto.randomUUID(). Call enableIdempotency() once in
main.ts — it registers the generator via setIdempotencyKeyGenerator() on the shared
@granit/api-client instance.
Test utilities
Section titled “Test utilities”Import from @granit/api-client/test-utils for Vitest:
import { createMockClient, axiosResponse } from '@granit/api-client/test-utils';
const client = createMockClient();vi.mocked(client.get).mockResolvedValue(axiosResponse({ items: [], total: 0 }));| Function | Purpose |
|---|---|
createMockClient() | Fully mocked AxiosInstance with vi.fn() stubs |
axiosResponse(data) | Wraps data in a minimal AxiosResponse<T> shape |
Public API summary
Section titled “Public API summary”| Category | Key exports | Package |
|---|---|---|
| Factory | createApiClient(), createMutator() | @granit/api-client |
| Global setters | setTokenGetter(), setTenantGetter(), setOnUnauthorized(), setIdempotencyKeyGenerator() | @granit/api-client |
| Error classes | HttpError, ValidationError, TimeoutError | @granit/api-client |
| Response types | ProblemDetails, ProblemDetailsPayload, ValidationDetails, PaginatedResponse<T>, ApiClientConfig | @granit/api-client |
| Idempotency | enableIdempotency(), disableIdempotency(), IdempotencyOptions | @granit/idempotency |
| Test utilities | createMockClient(), axiosResponse() | @granit/api-client/test-utils |
See also
Section titled “See also”- Granit.Core module — .NET foundation (exception hierarchy, batch operations)
- Granit.Idempotency module — .NET idempotency middleware
- Authentication —
setTokenGetteris wired automatically by@granit/react-authentication - Multi-tenancy —
setTenantGetteris wired automatically by@granit/react-multi-tenancy