name: add-api-endpoint
description: Wire a new backend endpoint end-to-end — types in types/.ts, axios wrapper in lib/api/.ts, React Query hook in hooks/use.ts. Use when the user mentions a new endpoint, fetch, submit, or backend integration.
Add an API Endpoint
Inputs
- HTTP method + path (e.g.,
GET /events/:id/participants,POST /promo-codes) - Request payload shape (if any)
- Response shape
- Whether it's a query (read) or mutation (write)
- Cache invalidation targets (which
queryKeyprefixes should refresh on mutation success)
Procedure
Read the canonical templates first:
lib/api/events.ts(axios wrapper)hooks/useEvents.ts(React Query hook + keys factory)types/event.ts(types)reference/query-template.ts.txtin this skill (compact template)
Add types in
types/<domain>.ts:- Request payload:
CreateFooRequest,UpdateFooRequest - Response entity:
Foo - For paginated lists, use
PaginatedResponse<Foo>fromtypes/api.ts
- Request payload:
Add axios wrapper in
lib/api/<domain>.ts:import apiClient from "./client"; import type { Foo, CreateFooRequest } from "@/types/foo"; import type { PaginatedResponse } from "@/types/api"; export interface GetFoosParams { /* ... */ } export const getFoos = async ( params: GetFoosParams = {} ): Promise<PaginatedResponse<Foo>> => { const response = await apiClient.get<PaginatedResponse<Foo>>("/foos", { params, }); return response.data; };- Never create a new
axios.create. - Never import
axiosdirectly here — go throughapiClient.
- Never create a new
Add React Query hook in
hooks/use<Domain>.ts:- Define the keys factory at the top:
export const fooKeys = { all: ["foos"] as const, lists: () => [...fooKeys.all, "list"] as const, list: (params: GetFoosParams) => [...fooKeys.lists(), params] as const, details: () => [...fooKeys.all, "detail"] as const, detail: (id: string) => [...fooKeys.details(), id] as const, }; - Read hooks:
useQuery({ queryKey: fooKeys.list(params), queryFn: () => getFoos(params), staleTime: 1000 * 60 * 5 }) - Mutations: invalidate via
queryClient.invalidateQueries({ queryKey: fooKeys.lists() })on success; toast viashowSuccessToast/ errors viahandleApiErrorfromlib/error-handler.
- Define the keys factory at the top:
Run
npm run type-checkto catch drift.Add a test for the wrapper (use the
write-vitest-testskill — axios-mock-adapter pattern).
File shape
See reference/query-template.ts.txt.
Anti-patterns
- Direct
fetch/ newaxios.create - Inline
queryKey: ["foo", id]instead of afooKeysfactory - Mutation
onSuccessthat doesn't invalidate the right prefix - Toast-spam from both the hook AND the component — toasts live in the hook
- Re-declaring types that already exist in
types/