@icib.dev/api-client
v1.3.3
Published
Generator for strictly-typed TypeScript API clients from OpenAPI specs
Readme
@icib.dev/api-client
Generator for strictly-typed TypeScript API clients from OpenAPI specs. The client is generated in your project—nothing is published to npm.
Install
npm install @icib.dev/api-client axiosQuick Start
- Generate the client in your project:
npx api-client-generate- Import from your generated client:
import { setAuthToken, apiClient } from "./api"; // or your --out path
setAuthToken(process.env.API_TOKEN);
const res = await apiClient.allegati.list({ page: 1, size: 10 });For JWT + localStorage, generate with --auth jwt (see Authentication).
- Add verify to your build (ensures version alignment for production; fails if docs changed or client was modified):
{
"scripts": {
"build": "api-client-verify && tsc"
}
}API Client Generator
From consuming apps (npx)
If you use this library in your app, run the generator from your project root:
npx api-client-generateWith options:
# Custom URL
npx api-client-generate --url https://api.example.com/docs/openapi --out api
# Using BASE_URL env (default: $BASE_URL/docs/openapi)
BASE_URL=https://api.example.com npx api-client-generate --out api
# Custom base path (default empty; when set, included in axios baseURL)
npx api-client-generate --base-path /v1/api
BASE_PATH=/v2 npx api-client-generate
# Custom client base URL (default: from spec URL, BASE_URL, or spec host)
npx api-client-generate --base-url https://api.mycompany.com
BASE_URL=https://api.mycompany.com npx api-client-generate
# Override client.ts (by default, existing client is preserved if you customized it)
npx api-client-generate --override-client # prompts for confirmation
npx api-client-generate --override-client --yes # skip confirmation (e.g. CI)
# client.ts includes only the auth mode you choose (see table below)
npx api-client-generate --auth jwt --jwt-init lazy --override-client --yes
npx api-client-generate --auth jwt --jwt-init eager --jwt-access-key myAccess --jwt-refresh-key myRefresh --override-client --yes
npx api-client-generate --auth cookie --override-client --yes
npx api-client-generate --auth custom --override-client --yes
# CLI reference
npx api-client-generate --help
npx api-client-verify --helpThe client is generated in your project directory (e.g. ./api/). If client.ts already exists, it is not overwritten unless you pass --override-client (which prompts for confirmation; use --yes to skip the prompt).
Auth mode (--auth)
client.ts is generated with only the matching implementation (no configureAuth, setDefaultAuthProfile, or unused profiles).
| Flag | Generated behavior |
|------|-------------------|
| --auth none (default) | Bearer from setAuthToken in memory; optional object form for access + refresh; no localStorage. |
| --auth jwt | Same Bearer + persist access/refresh in localStorage. --jwt-init lazy (default): read storage on the first HTTP request. --jwt-init eager: read when the module loads. Keys: --jwt-access-key, --jwt-refresh-key (defaults accessToken / refreshToken). |
| --auth cookie | withCredentials: true; no Bearer header; token helpers are no-ops for headers. |
| --auth custom | Bearer + empty applyRequestAuth in client.ts — edit it for API keys, etc. |
Aliases: --default-auth → --auth; --default-auth-timing immediate → --jwt-init eager; lazy → --jwt-init lazy.
You still register setAuthRefreshHandler in your app (refresh URLs are not in the OpenAPI spec). Use --override-client when regenerating so client.ts matches your chosen --auth.
From the library repo (maintainers)
npm run generateBy default, the spec URL is $BASE_URL/docs/openapi when the BASE_URL env variable is set. If unset, it falls back to the ICIB default.
Output
The generator creates an api/ folder and a local manifest (api-client.manifest.json, gitignored):
api/
├── client.ts # Axios instance + single auth mode + optional token refresh
├── apiClient.ts # Nested apiClient (generated; imports apiClient.custom.ts)
├── apiClient.custom.ts # Re-exports augment — regenerated on every generate
├── apiClient.augment.ts # Implement augmentApiClient here — created once, never overwritten
├── types/index.ts # TypeScript interfaces from schema definitions
├── contexts/ # One file per API context (tag)
│ ├── allegati.ts
│ ├── articolo.ts
│ └── ...
└── index.ts # Re-exports client, apiClient, types, contextsHash verification
Add api-client-verify before your build to ensure the generated client matches the current OpenAPI docs. You can insert it in the build step of your utilization library (the app or library that consumes the API client) to verify version alignment before production builds—if the API docs changed or the client was modified, the build fails and you must regenerate.
When you run your build, it:
- Reads the manifest (created by
generate) - Fetches the current docs and compares their hash
- Hashes the generated client files and compares with the manifest
If docs changed: Build fails with:
API docs have changed. Run
npm run generateto regenerate the client, then update your application.
If client was manually edited: Build fails with:
Generated client files were modified. Run
npm run generateto regenerate.
If manifest is missing: Run npm run generate first (e.g. after a fresh clone).
Extending apiClient (apiClient.augment.ts)
The generator writes apiClient.ts as a thin wrapper: it builds _generatedApiClient, exports the type GeneratedApiClient, and assigns apiClient = augmentApiClient(_generatedApiClient) via ./apiClient.custom.ts.
apiClient.custom.tsis regenerated on every run ofapi-client-generate. It only re-exportsaugmentApiClientfrom./apiClient.augment.ts, so the import path inapiClient.tsstays stable.apiClient.augment.tsholds your realaugmentApiClientimplementation. It is created if missing and never overwritten (same idea as preservingclient.ts). Put overrides, bypasses, and extra methods there without touchingcontexts/*.ts.
On the first run after this layout exists, if you still had a legacy single file apiClient.custom.ts with an inline augmentApiClient, the generator copies that content into apiClient.augment.ts once, then replaces apiClient.custom.ts with the stub.
If your apiClient.augment.ts still uses a generic `<T>(base: T): T` signature from an older scaffold, replace it with the typed signature above (or delete apiClient.augment.ts once and regenerate so a fresh typed scaffold is created—only if you have no custom code to keep).
When you need the generated client type, the scaffold already uses import type { GeneratedApiClient } from "./apiClient.js" so base and the return type are fully typed per context and method. Use import type only for any extra types from apiClient.js so you avoid a runtime circular dependency.
api-client-verify hashes apiClient.custom.ts and apiClient.augment.ts. After you change apiClient.augment.ts, run api-client-generate again so api-client.manifest.json picks up the new clientHash.
The generated index.ts also re-exports GeneratedApiClient for use elsewhere in your app.
Authentication (client.ts)
Choose --auth when you generate so client.ts only contains that flow. There is no runtime configureAuth / setDefaultAuthProfile — switching modes means regenerating with --override-client.
Exports (typical): client, setAuthToken (string or { accessToken, refreshToken? }), setRefreshToken, getAuthToken, getRefreshToken, clearAuthToken, setAuthRefreshHandler, blob helpers.
Successful calls on client resolve to a full AxiosResponse (use .data for the parsed body, .headers / .status for debugging).
setAuthRefreshHandler— on 401/403, runs your refresh, thensaveTokenlikesetAuthToken. Mark the refresh HTTP call withskipAuthRefresh: true(seeclient.tsJSDoc).--auth jwt+ lazy: first request loads tokens fromlocalStorageif memory is still empty (then keeps using memory). Eager loads storage when the module loads.--auth custom: editapplyRequestAuthinsideclient.tsfor API keys, etc.
Example (JWT client + refresh) — generate with npx api-client-generate --auth jwt --jwt-init lazy …:
import { apiClient, client, setAuthToken, setAuthRefreshHandler, getRefreshToken } from "./api";
setAuthRefreshHandler(async (saveToken) => {
const refreshToken = getRefreshToken();
if (!refreshToken) throw new Error("Not logged in");
const res = await client.post<{
accessToken: string;
refreshToken: string;
}>("/auth/refresh", { refreshToken }, { skipAuthRefresh: true });
saveToken({
accessToken: res.data.accessToken,
refreshToken: res.data.refreshToken,
});
});
setAuthToken({
accessToken: loginResponse.accessToken,
refreshToken: loginResponse.refreshToken,
});
await apiClient.someContext.list({ page: 1, size: 10 });Nuxt 3: use a .client plugin for setAuthRefreshHandler (and any login that calls setAuthToken). With --auth jwt, you do not configure a separate “profile” in Nuxt—client.ts is already JWT-only.
JSDoc documentation
The generated client includes JSDoc comments from the OpenAPI spec:
- Context/controller: Description from tag or "API client for X endpoints"
- Methods: Operation
summaryanddescription - Params:
@paramwith descriptions for path params, query params, and body - Types: Interface and property descriptions when present in the schema
File uploads (multipart/form-data)
OpenAPI 3 operations whose requestBody uses multipart/form-data with a flat object schema are generated to accept a typed data object and build FormData inside the method (so axios sends the correct multipart body and boundary). Properties with format: binary are typed as Blob | File; other scalar parts are appended as strings. Arrays of binary parts (array of string + format: binary) are supported as repeated append calls.
If the same operation lists application/json and multipart, the generator prefers JSON for the body schema (unchanged behavior). Multipart-only (or multipart when JSON is absent) triggers automatic FormData.
If the multipart schema is not a plain object with supported fields (e.g. nested objects, allOf, or unsupported array shapes), the method expects data: FormData and passes it through unchanged.
OpenAPI 2 in: formData / type: file parameters are not handled by this path; use OpenAPI 3 requestBody + multipart/form-data for uploads.
Blob / file download endpoints
Endpoints that return files (CSV, PDF, etc.) are detected from the spec (description, path patterns like /download/, x-response-type: blob). They return Blob and support download: true to trigger a browser download:
import { apiClient } from "./api";
// Get blob in response.data
const res = await apiClient.QR_Code.downloadUnassigned({ page: 1, size: 100 });
const csvBlob = res.data; // Blob
// Auto-download in browser
await apiClient.QR_Code.downloadUnassigned({}, { download: true });
await apiClient.QR_Code.generateCsv(
{ n: 10 },
{ download: true, filename: "qrcodes.csv" },
);