53 lines
1.9 KiB
TypeScript
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" }),
|
|
};
|