Fetcher & Transports
createFetcher, custom transports (axios), and serializers (superjson).
@dudousxd/nestjs-client provides createFetcher — the typed client the generated
api.ts calls. It owns URL building, headers, error mapping (ApiHttpError), and the
payload transformer. The actual network call is a pluggable transport.
import { createFetcher } from '@dudousxd/nestjs-client';
const fetcher = createFetcher({
baseUrl: '/api',
headers: () => ({ authorization: `Bearer ${getToken()}` }),
});Bring your own HTTP client
By default the transport is native fetch. Pass transport to use anything else.
Use your existing axios instance
import axios from 'axios';
import { createFetcher, axiosTransport } from '@dudousxd/nestjs-client';
const http = axios.create({ baseURL: '/api', withCredentials: true });
const fetcher = createFetcher({ transport: axiosTransport(http) });Set the base URL on the axios instance (not createFetcher.baseUrl) so it isn't
prefixed twice.
A fully custom transport
A Transport takes a normalized request and returns a normalized response:
import type { Transport } from '@dudousxd/nestjs-client';
const transport: Transport = async (req) => {
const res = await myClient(req.url, { method: req.method, headers: req.headers, body: req.body });
return {
ok: res.ok,
status: res.status,
statusText: res.statusText,
contentType: res.headers.get('content-type'),
text: () => res.text(),
};
};
createFetcher({ transport });superjson & transformer pipelines
A transformer is a { stringify, parse } pair. Pass superjson to round-trip rich
types (Date, Map, Set, BigInt) — the server must use the same transformer.
import superjson from 'superjson';
import { createFetcher } from '@dudousxd/nestjs-client';
const fetcher = createFetcher({ transformer: superjson });You can pass an array to compose a pipeline: a base value↔string serializer first, then string↔string wrappers (compression, encryption) applied in order and unwound on parse.
import superjson from 'superjson';
import { createFetcher } from '@dudousxd/nestjs-client';
import { compress } from './my-compress'; // { stringify, parse } over strings
const fetcher = createFetcher({ transformer: [superjson, compress] });Bring your own — a transformer is just an object that implements
{ stringify(value): string; parse(text): T }.
superjson runtime
The transformer above replaces serialization on both directions for every
consumer — the server must speak the exact same transformer, so adopting it is an atomic
cross-app flip. When you only need to revive rich types in responses (so Date,
Map, Set, BigInt round-trip) and want each client to opt in independently, use the
dedicated @dudousxd/nestjs-client/superjson subpath instead.
It is built on the fetcher's generic deserialize hook (applied to parsed JSON
responses) and a x-superjson header so plain-JSON consumers are never affected:
- Client sends
x-superjson: 1and deserializes the response withsuperjson. - Server
SuperjsonInterceptorsuperjson-serializes the response only when that header is present; every other request gets plain JSON.
This is the runtime complement to the
serialization: 'superjson' config:
turn off the compile-time Jsonify wrapping there, and revive the values at runtime here.
superjson, rxjs, and @nestjs/common are optional peer dependencies, pulled in
only by the /superjson subpath. Install superjson to use this runtime.
Opt the client in
superjsonFetcherOptions() returns the headers + deserialize pair to spread into
createFetcher:
import { createApi } from '../generated/api';
import { createFetcher } from '@dudousxd/nestjs-client';
import { superjsonFetcherOptions } from '@dudousxd/nestjs-client/superjson';
export const api = createApi(
createFetcher({ baseUrl: '/api', ...superjsonFetcherOptions() }),
);Already passing your own headers() (e.g. auth)? Use withSuperjson() — it composes your
headers with the x-superjson opt-in header so both are sent:
import { createFetcher } from '@dudousxd/nestjs-client';
import { withSuperjson } from '@dudousxd/nestjs-client/superjson';
const fetcher = createFetcher(
withSuperjson({ baseUrl: '/api', headers: () => ({ authorization: token() }) }),
);Add the server interceptor
Register SuperjsonInterceptor so responses are superjson-serialized only for
requests carrying the opt-in header. Plain-JSON consumers (and any app that hasn't flipped
yet) are untouched, so superjson can be adopted per-consumer without an atomic migration:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { SuperjsonInterceptor } from '@dudousxd/nestjs-client/superjson';
@Module({
providers: [{ provide: APP_INTERCEPTOR, useClass: SuperjsonInterceptor }],
})
export class AppModule {}Drop the compile-time Jsonify wrapping
With responses revived at runtime, the raw controller return types are now correct. Set
serialization: 'superjson' so the codegen stops wrapping response in Jsonify<...>:
export default defineConfig({
// ...
serialization: 'superjson',
});Now api.users.show() resolves to { createdAt: Date } again — and the value really is a
Date at runtime.