create-nttb
Version:
An opinionated Next.js, TypeScript, and Tailwind boilerplate using Atomic Design Methodology for presentation components.
57 lines (50 loc) • 1.82 kB
text/typescript
import { NEXT_PUBLIC_API_URL } from "../config/variables";
/**
* Represents an HTTP error returned from a failed request.
*/
export class HttpError extends Error {
public readonly status: number | undefined;
constructor(status: number | undefined, message: string) {
super(message);
this.name = "HttpError";
this.status = status;
}
}
/**
* Makes an HTTP request using the native fetch API.
*
* Automatically prepends the base API URL from environment config,
* sets JSON content headers, and throws an {@link HttpError} for
* non-2xx responses with the status code and error message from the body.
*
* Requests time out after 5 seconds via {@link AbortSignal.timeout}.
*
* @param path - The API path to append to the base URL (e.g. `/users/1`).
* @param options - Standard {@link RequestInit} fetch options (method, body, headers, etc).
* @returns The parsed JSON response body typed as `T`.
* @throws {@link HttpError} if the response status is not ok, or if the network fails.
* @throws {@link DOMException} with name `"TimeoutError"` if the request exceeds 5 seconds.
*
* @example
* const user = await request<User>("/users/1");
* const created = await request<User>("/users", { method: "POST", body: JSON.stringify(data) });
*/
export async function request<T = unknown>(
path: string,
options?: RequestInit,
): Promise<T> {
const url = `${NEXT_PUBLIC_API_URL}${path}`;
const response = await fetch(url, {
signal: AbortSignal.timeout(5000),
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new HttpError(response.status, body?.message ?? response.statusText);
}
return response.json() as Promise<T>;
}