CRUD Operations
Problem
You need to perform the full create, read, update, delete lifecycle against a REST resource with typed request and response bodies, sharing a single base URL configuration.
Solution
ts
import { createApi, createMutation, createQuery } from '@vielzeug/fetchit';
const api = createApi({ baseUrl: 'https://api.example.com' });
const qc = createQuery({ staleTime: 5_000 });
// READ — cached
const users = await qc.query({
key: ['users'],
fn: ({ signal }) => api.get<User[]>('/users', { signal }),
});
// READ one
const user = await qc.query({
key: ['users', 1],
fn: ({ signal }) => api.get<User>('/users/{id}', { params: { id: 1 }, signal }),
});
// CREATE
const addUser = createMutation((input: NewUser, signal: AbortSignal) =>
api.post<User>('/users', { body: input, signal }),
);
const created = await addUser.mutate({ name: 'Alice', email: 'alice@example.com' });
qc.set(['users', created.id], created);
qc.invalidate(['users']);
// UPDATE
const updateUser = createMutation((input: { id: number } & Partial<User>, signal: AbortSignal) => {
const { id, ...patch } = input;
return api.put<User>('/users/{id}', { params: { id }, body: patch, signal });
});
const updated = await updateUser.mutate({ id: 1, name: 'Alice Smith' });
qc.set(['users', updated.id], updated);
// DELETE
const deleteUser = createMutation((input: number, signal: AbortSignal) =>
api.delete('/users/{id}', { params: { id: input }, signal }),
);
await deleteUser.mutate(1);
qc.invalidate(['users']);Pitfalls
- Query keys must be stable across renders. Building them with
Date.now()or random values bypasses the cache and triggers a fresh fetch on every call. mutation.run()does not automatically invalidate related queries. Callquery.invalidate()orquery.refresh()after a successful mutation to reflect the server change.DELETEresponses often return 204 with no body. Attempting to parse an empty body as JSON throws. Handle the no-content case explicitly before parsing.