Ilo/frontend/src/lib/api.ts
2026-05-03 19:51:38 +03:30

53 lines
1.9 KiB
TypeScript

// On the server (SSR / route handlers / RSC) prefer INTERNAL_API_URL — Docker compose service name.
// In the browser, NEXT_PUBLIC_API_URL is baked in at build time and points at the host.
const rawBaseUrl =
typeof window === "undefined"
? process.env.INTERNAL_API_URL ?? process.env.NEXT_PUBLIC_API_URL
: process.env.NEXT_PUBLIC_API_URL;
const baseUrl = (rawBaseUrl ?? "http://localhost:8000").replace(/\/$/, "");
export class ApiError extends Error {
constructor(public status: number, message: string, public body?: unknown) {
super(message);
this.name = "ApiError";
}
}
type FetchInit = RequestInit & { token?: string; locale?: string };
async function request<T>(path: string, init: FetchInit = {}): Promise<T> {
const { token, locale, headers, ...rest } = init;
const res = await fetch(`${baseUrl}${path.startsWith("/") ? path : `/${path}`}`, {
...rest,
headers: {
"Content-Type": "application/json",
...(locale ? { "Accept-Language": locale } : {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
...headers,
},
});
if (!res.ok) {
let body: unknown;
try {
body = await res.json();
} catch {
body = await res.text().catch(() => undefined);
}
throw new ApiError(res.status, `${res.status} ${res.statusText}`, body);
}
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}
export const api = {
baseUrl,
get: <T>(path: string, init?: FetchInit) => request<T>(path, { ...init, method: "GET" }),
post: <T>(path: string, body?: unknown, init?: FetchInit) =>
request<T>(path, { ...init, method: "POST", body: body ? JSON.stringify(body) : undefined }),
patch: <T>(path: string, body?: unknown, init?: FetchInit) =>
request<T>(path, { ...init, method: "PATCH", body: body ? JSON.stringify(body) : undefined }),
delete: <T>(path: string, init?: FetchInit) => request<T>(path, { ...init, method: "DELETE" }),
};