TypeScript
Type responses, paginated lists, ordering params, the auth user and plugin methods with @mrzr/api-client, and build a typed API layer on top of it.
The package is written in TypeScript and ships its own types. Every type is exported from @mrzr/api-client.
Typing responses
The generic on each method types res.data:
const { data } = await api.get<User[]>("/users");
// ^? User[] | undefineddata is optional because a 204, an empty body or a failed parse legitimately has none. With the default throwError: true, a failure has already thrown by the time you read it, so data! is safe after a successful await.
The generic also types the transforms: afterFunc and beforeSelectOptions receive the payload as R.
Lists and ordering
ListResponse<T> is the common paginated shape (Django REST Framework's), and ordering in params is typed against the item, not the wrapper:
import type { ListResponse } from "@mrzr/api-client";
const { data } = await api.get<ListResponse<User>>("/users", {
params: { page: 2, ordering: { createdAt: "desc" } }, // keys come from User
});
data?.results; // User[]
data?.count; // number
data?.next; // string | nullinterface ListResponse<T> { count: number; next: string | null; previous: string | null; results: T[] }
type Ordering<T> = { [K in keyof T]?: "asc" | "desc" };Other params stay untyped ([key: string]: unknown), so any query parameter is allowed.
A typed API layer
Wrap each resource once, and components never see URLs:
import { api } from "@/lib/api";
import type { ListResponse } from "@mrzr/api-client";
export interface User { id: number; name: string; email: string }
export type NewUser = Omit<User, "id">;
export const users = {
list: (page = 1) => api.get<ListResponse<User>>("/users", { params: { page } }).then((r) => r.data!),
byId: (id: number) => api.get<User>("/users/{id}", { addTemplateToUrl: { id } }).then((r) => r.data!),
create: (input: NewUser) => api.post<User>("/users", input).then((r) => r.data!),
update: (id: number, patch: Partial<NewUser>) =>
api.patch<User>("/users/{id}", patch, { addTemplateToUrl: { id } }).then((r) => r.data!),
remove: (id: number) => api.delete("/users/{id}", { addTemplateToUrl: { id } }),
};Typing the user
AuthState.user is unknown, because it's whatever your server returns. Narrow it in one place:
import type { AuthState } from "@mrzr/api-client";
interface AppUser { id: number; email: string; role: "admin" | "member" }
export type AppAuthState = Omit<AuthState, "user"> & { user?: AppUser };
api.onAuthStateChange((s) => setAuth(s as AppAuthState));Narrowing errors
import { ApiError } from "@mrzr/api-client";
export const isStatus = (e: unknown, code: number): e is ApiError =>
e instanceof ApiError && e.statusCode === code;
if (isStatus(err, 409)) showConflictDialog(err.message);ApiError is a real subclass of Error with name === "ApiError", so the name check also works across bundles. Its errors are typed Record<string, string[]>, and data is unknown.
Plugin methods
Methods a plugin adds are typed on the client automatically. For the services plugin, a misspelled service name is a compile error:
const api = createClient({ plugins: [services({ files: "https://files.example.com" })] });
api.service("files"); // ok
api.service("fiels"); // errorTo type your own, see adding typed methods. PluginExtensions<typeof plugins> is the type of the methods a plugin list adds.
Exported types
| Type | Is |
|---|---|
ApiClient | The client createClient returns |
ClientOptions, RequestConfig<T> | Client and per-request options |
IRes<R> | The response envelope |
ApiError | The error class (a value, not just a type) |
HttpMethod, ResponseFormat, AuthMode, StorageKind | String unions |
Params<T>, Ordering<T>, ListResponse<T> | Query and list helpers |
AuthState, TokenPair, TokenStorage | Auth state, tokens and storage adapters |
TokenExtractor, TokenFieldMap, RefreshBodyConfig | Custom token shapes |
CancelOptions, CancelSelector, CancelMatch, CancelScope, PendingRequest | Cancellation |
SocketTokenOptions | Options for getSocketToken |
LogEntry | What onLog receives |
ApiPlugin<E>, PluginRequest, PluginExtensions<P> | Plugins |
ServiceOptions, ServiceDefinition, ServiceClient | From @mrzr/api-client/services |