-
Notifications
You must be signed in to change notification settings - Fork 0
Plugins
Plugins are optional add-ons. The core stays small, and an app pays only for the plugins it imports: each one ships as its own entry point.
import { createClient } from "@mrzr/api-client";
import { services } from "@mrzr/api-client/services";
export const api = createClient({
baseUrl: "https://api.example.com",
plugins: [services({ files: "https://files.example.com" })],
});Name your other APIs once; every service shares the client's login, token refresh, cancellation and plugins.
import { services } from "@mrzr/api-client/services";
const api = createClient({
baseUrl: "https://api.example.com", // the default service
plugins: [
services({
files: "https://files.example.com", // short form
search: { baseUrl: import.meta.env.VITE_SEARCH_URL, timeout: 5_000 },
billing: { baseUrl: "https://billing.example.com", headers: { "X-Tenant": "acme" } },
maps: { baseUrl: "https://maps.thirdparty.com", auth: false },
}),
],
});
await api.service("files").get("/uploads");
await api.service("billing").post("/invoices", invoice);
api.service("fiels"); // compile error: not a declared service| Option | Default | Meaning |
|---|---|---|
baseUrl |
required | Where the service lives. An unset env variable fails at setup, naming the service |
auth |
true |
Send the access token and refresh on 401. false for a third-party API: it never gets the token, and on its own origin no CSRF header either |
timeout |
client timeout
|
Default for this service's calls |
headers |
– | Merged under each call's own headers |
Services with auth are added to authOrigins for you. A call's own options still apply: api.service("files").get("/x", { timeout: 60_000 }).
Environment variables are not detected per service: bundlers only replace env names written literally in code, so pass them in as above.
If two services need different logins, create separate clients instead (see Client Options → Multiple APIs in one app).
A plugin is an object with a name and any of four hooks:
import type { ApiPlugin } from "@mrzr/api-client";
export const traceIds: ApiPlugin = {
name: "trace-ids",
beforeRequest: (request) => ({
...request,
config: {
...request.config,
headers: { ...request.config?.headers, "X-Trace-Id": crypto.randomUUID() },
},
}),
};| Hook | When | Use it to |
|---|---|---|
configure(options) |
Once, before the client exists | Add defaults, headers, trusted origins |
beforeRequest(request) |
Before each get/post/put/patch/delete
|
Rewrite method, url, body or config
|
afterResponse(result, request) |
After each of those calls, before throwError
|
Reshape results, collect metrics, turn a failure into a fallback |
extend(client) |
Once, after the client exists | Add typed methods, like api.service()
|
Plugins run in the order given. login, logout and the auth methods don't pass through beforeRequest / afterResponse; they stay under the client's control.
To add typed methods, give ApiPlugin the shape of what extend returns:
const health: ApiPlugin<{ health(): Promise<boolean> }> = {
name: "health",
extend: (client) => ({
health: async () => (await client.get("/health", { throwError: false })).status,
}),
};
const api = createClient({ baseUrl, plugins: [health] });
await api.health(); // typed-
A failing plugin fails one call, never the client. A hook that throws resolves that call as a failure whose message names the plugin; the next call runs normally. A plugin that throws in
configureis a setup error and throws fromcreateClient. -
Plugins can't replace built-in methods.
extendreturningget,loginor another plugin's method throws at setup. - Plugins never see tokens. They run on the page, around the request; in worker mode the token stays inside the worker.
Plugins are code your app chose to install, so they can do what your app can: a plugin's configure may add trusted origins, and its hooks see every request and response. Install plugins you trust, as you would any dependency. What a plugin can't do is read the access token — the same guarantee page code has.
Next: Security Model
Getting started
Requests
Authentication
Advanced
- Web Worker Isolation
- Multi-Tab Sync
- WebSockets and Socket.io
- Plugins
- Logging and Observability
- Security Model
Reference
Guides