create-api-endpoint
스타4
포크0
업데이트2025년 12월 28일 07:09
Django REST APIへのプロキシエンドポイントとそれを使用するComposableを作成する手順
설치
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SKILL.md
readonly메뉴
Django REST APIへのプロキシエンドポイントとそれを使用するComposableを作成する手順
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | create-api-endpoint |
| description | Django REST APIへのプロキシエンドポイントとそれを使用するComposableを作成する手順 |
このスキルは、「いぬいのうた」プロジェクトでDjango REST APIへのプロキシエンドポイントと、それを使用するComposableを作成する標準パターンを提供します。
クライアント → Composable → Nuxt Server API (プロキシ) → Django REST API
なぜプロキシ層が必要か:
// server/api/resource/index.get.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const query = getQuery(event);
try {
const response = await fetch(
`${config.djangoApiUrl}/resource/?${new URLSearchParams(query as any)}`
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
} catch (error) {
console.error('API Error:', error);
throw createError({
statusCode: 500,
message: 'Failed to fetch data'
});
}
});
// server/api/resource/[id].get.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const id = getRouterParam(event, 'id');
if (!id) {
throw createError({
statusCode: 400,
message: 'ID is required'
});
}
try {
const response = await fetch(
`${config.djangoApiUrl}/resource/${id}/`
);
if (!response.ok) {
throw createError({
statusCode: response.status,
message: `Resource not found: ${id}`
});
}
return response.json();
} catch (error) {
console.error('API Error:', error);
throw createError({
statusCode: 500,
message: 'Failed to fetch resource'
});
}
});
// server/api/resource/index.post.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const body = await readBody(event);
try {
const response = await fetch(
`${config.djangoApiUrl}/resource/`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}
);
if (!response.ok) {
throw createError({
statusCode: response.status,
message: 'Failed to create resource'
});
}
return response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
});
// server/api/resource/[id].put.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const id = getRouterParam(event, 'id');
const body = await readBody(event);
try {
const response = await fetch(
`${config.djangoApiUrl}/resource/${id}/`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}
);
if (!response.ok) {
throw createError({
statusCode: response.status,
message: 'Failed to update resource'
});
}
return response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
});
// server/api/resource/[id].delete.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const id = getRouterParam(event, 'id');
try {
const response = await fetch(
`${config.djangoApiUrl}/resource/${id}/`,
{
method: 'DELETE',
}
);
if (!response.ok) {
throw createError({
statusCode: response.status,
message: 'Failed to delete resource'
});
}
return { success: true };
} catch (error) {
console.error('API Error:', error);
throw error;
}
});
// app/types/api.ts
// 検索パラメータ
export interface ResourceSearchParams {
search?: string;
page?: number;
page_size?: number;
ordering?: string;
}
// レスポンス型
export interface Resource {
id: string;
name: string;
description: string;
created_at: string;
updated_at: string;
}
// ページネーション付きレスポンス
export interface PaginatedResponse<T> {
count: number;
next: string | null;
previous: string | null;
results: T[];
}
// 作成・更新用の型
export interface CreateResourceInput {
name: string;
description: string;
}
export interface UpdateResourceInput extends Partial<CreateResourceInput> {
id: string;
}
// composables/useResources.ts
import type {
Resource,
ResourceSearchParams,
PaginatedResponse,
CreateResourceInput,
UpdateResourceInput
} from '~/types/api';
export const useResources = () => {
const resources = ref<Resource[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
const totalCount = ref(0);
// 一覧取得
const fetchResources = async (params?: ResourceSearchParams) => {
loading.value = true;
error.value = null;
try {
const data = await $fetch<PaginatedResponse<Resource>>('/api/resource', {
query: params,
});
resources.value = data.results;
totalCount.value = data.count;
} catch (e) {
error.value = 'データの取得に失敗しました';
console.error(e);
} finally {
loading.value = false;
}
};
// ID指定取得
const fetchResource = async (id: string): Promise<Resource | null> => {
loading.value = true;
error.value = null;
try {
const data = await $fetch<Resource>(`/api/resource/${id}`);
return data;
} catch (e) {
error.value = 'データの取得に失敗しました';
console.error(e);
return null;
} finally {
loading.value = false;
}
};
// 作成
const createResource = async (input: CreateResourceInput): Promise<Resource | null> => {
loading.value = true;
error.value = null;
try {
const data = await $fetch<Resource>('/api/resource', {
method: 'POST',
body: input,
});
return data;
} catch (e) {
error.value = '作成に失敗しました';
console.error(e);
return null;
} finally {
loading.value = false;
}
};
// 更新
const updateResource = async (input: UpdateResourceInput): Promise<Resource | null> => {
loading.value = true;
error.value = null;
try {
const data = await $fetch<Resource>(`/api/resource/${input.id}`, {
method: 'PUT',
body: input,
});
return data;
} catch (e) {
error.value = '更新に失敗しました';
console.error(e);
return null;
} finally {
loading.value = false;
}
};
// 削除
const deleteResource = async (id: string): Promise<boolean> => {
loading.value = true;
error.value = null;
try {
await $fetch(`/api/resource/${id}`, {
method: 'DELETE',
});
return true;
} catch (e) {
error.value = '削除に失敗しました';
console.error(e);
return false;
} finally {
loading.value = false;
}
};
return {
resources,
loading,
error,
totalCount,
fetchResources,
fetchResource,
createResource,
updateResource,
deleteResource,
};
};
<script setup lang="ts">
const { resources, loading, error, fetchResources } = useResources();
// ページマウント時にデータ取得
onMounted(() => {
fetchResources({ page: 1, page_size: 20 });
});
</script>
<template>
<div>
<div v-if="loading">読み込み中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else>
<div v-for="resource in resources" :key="resource.id">
{{ resource.name }}
</div>
</div>
</div>
</template>
プロキシエンドポイントでは必ず useRuntimeConfig() を使用:
const config = useRuntimeConfig();
const djangoApiUrl = config.djangoApiUrl; // server側のみアクセス可能
createError() でHTTPエラーを返すconst query = getQuery(event);
// query は Record<string, string | string[]> 型
// 型安全に変換
const params = {
search: typeof query.search === 'string' ? query.search : undefined,
page: query.page ? Number(query.page) : 1,
};
API エンドポイント作成完了時に確認:
server/api/ にプロキシエンドポイント作成runtimeConfig.djangoApiUrl を使用app/types/ に作成app/composables/ に作成