@liha-labs/apizel
v0.3.0
Published
Lightweight fetch-based API client for Web + React Native.
Downloads
275
Readme
apizel
Thin fetch wrapper for TypeScript.
標準の fetch に、ちょうどいい薄皮。
- ✅ Small & predictable: 余計な抽象化なし
- ✅ TanStack Query friendly:
signalを尊重、timeoutMsは Abort で中断 - ✅ Minimal axios-like body: FormData をそのまま、JSON は
Content-Type補完 - ✅ 401 refresh retry: refresh は single-flight、リトライは 1 回だけ
- ✅ Observe hooks:
onRequest/onResponseは観測のみ
Status: v0.1.2 Design goal: “軽く、標準に沿い、必要なら捨てられる”。
Install
pnpm add @liha-labs/apizel
# npm i @liha-labs/apizel
# yarn add @liha-labs/apizelQuick Start
import { apizel } from '@liha-labs/apizel'
type Me = { id: string; name: string }
const api = apizel({
baseURL: 'https://api.example.com',
})
const me = await api.get<Me>('/v1/me')
console.log(me)Extend Client
共通設定を保ちつつ、baseURL などだけを差し替えた派生クライアントを作れます。
const api = apizel(common)
const usersApi = api.extend({ baseURL: USERS_URL })
const billingApi = api.extend({ baseURL: BILLING_URL })
await usersApi.get('/me')
await billingApi.post('/invoices', body)Usage
Request options
await api.get('/v1/items', {
params: { q: 'hello', tag: ['a', 'b'] }, // => tag=a&tag=b
timeoutMs: 10_000, // <= 0 disables timeout
signal, // AbortSignal (TanStack Query compatible)
headers: { 'X-Trace-Id': '...' },
})Body (minimal axios-like)
// JSON (auto sets Content-Type if missing)
await api.post('/v1/items', { name: 'apple' })
// FormData (Content-Type is NOT touched)
const fd = new FormData()
fd.append('file', file)
await api.post('/v1/upload', fd)Errors
Non-2xx responses throw HttpError.
import { HttpError } from '@liha-labs/apizel'
try {
await api.get('/v1/me')
} catch (e) {
if (e instanceof HttpError) {
console.log(e.status, e.data, e.url)
}
}Note: abort/timeout errors are not converted to
HttpError. You’ll receive the nativeAbortError(or environment equivalent).
Auth Token
const api = apizel({
baseURL: 'https://api.example.com',
getAccessToken: () => localStorage.getItem('token'),
shouldAttachToken: ({ endpoint }) => !endpoint.startsWith('/auth/'),
})Refresh on 401
- refresh is single-flight
- retry is once
const api = apizel({
baseURL: 'https://api.example.com',
getAccessToken: async () => getToken(),
refresh: async () => {
const newToken = await refreshToken()
return newToken
},
onRefreshFailed: async () => {
// logout / clear tokens / notify...
},
})Hooks (observe only)
const api = apizel({
baseURL: 'https://api.example.com',
onRequest: ({ method, url, init }) => {
// observe only (no behavior changes)
console.log('[req]', method, url, init)
},
onResponse: ({ method, url, response }) => {
console.log('[res]', method, url, response.status)
},
})Documentation
- Documentation: https://apizel.liha.dev/
- Repository: https://github.com/liha-labs/apizel
License
MIT © 2026 Liha Labs
