kypi
v0.4.5
Published
Type-safe, ergonomic API client builder for TypeScript & React based on ky.
Maintainers
Readme
import { get, post, client } from 'kypi'
const api = client({
baseUrl: 'https://startrek.example/api',
endpoints: {
starships: {
list: get<void, Starship[]>('/starships'),
get: get<void, Starship, { id: number }>('/starships/:id'),
create: post<StarshipCreate, Starship>('/starships'),
},
},
})
// Usage
const starships = await api.starships.list().json()
const enterprise = await api.starships.get({ id: 1701 }).json()
const newStarship = await api.starships
.create({
name: 'USS Discovery',
class: 'Crossfield',
registry: 'NCC-1031',
})
.json()Table of Contents
- What is kypi?
- Install
- Quickstart
- React Integration
- API Reference
- Advanced Usage
- React Example
- TODO
- Thanks
- License
What is kypi?
kypi is a small, type-safe toolkit for building API clients in TypeScript, with optional React integration.
It's built on top of the fantastic ky HTTP client by Sindre Sorhus (thank you Sindre! 🙏), and aims to make API clients in your apps as delightful as making raw requests with ky.
- Type-safe endpoints: Define your API once, get full type inference everywhere.
- Path params, query, body: All handled, all typed.
- Auth? Just a flag and a token getter (only supports Authorization Bearer for now).
- React? One hook, instant client, no magic.
- Small: Zero dependencies except for
ky(and React, if you use the React hook).
Install
bun add kypi
# or
npm install kypi
# or
yarn add kypi
# or... or... or... or... ERROR: Maximum call stack size exceeded 😝React is an optional peer dependency. You only need it if you use the React hook.
Quickstart
1. Define your endpoints
import { del, get, post, type EndpointGroup } from 'kypi'
const endpoints = {
products: get<void, Array<{ id: number; title: string }>>('/products'),
addProduct: post<{ title: string }, { id: number; title: string }>(
'/products',
),
deleteProduct: del<void, {}, { id: number }>('/products/:id'),
} satisfies EndpointGroup2. Create a client
import { client } from 'kypi'
const api = client({
baseUrl: 'https://fakestoreapi.com',
endpoints,
// Optional: getToken for auth endpoints
getToken: () => localStorage.getItem('token'),
})3. Call your API
// GET (no params)
const products = await api.products().json()
// POST (with body)
const newProduct = await api.addProduct({ title: 'New' })
// DELETE (with path param)
await api.deleteProduct({ params: { id: 42 } })React Integration
Want to use your API client in React, with full type safety and memoization?
Just use the createClientHook:
import { createClientHook } from 'kypi/react'
const useApi = createClientHook(endpoints)
function MyComponent() {
const api = useApi({ baseUrl: 'https://fakestoreapi.com' })
// ...use api.products(), api.addProduct(), etc.
}See playground/src/App.tsx for a real-world example using the Fake Store API.
API Reference
Endpoint Creators
get,post,put,patch,head,del— create endpoints for each HTTP method.aget,apost,aput,apatch,ahead,adel— same, but require authentication.endpoint,authed— low-level endpoint creators.
Each endpoint is fully typed:
- Input: body, query, and/or path params (with type inference).
- Output: response type.
Example
const getUser = get<void, { id: number; name: string }, { id: number }>(
'/users/:id',
)Client
client({ baseUrl, endpoints, getToken?, onError? })— creates a type-safe API client.baseUrl: the base URL for your API.endpoints: an object containing your endpoint definitions.getToken(optional): a function that returns the authentication token.onError(optional): a function called with the error if a request fails. Useful for global error handling (e.g., showing a toast, logging out on 401, etc).
Each endpoint method:
- Accepts input (body/query/params) as the first argument.
- Accepts an optional
kyoptions object as the second argument (for per-request overrides).
Example
const api = client({
baseUrl: 'https://fakestoreapi.com',
endpoints,
onError: (error) => {
if (error.response?.status === 401) {
// handle unauthorized globally
toast("Oops! You're not logged in, intruder!")
logout()
}
},
})
await api.products({ id: 1 }, { headers: { 'X-Foo': 'bar' } })Auth
- Mark an endpoint as
auth: true(or useaget,apost, etc.). - Provide a
getTokenfunction to the client. - The token will be sent as a
Bearerin theAuthorizationheader.
Advanced Usage
Path Params
Endpoints like /users/:id are automatically interpolated:
await api.getUser({ params: { id: 123 } })Query Params
For GET/HEAD, you can pass query params as the input, or as { query: ... }:
await api.products({ search: 'foo' })
await api.products({ query: { search: 'foo' } })Per-request Options
We support passing custom ky options.
await api.products({ search: 'foo' }, { headers: { 'X-Request-ID': 'abc', retry: { ... } } })React Example
Here's a taste of how you might use kypi in a React app:
import { del, get, post } from 'kypi'
import { createClientHook } from 'kypi/react'
const useApi = createClientHook({
products: get<void, Array<{ id: number; title: string }>>('/products'),
addProduct: post<{ title: string }, { id: number; title: string }>(
'/products',
),
deleteProduct: del<void, {}, { id: number }>('/products/:id'),
})
function App() {
const api = useApi({ baseUrl: 'https://fakestoreapi.com' })
// ...use api.products(), api.addProduct(), etc.
}TODO
- [ ] Support for other auth schemes (not just Bearer)
- [ ] Basic Auth
- [ ] API Key in header
- [ ] Custom header
- [ ] Token refresh?
- [ ] Your ideas? PRs and issues welcome!
Thanks
- ky by Sindre Sorhus — the HTTP client that does all the heavy lifting.
- Everyone who builds and shares open source. You rock.
[!NOTE] The
kypilogo is obviously inspired byky’s logo, but since I’m from Argentina, I chose jacaranda blossoms instead of sakura to give it a unique touch—and because I like jacarandas.
License
MIT © Lucas Colombo
With 🧉 from Argentina 🇦🇷
