Skip to content

REST, GraphQL and realtime through interchangeable drivers.

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()

Business code declares resources. The protocol behind them (Laravel/Lomkit, Hasura, Supabase) becomes an interchangeable detail.

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.

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.

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.