Next.js app/apiの使い方
Published on November 15, 2024
Category: Next.js
はじめに
外部連携のAPIと独自実装のAPIの実装の違い・管理方法について思考の整理をするためにこの記事を書きました。
結論
- 外部APIを連携する場合、app/apiを介さずに下記のディレクトリ構成の場合 、feature配下にAPI実行するためのメソッドを定義、Hooks配下で呼び出し、page.tsxから参照して実行する。
- 独自のAPIを実装する場合、app/api配下にAPIを処理内容を実装して、上記の流れとあとは同様
ディクトリ構成
├── 📂 apps
│ ├── 📂 api
│ └── 📂 Blog
│ └── 📄 route.ts
│ ├── 📄 page.tsx
│ └── 📂 blog
│ └── 📂 [id]
│ └── 📄 page.tsx
│
├── 📂 hooks // featuresで定義されているAPIを参照してhooksで実行できるようように定義
│ └── 📂 blog
│ └── 📄 index.ts
│
├── 📂 features // 外部連携しているAPIを定義
│ └── 📂 blog
│ └── 📄 index.ts
│
└── 📂 utils // microCMSのClientを定義
└── 📂 microcms
└── 📄 client.ts
app/apiの認識
- 独自のAPIを実装する場合、app/app配下にAPIを実装して、「API実装手順 - 詳細」の流れで実装
API実装手順 - 詳細
- app/api/blog/routes.ts
- 独自のエンドポイントを実装したい時、上記のように任意のエンドポイント作成。
import { NextResponse, NextRequest } from 'next/server'
// リクエストカウントを保持するための変数
// Note: このアプローチはサーバーの再起動時にリセットされます
let responseCount = 0
// カウンターをインクリメントするための関数
const incrementCounter = () => {
responseCount += 1
return responseCount
}
export async function GET(request: NextRequest) {
try {
// リクエストカウントをインクリメント
const currentCount = incrementCounter()
return NextResponse.json({
success: true,
count: currentCount,
message: 'Request processed successfully',
timestamp: new Date().toISOString()
})
} catch (error) {
// エラーハンドリング
console.error('Error processing request:', error)
return NextResponse.json(
{
success: false,
error: 'Internal server error',
message: error instanceof Error ? error.message : 'Unknown error occurred'
},
{ status: 500 }
)
}
}
- features/blog/index.ts
- 1で実装したエンドポイントをfeatures/blogで実行する処理を定義
※エンドポイントをfeatures/blog任意のディレクトリでOK、本サンプルではすでに’実装されている箇所で呼び出しているだけです。
import { client } from '@/utils/microCms/client';
import { Blog } from '@/types/blog';
import { GET as GetBLogsAPITest } from '@/app/api/blog/route';
const getBlogDetail = async (id: string) => {
try {
const data = await client.get({ endpoint: `blogs/${id}` });
return data;
} catch (error) {
console.error('Error fetching blog:', error);
throw error;
}
};
const getBlogs = async () => {
try {
const data = await client.get({ endpoint: "blogs" });
return data.contents;
} catch (error) {
console.error('Error fetching blogs:', error);
throw error;
}
};
const getApiTest = async () => {
try {
const blogs = await GetBLogsAPITest('test' as any); // APIを呼び出し
console
const data = await blogs.json(); // レスポンスをJSONとして解析
// console.log(data);
return data.message;
} catch (error) {
console.error('Error fetching blogs:', error);
}
};
export { getBlogDetail, getBlogs, getApiTest };
- hooks配下でカスタムフックを作成
// hooks/blog/index.ts
import { useEffect, useState } from 'react';
import { getBlogs, getBlogDetail, getApiTest } from '@/features/blog';
export function useBlogs() {
const [blogArticles, setBlogArticles] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchBlogs() {
try {
const blogs = await getBlogs();
setBlogArticles(blogs);
} catch (error) {
console.error('Error fetching blogs:', error);
} finally {
setLoading(false);
}
}
useEffect(() => {
fetchBlogs();
}, []);
return { blogArticles, loading };
}
export function useBlogDetail(id: string | string[]) {
const [blog, setBlog] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
async function fetchBlogDetail() {
try {
const data = await getBlogDetail(id as string);
setBlog(data);
} catch (error: any) {
setError(error.message);
} finally {
setLoading(false);
}
}
useEffect(() => {
if (id) {
fetchBlogDetail();
}
}, [id]);
return { blog, loading, error };
}
const useGetAPITest = async () => {
try {
const blogs = await getApiTest('test' as any); // APIを呼び出し
console
const data = await blogs.json(); // レスポンスをJSONとして解析
// console.log(data);
return data.message;
} catch (error) {
console.error('Error fetching blogs:', error);
}
};
export { useBlogs, useBlogDetail, useGetAPITest };
- page.tsxで任意のカスタムHooksを参照して実行させる
'use client';
import { Header } from "@/components/organisms/header";
import { PageTitle } from "@/components/atoms/pageTittle";
import { CardList } from "@/components/organisms/cardList";
import { useBlogs, useGetApiTest } from "@/hooks/blog";
import { useEffect } from "react";
export default function Home() {
const { blogArticles, loading } = useBlogs();
const { APIResponse, GetAPILoading } = useGetApiTest();
// データ取得の状態を監視
useEffect(() => {
if (loading || GetAPILoading) {
console.log('データを取得中...');
return;
}
if (blogArticles) {
console.log('ブログ記事の取得完了:', blogArticles);
}
if (APIResponse) {
console.log('API レスポンス:', APIResponse);
}
}, [loading, GetAPILoading, blogArticles, APIResponse]);
return (
<div className={styles.base}>
<Header />
<main className="flex flex-col gap-8 row-start-2 items-center w-11/12 max-w-6xl">
<div className="md:flex flex-row gap-4 justify-between w-full">
<PageTitle title="Blog" />
</div>
{loading ? (
<p className="text-2xl font-semibold text-gray-500">Loading...</p>
) : blogArticles.length === 0 ? (
<p className="text-2xl font-semibold text-gray-500">現在、記事が投稿されていません。</p>
) : (
<CardList blogs={blogArticles} />
)}
</main>
</div>
);
}