| name | gif-api |
| description | Creates API service functions and TanStack Query hooks for the GIF project following project conventions (apiClient from @repo/lib, array queryKey, useQuery/useMutation v5 patterns, Sonner feedback). Use when adding or updating API integrations without building the UI. Triggers on: 'API ์ฐ๋', 'axios ์๋น์ค ๋ง๋ค์ด์ค', 'useQuery ๋ง๋ค์ด์ค', 'useMutation ์ถ๊ฐ', 'API hook ๋ง๋ค์ด์ค', 'service ํจ์', 'TanStack Query', 'ํ
๋ง๋ค์ด์ค', 'API ํจ์ ์ถ๊ฐ'. Does NOT trigger when also building a page or component โ use gif-feature for that. |
GIF API Skill
File Locations (FSD)
apps/{admin|client}/src/entities/{domain}/
โโโ model/
โ โโโ types.ts โ TypeScript interfaces
โโโ api/
โโโ {domain}Api.ts โ Axios service functions
โโโ use{Domain}.ts โ TanStack Query hooks
User-action-specific API โ src/features/{action}/api/ instead.
Types shared across apps โ packages/lib/src/types/, export from packages/lib/src/index.ts.
Types
export interface {Resource} {
id: number;
createdAt: string;
updatedAt: string;
}
export interface Create{Resource}Dto {
}
export interface Update{Resource}Dto extends Partial<Create{Resource}Dto> {}
Service Functions
Always use apiClient from @repo/lib. Destructure data from the response.
import { apiClient } from "@repo/lib";
import type { Resource, CreateResourceDto, UpdateResourceDto } from "../model/types";
export const get{Resource}s = async (): Promise<{Resource}[]> => {
const { data } = await apiClient.get<{Resource}[]>("/{resources}");
return data;
};
export const get{Resource} = async (id: number): Promise<{Resource}> => {
const { data } = await apiClient.get<{Resource}>(`/{resources}/${id}`);
return data;
};
export const create{Resource} = async (dto: Create{Resource}Dto): Promise<{Resource}> => {
const { data } = await apiClient.post<{Resource}>("/{resources}", dto);
return data;
};
export const update{Resource} = async (id: number, dto: Update{Resource}Dto): Promise<{Resource}> => {
const { data } = await apiClient.patch<{Resource}>(`/{resources}/${id}`, dto);
return data;
};
export const delete{Resource} = async (id: number): Promise<void> => {
await apiClient.delete(`/{resources}/${id}`);
};
TanStack Query Hooks (v5)
"use client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import {
get{Resource}s,
get{Resource},
create{Resource},
update{Resource},
delete{Resource},
} from "./{domain}Api";
export const use{Resource}List = () =>
useQuery({
queryKey: ["{resources}"],
queryFn: get{Resource}s,
});
export const use{Resource} = (id: number) =>
useQuery({
queryKey: ["{resources}", id],
queryFn: () => get{Resource}(id),
enabled: !!id,
});
export const useCreate{Resource} = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: create{Resource},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["{resources}"] });
toast.success("{Resource}๊ฐ ์์ฑ๋์์ต๋๋ค.");
},
onError: () => toast.error("{Resource} ์์ฑ์ ์คํจํ์ต๋๋ค."),
});
};
export const useUpdate{Resource} = (id: number) => {
const qc = useQueryClient();
return useMutation({
mutationFn: (dto: Update{Resource}Dto) => update{Resource}(id, dto),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["{resources}", id] });
qc.invalidateQueries({ queryKey: ["{resources}"] });
toast.success("{Resource}๊ฐ ์์ ๋์์ต๋๋ค.");
},
onError: () => toast.error("{Resource} ์์ ์ ์คํจํ์ต๋๋ค."),
});
};
export const useDelete{Resource} = () => {
const qc = useQueryClient();
return useMutation({
mutationFn: delete{Resource},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["{resources}"] });
toast.success("{Resource}๊ฐ ์ญ์ ๋์์ต๋๋ค.");
},
onError: () => toast.error("{Resource} ์ญ์ ์ ์คํจํ์ต๋๋ค."),
});
};
queryKey Convention
| Scope | Shape | Example |
|---|
| List | ["{resources}"] | ["projects"] |
| Single | ["{resources}", id] | ["projects", 3] |
| Filtered | ["{resources}", "list", filters] | ["projects", "list", { status: "submitted" }] |
| Nested | ["{parent}", parentId, "{child}"] | ["teams", 1, "members"] |
Important Rules
- Never import bare
axios โ always use apiClient from @repo/lib
- Always use array queryKey
- Always type the generic on the apiClient call:
apiClient.get<Type>(...)
- Hook files need
"use client" at the top because they use TanStack Query hooks
- Service files do NOT need
"use client" โ they are plain async functions