API Client
Auto-generated, fully type-safe endpoints
The same config that boots your backend also describes its API surface — so nucleus-core ships a generator that turns it into a fully-typed client. You never hand-write fetch calls, endpoint URLs or response types; they are derived from your entities and auth config.
The flow is three small files: a server factory that knows your base URL and forwards cookies, a client hook created from the generated endpoint map, and components that call typed actions. Every payload, success and error type is inferred end to end.
Endpoint keys are generated per entity (GET_PRODUCTS list, GET_PRODUCT_BY_ID, ADD_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT, plus BULK_ADD_PRODUCTS… only when the entity opts in) plus auth routes (LOGIN, REGISTER, ME…) when enabled — the list GET, distinct and bulk keys carry the full (usually plural) table name, while the single-record keys (GET_..._BY_ID, ADD/UPDATE/PATCH/DELETE) are singularized. The BULK_* keys are emitted only for entities that set bulk_endpoints_enabled; the BULK_ADD_PRODUCTS above illustrates an opt-in key, not one every entity gets. You can fold in extra endpoints for third-party APIs. See the Ready-Made Actions page for the full catalog of what generateAllEndpoints emits.
1 · Generate the endpoint map#
generateAllEndpoints reads your NucleusConfigOptions and emits a typed record of every endpoint. Merge in extraEndpoints for any non-Nucleus API you also want to call through the same hook. The resulting type is the single source of truth the rest of the client infers from.
1import { generateAllEndpoints } from "nucleus-core-ts";2import nucleusConfig from "@/config.json";3 4const extraEndpoints = {5 GET_WEATHER: {6 method: "GET" as const,7 path: "https://api.weather.com/v1/current",8 isPublic: true,9 _payload: undefined as { city: string } | undefined,10 _success: undefined as { temp: number } | undefined,11 _error: undefined as { message: string } | undefined,12 },13} as const;14 15export const endpoints = generateAllEndpoints(nucleusConfig, extraEndpoints);16export type AllEndpoints = typeof endpoints;generateAllEndpoints(config, extra?)(config, extra) => EndpointsOptionalProduces the endpoint definitions for every entity (CRUD, plus bulk keys only when the entity sets bulk_endpoints_enabled), the built-in system-table routes (always emitted), the admin routes (whenever authentication is enabled), and each feature route you have enabled — auth, cohort, monitoring, verification, the notification actions (emitted by the verification generator, so they require config.verification.enabled === true IN ADDITION to config.notification.enabled && config.notification.endpoints.enabled — enabling the notification flags with verification disabled yields none), chat, tenant, domain and marketplace. The optional second argument lets you register external endpoints with their own payload/success/error generics.
extraEndpointsRecord<string, EndpointDefinition>OptionalManually-typed endpoints for third-party APIs (weather, Slack…). Each declares method, path, isPublic and _payload/_success/_error phantom types so the hook stays fully typed for them too.
Example: GET_WEATHER, SEND_SLACK_MESSAGE
2 · Create the server factory#
createServerFactory binds the endpoints to a runtime: the API base URL, the names of your auth cookies, and adapters that read cookies/headers. It runs server-side so tokens are attached from httpOnly cookies and never touch client JavaScript.
createServerFactory(endpoints, config, getCookies, getHeaders)(…) => FactoryOptionalWires the endpoint map to a transport. The cookie/header adapters bridge Next's async cookies()/headers() so each request is authenticated with the caller's session automatically.
config.baseUrlstringOptionalWhere requests are sent — your API gateway or service URL, usually from process.env.API_BASE_URL.
config.tokenNames{ accessToken?; refreshToken?; sessionToken? }OptionalOptional. The cookie names the factory looks for when attaching credentials — must match your authentication token config. Each field is optional and is merged over the standard defaults (access_token, refresh_token, session_token), so you only override the names you've changed.
access_token / refresh_token / session_tokenconfig.debugbooleanOptionalLog each request/response server-side during development.
false3 · Create & use the hook#
createApiHook(endpoints, factory) returns useApiActions. Every key is an action with .start() and a .state ({ isPending, data, error, code }). start() takes a typed payload plus onAfterHandle/onErrorHandle callbacks — there is no try/catch/await; the callback flow keeps optimistic UI and error handling tidy.
1const actions = useApiActions();2 3const load = () =>4 actions.GET_PRODUCTS.start({5 payload: { page: 1, limit: 20 },6 onAfterHandle: (res) => console.log(res.data.items, res.data.meta.totalItems),7 onErrorHandle: (err, code) => {8 if (code === 401) redirectToLogin();9 console.error(err.message);10 },11 });12 13// in render14{actions.GET_PRODUCTS.state.isPending && <Skeleton />}15{actions.GET_PRODUCTS.state.data?.data.items.map((p) => <Row key={p.id} item={p} />)}actions.KEY.start(options)(options) => voidOptionalFire the call. options.payload is fully typed to that endpoint; onAfterHandle(data) receives the typed success; onErrorHandle(error, code) receives the typed error plus HTTP status.
actions.KEY.state.isPendingbooleanOptionalTrue while the request is in flight — drive spinners/skeletons from this.
actions.KEY.state.dataT | nullOptionalThe last successful, fully-typed response for that endpoint.
actions.KEY.state.errorTError | nullOptionalThe last typed error object for that endpoint (e.g. { message }), or null.
actions.KEY.state.codenumber | nullOptionalHTTP status of the last response (mirrors the code passed to onErrorHandle); null before the first call. On a thrown/network error the failure surfaces via state.error and code retains its previous value.
Related sections