Skip to Content
Adding an API Service

Adding an API Service

How to add a new API domain with auto-generated query and mutation hooks.

1. Create the service file

# In packages/api/services/energy/ touch my-domain.service.ts

2. Define types

// packages/api/services/energy/my-domain.service.ts import { createQueryGroup, createMutationGroup, queryEndpoint, paginatedQueryEndpoint, mutationEndpoint, } from "../../helpers"; export interface TItem { id: string; name: string; status: string; createdAt: string; } export interface TItemCreateReq { name: string; status: string; } export interface TItemsListParams { search?: string; status?: string; }

3. Define queries

export const { useGetItemsQuery, useGetItemQuery } = createQueryGroup({ getItems: paginatedQueryEndpoint<TItem, TItemsListParams>({ url: "/energy/items", paginated: { uniqueBy: "id" }, }), getItem: queryEndpoint<TItem, { id: string }>({ url: ({ id }) => `/energy/items/${id}`, }), });

4. Define mutations

export const { useCreateItemMutation, useUpdateItemMutation, useDeleteItemMutation, } = createMutationGroup({ createItem: mutationEndpoint<TItem, TItemCreateReq>({ url: "/energy/items", method: "POST", addCreatedToPaginatedList: { listQueryKey: "getItems", selectListItem: (data) => data, }, }), updateItem: mutationEndpoint<TItem, Partial<TItemCreateReq>, { id: string }>({ url: ({ id }) => `/energy/items/${id}`, method: "PATCH", mergeUpdatedInPaginatedList: { listQueryKey: "getItems", selectListItem: (data) => data, }, }), deleteItem: mutationEndpoint<void, void, { id: string }>({ url: ({ id }) => `/energy/items/${id}`, method: "DELETE", removeFromPaginatedList: { listQueryKey: "getItems", getDeletedId: (variables) => (variables as { params: { id: string } }).params.id, }, }), });

5. Export from the domain index

// packages/api/services/energy/index.ts export * from "./my-domain.service";

6. Add sub-path export (if needed)

If the service is in a new directory, add the export path to packages/api/package.json:

{ "exports": { "./services/energy": "./services/energy/index.ts" } }

7. Use in components

"use client"; import { useGetItemsQuery, useCreateItemMutation } from "@numa/api/services/energy"; export default function ItemsList() { const { data, isLoading } = useGetItemsQuery({ search: "" }); const { mutate: createItem, isPending } = useCreateItemMutation({ onSuccess: () => toast.success("Item created"), }); return ( <> {data?.items.map((item) => ( <div key={item.id}>{item.name}</div> ))} <button onClick={() => createItem({ body: { name: "New", status: "active" } })}> Add </button> </> ); }

Hook naming convention

The key name becomes the hook name automatically:

KeyGenerated hook
getItemsuseGetItemsQuery
getItemuseGetItemQuery
createItemuseCreateItemMutation
updateItemuseUpdateItemMutation
deleteItemuseDeleteItemMutation

Pattern: use + PascalCase(key) + Query or Mutation.

Cache invalidation

For cases where automatic list cache updates aren’t sufficient:

import { useQueryClient } from "@tanstack/react-query"; const queryClient = useQueryClient(); const { mutate } = useCreateItemMutation({ onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["getItems"] }); }, });

Query keys use the endpoint key name, not the URL.

Last updated on