REST, GraphQL and realtime through interchangeable drivers.
The problem
Section titled “The problem”A typical app ends up with three different data layers: a REST client here, a GraphQL client there, raw WebSocket handling for realtime. Each has its own error handling and its own shape. Swap one backend and the business code that calls it has to change too.
const res = await fetch(`/api/products?${qs}`)if (!res.ok) throw new Error(`HTTP ${res.status}`)const products = await res.json()
const { data, errors } = await graphqlClient.query({ query: PRODUCTS_QUERY })if (errors) console.error(errors)
const channel = supabase.channel('messages').on('postgres_changes', {}, handler).subscribe()await sdk.lomkit.products.get({})await sdk.nhost.products.query({})sdk.supabase.messages.subscribe({}, handler)Business code declares resources. The protocol behind them (Laravel/Lomkit, Hasura, Supabase) becomes an interchangeable detail.
Quickstart
Section titled “Quickstart”nexdk is not published to npm yet. Clone the repository and run pnpm install && pnpm build.
import { createSdk, defineResource } from 'nexdk'import { LomkitDriver } from '@nexdk/driver-lomkit'import { z } from 'zod'
const product = defineResource({ name: 'products', schema: z.object({ id: z.number(), name: z.string(), price: z.number() }),})
export const sdk = createSdk({ drivers: { lomkit: new LomkitDriver({ baseUrl: 'https://api.example.com' }) }, resources: { lomkit: { products: product } },})
const { data, error } = await sdk.lomkit.products.get({ search: { filters: [{ field: 'price', operator: '<', value: 100 }] },})Once error is checked, data.data is typed as { id: number; name: string; price: number }[]. A call never throws unless you pass throw: true to createSdk.
One call grammar
sdk.[driver].[resource].[method](payload?, onEvent?, extra?): the same shape for every backend.
Validator-agnostic
Resources use any Standard Schema validator: Zod, Valibot, ArkType.
One driver contract
The core emits the request, success and error events, once per call. Authors write only the protocol translation.
Zero-dependency core
nexdk depends on nothing. Drivers and hooks peer-depend on it.
What it does not do
Section titled “What it does not do”nexdk is a call grammar and a driver contract, not a cache. It does not dedupe requests or revalidate on focus. Use TanStack Query or SWR on top of a driver call if you need that.
Ecosystem
Section titled “Ecosystem”| Project | Role |
|---|---|
| neXdk | One resource grammar for REST, GraphQL and WebSocket backends. |
| beepr | Typed notifications and debug output. |
| shamock | Mock fetch without any dependency. |
| dialectr | Universal query builder (lomkit, PocketBase, Strapi). |
Each works alone. See Ecosystem to combine them.
