@elunarlabs/api-client
v0.1.0
Published
Modulare API-Client-Bibliothek für React und Next.js Projekte.
Maintainers
Readme
@elunarlabs/api-client
Modulare TypeScript-Bibliothek zum Aufbau eigener API-Clients für React-, Next.js- und Node.js-Projekte. Der Fokus liegt auf:
- typsicherer Service-Registrierung (
ApiRegistry), - einem flexiblen Fetch-Wrapper (
createFetcher), - konfigurierbaren Interceptors,
- Timeouts, Retries und Next.js-spezifischen Optionen bereits out-of-the-box.
npm install @elunarlabs/api-clientSchnellstart
// src/api/index.ts
import { ApiRegistry, createServiceFetcher, type ServiceFactory } from '@elunarlabs/api-client';
interface UsersService {
list: () => Promise<{ users: { id: string; email: string }[] }>;
}
const usersService: ServiceFactory<UsersService> = (config) => {
const fetcher = createServiceFetcher(config);
return {
async list() {
return fetcher({ path: '/users' });
},
} satisfies UsersService;
};
export const api = new ApiRegistry({
users: usersService({ baseUrl: 'https://api.example.com' }),
});
// irgendwo im Code
const { users } = await api.users.list();Kernkonzepte
createFetcher(config)- Basis-Fetcher mit Retry-, Timeout- und Interceptor-Support.
- Unterstützt
GET,POST,PUT,PATCH,DELETEinkl. JSON-/Form-Body. - Next.js-Fetch-Optionen (
cache,revalidate,tags,next) werden automatisch zusammengeführt.
ApiRegistry&createServiceFetcher- Registriere deine Services (z. B.
backend,billing,analytics) mit konsistentem Fetch-Verhalten. - Typisierte Zugriffe dank generischer Registry.
- Registriere deine Services (z. B.
ApiError- Einheitlicher Fehler für nicht-ok HTTP-Antworten (
status,statusText,body).
- Einheitlicher Fehler für nicht-ok HTTP-Antworten (
Interceptors (
registerGlobalInterceptor, service-spezifisch viaServiceConfig.interceptors)- Hooks für
onRequest,onResponse,onError.
- Hooks für
Services anlegen
// src/services/backend/users.ts
import type { Fetcher } from '@elunarlabs/api-client';
export interface BackendUser {
id: string;
email: string;
}
export const createUsersApi = (fetcher: Fetcher) => ({
getAll: () => fetcher<{ users: BackendUser[] }>({ path: '/users' }),
create: (payload: { email: string }) =>
fetcher<{ user: BackendUser }>({
path: '/users',
method: 'POST',
body: payload,
}),
});// src/services/backend/index.ts
import { createServiceFetcher, type ServiceConfig, type ServiceFactory } from '@elunarlabs/api-client';
import { createUsersApi } from './users';
export interface BackendService {
users: ReturnType<typeof createUsersApi>;
}
export const backendService: ServiceFactory<BackendService> = (config: ServiceConfig) => {
const fetcher = createServiceFetcher(config);
return {
users: createUsersApi(fetcher),
} satisfies BackendService;
};// src/api/registry.ts
import { ApiRegistry } from '@elunarlabs/api-client';
import { backendService } from '../services/backend';
export const api = new ApiRegistry({
backend: backendService({
baseUrl: 'https://backend.example.com',
apiKey: process.env.BACKEND_API_KEY,
customHeaders: { 'x-client-id': 'my-app' },
retry: { attempts: 3, delay: 250 },
timeout: 5000,
}),
});
export type Api = typeof api;Fetch Request Options (FetcherConfig & FetchRequestOptions)
- Authentifizierung:
apiKey,apiKeyHeader,authToken,authScheme. - Timeout: pro Service (
timeout) oder pro Request (fetcher({ timeout: 1000 })). - Retries:
retry.attempts,retry.delay(Zahl oder Funktion(attempt, error) => number). - Next.js:
cache,revalidate,tags,nextwerden aufeinander aufbauend gemerged (Request > Service > Defaults). - Body-Handling: JSON wird automatisch serialisiert. Für
FormData/Blobeinfach direkt übergeben. - Abbruch:
AbortSignalwird mit Timeout-Signal gemerged (signal,requestInit.signal, Service-Defaults).
Fehlerbehandlung
import { ApiError } from '@elunarlabs/api-client';
try {
await api.backend.users.getAll();
} catch (error) {
if (error instanceof ApiError) {
console.log(error.status, error.body);
} else {
console.error('Unbekannter Fehler', error);
}
}- Netzwerkfehler (z. B.
TypeErroroderTimeoutError) werden automatisch erneut versucht, sofernretryaktiv ist. - Timeout-Fehler tragen den Namen
TimeoutError.
Interceptors
import { registerGlobalInterceptor } from '@elunarlabs/api-client';
registerGlobalInterceptor({
async onRequest(request) {
console.debug('request', request);
return request;
},
async onResponse(response) {
if (response.status === 401) {
// Token-Refresh
}
return response;
},
async onError(error) {
// optional custom fallback response
console.error('fetch error', error);
},
});Service-spezifische Interceptors kannst du über ServiceConfig.interceptors hinzufügen. Reihenfolge: globale Interceptors → Service-Interceptors.
Next.js & React Server Components
cache,next,revalidate,tagswerden automatisch an den nativenfetchdurchgereicht.createFetcher()respektiert Request-spezifische Overrides.- Nutzbar in RSCs, Route-Handlern und Edge Functions.
Beispiel (Server Action):
'use server';
import { api } from '../api/registry';
export const createUser = async (formData: FormData) => {
const email = formData.get('email');
return api.backend.users.create({ email: String(email) });
};Tests & Tooling
- Unit-Tests mit
vitest:npm run test - Typprüfung:
npm run typecheck - ESLint:
npm run lint - Build mit
tsup:npm run build
Mocking-Beispiel im Test (src/__tests__/fetcher.test.ts):
const fetchMock = vi.fn<[RequestInfo | URL, RequestInit?], Promise<Response>>(async () => {
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
globalThis.fetch = fetchMock as unknown as typeof fetch;Repository-Layout & GitHub-Hinweise
src/enthält den Bibliotheksquellcode.dist/wird pernpm run builderzeugt und ist das einzige Verzeichnis, das mit veröffentlicht wird (siehepackage.json→files).examples/next-app/ist eine Next.js-15-Playground-App zum lokalen Testen. Sie bleibt dank.gitignoreundfiles-Konfiguration vom NPM-Paket ausgeschlossen.- Vor Pushes/Publishes empfehlenswert:
npm run lint,npm run typecheck,npm run test,npm run build, optionalnpm packzur Kontrolle des Tarballs. - Für GitHub reicht ein
git add .(ohnenode_modules/oder.next/, siehe.gitignore) und ein Commit wiegit commit -m "feat: prepare api client".
Playground starten
cd examples/next-app
npm install
npm run devAnschließend ist die Demo unter http://localhost:3000 verfügbar.
Troubleshooting
- Unhandled TimeoutError in Tests: In Tests
vi.useFakeTimers()nutzen und Timer mitawait vi.advanceTimersByTimeAsync(ms)vor Zurückstellen der Real-Timer vorwärts bewegen. - TypeScript-Version: Das Linting erwartet eine TS-Version
<5.6. Stelle sicher, dass deinepackage.jsoneine kompatible Version enthält (^5.5.x). - Fetch wird nicht aufgerufen: Prüfe, ob
baseUrlkorrekt gesetzt ist undpathmit/beginnt. - Retries greifen nicht: Verifiziere
retry.attempts > 1und dass Fehler alsTypeErroroderTimeoutErrorgeworfen werden (HTTP-Fehler zählen nicht als Retry-Kandidaten).
API-Referenz (Kurzüberblick)
createFetcher(config: FetcherConfig): FetchercreateServiceFetcher(config: ServiceConfig): FetcherApiRegistry/createApiRegistryApiErrorregisterGlobalInterceptor(interceptor: FetchInterceptor)clearGlobalInterceptors()- Types:
Fetcher,FetcherConfig,FetchRequestOptions,FetchInterceptor,ServiceFactory,ServiceConfig
