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_PRODUCT, ADD_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT, BULK_ADD_PRODUCTS…) plus auth routes (LOGIN, REGISTER, ME…) when enabled — and 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.

lib/api/factory.ts — generate + merge
1import { generateAllEndpoints } from "nucleus-core";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) => EndpointsOptional

Produces the endpoint definitions for every entity (CRUD + bulk) and every enabled auth/admin/monitoring/tenant route. The optional second argument lets you register external endpoints with their own payload/success/error generics.

extraEndpointsRecord<string, EndpointDefinition>Optional

Manually-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)(…) => FactoryOptional

Wires 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.baseUrlstringOptional

Where requests are sent — your API gateway or service URL, usually from process.env.API_BASE_URL.

config.tokenNames{ accessToken; refreshToken; sessionToken }Optional

The cookie names the factory looks for when attaching credentials — must match your authentication token config.

config.debugbooleanOptional

Log each request/response server-side during development.

Defaultfalse

3 · Create & use the hook#

createApiHook(endpoints, factory) returns useApiActions. Every key is an action with .start() and a .state ({ isPending, data, error }). 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.

components — typed actions, callback flow
1const actions = useApiActions();2 3const load = () =>4  actions.GET_PRODUCT.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_PRODUCT.state.isPending && <Skeleton />}15{actions.GET_PRODUCT.state.data?.data.items.map((p) => <Row key={p.id} item={p} />)}
actions.KEY.start(options)(options) => voidOptional

Fire 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.isPendingbooleanOptional

True while the request is in flight — drive spinners/skeletons from this.

actions.KEY.state.dataT | nullOptional

The last successful, fully-typed response for that endpoint.

actions.KEY.state.errorError | nullOptional

The last typed error for that endpoint, or null.

Related sections