はじめに
「外部APIからデータを取得して表示したい」と思ったことはありませんか?
私も最初は「APIって難しそう...」と感じていましたが、実際にやってみると意外とシンプルでした。この記事では、Qiita API v2を使って技術記事を取得し、Next.js App Routerで表示する方法を、一緒に実装していきます。
この記事で学べること
✅ Qiita APIの基本的な使い方
✅ Next.js API Routesの作り方
✅ TypeScriptでAPIレスポンスの型を定義する方法
✅ fetchでデータを取得する基本パターン
前提知識
この記事は以下のような方を想定しています:
- Next.js 14以上を少し触ったことがある
- TypeScriptの型定義を見たことがある
- fetchを1回でも使ったことがある
Step 1: Qiita API v2を理解する
Qiita APIとは?
Qiita APIは、Qiitaが公式に提供するREST APIです。無料で利用でき、認証なしでも1時間に60回までリクエストできます。
基本的なエンドポイント
https://qiita.com/api/v2/items
このエンドポイントにGETリクエストを送ると、Qiitaの記事一覧が取得できます。
まずはブラウザで動作確認
コードを書く前に、APIが実際にどんなデータを返すか見てみましょう。以下のURLをブラウザで開いてみてください:
https://qiita.com/api/v2/items?page=1&per_page=5&query=stocks:>=10
JSON形式でQiitaの記事データがブラウザに表示されるはずです。
クエリパラメータの意味:
-
page=1: 1ページ目 -
per_page=5: 1ページあたり5件 -
query=stocks:>=10: ストック数10以上の記事
Step 2: APIレスポンスの型を定義する
ブラウザで見たJSONデータ、そのままだとTypeScriptで扱いにくいですよね。まずは型定義をしていきます。
Qiita APIが返すデータの例
[
{
"id": "1a2b3c4d5e6f",
"title": "Next.js 15の新機能まとめ",
"body": "Next.js 15で追加された...",
"url": "https://qiita.com/user/items/1a2b3c4d5e6f",
"created_at": "2025-12-10T10:00:00+09:00",
"likes_count": 42,
"stocks_count": 18,
"tags": [
{ "name": "Next.js" },
{ "name": "React" }
],
"user": {
"id": "user123"
}
}
]
TypeScriptで型定義
// Qiita APIのタグ型
export interface QiitaTag {
name: string; // タグ名(例: "React")
}
// Qiita APIの記事型
export interface QiitaItem {
id: string; // 記事ID
title: string; // タイトル
body: string; // 本文(Markdown)
url: string; // 記事URL
created_at: string; // 作成日時
likes_count: number; // いいね数
stocks_count: number; // ストック数
tags: QiitaTag[]; // タグ配列
user: {
id: string; // 著者ID
};
}
Step 3: APIを呼び出す関数を作る
Qiita APIを呼び出す関数をクラスで実装します。
export class QiitaAPIFetcher {
private readonly baseUrl = 'https://qiita.com/api/v2';
/**
* Qiita APIから記事を取得
* @param limit 取得件数(デフォルト: 30)
* @returns 記事の配列
*/
async fetchItems(limit: number = 30): Promise<QiitaItem[]> {
try {
// 1. URLを作成
const url = `${this.baseUrl}/items?page=1&per_page=${limit}&query=stocks:>=5`;
// 2. fetchでAPIを呼び出す
const response = await fetch(url, {
next: { revalidate: 300 } // 5分間キャッシュ
});
// 3. エラーチェック
if (!response.ok) {
throw new Error(`Qiita API Error: ${response.status}`);
}
// 4. JSONをパース
const items: QiitaItem[] = await response.json();
return items;
} catch (error) {
console.error('Qiita APIからの取得に失敗:', error);
throw error;
}
}
}
ポイント解説
next: { revalidate: 300 }
Next.js App Routerの機能で、5分間(300秒)データをキャッシュします。
- キャッシュがある → APIを呼ばずにキャッシュを返す(高速)
- キャッシュが古い → 新しいデータを取得
これにより、Qiita APIのレート制限(60回/時)を回避できます。
エラーハンドリング
if (!response.ok) {
throw new Error(`Qiita API Error: ${response.status}`);
}
response.okは、ステータスコードが200番台ならtrueです。
400/500エラーの場合はfalseになるので、エラーを投げます。
Step 4: Next.js API Routeを作成する
クライアントから呼び出すためのAPI Routeを作成します。
import { NextResponse } from 'next/server';
import { QiitaAPIFetcher } from '@/app/lib/fetchers/qiita-api';
/**
* GET /api/articles
* Qiitaから記事を取得するAPI
*/
export async function GET(request: Request) {
try {
// 1. Fetcherのインスタンスを作成
const fetcher = new QiitaAPIFetcher();
// 2. 記事を取得
const articles = await fetcher.fetchItems(30);
// 3. JSONレスポンスを返す
return NextResponse.json({
articles,
count: articles.length,
});
} catch (error) {
// エラーレスポンス
return NextResponse.json(
{ error: 'Failed to fetch articles' },
{ status: 500 }
);
}
}
// 5分ごとにキャッシュを更新
export const revalidate = 300;
API Routeの基本
Next.js App Routerでは、app/api/xxx/route.tsというファイルを作ると、自動的にAPIエンドポイントになります。
-
ファイル:
app/api/articles/route.ts -
URL:
http://localhost:3000/api/articles
export async function GET
HTTPメソッドに対応する関数をexportします。
export async function GET(request: Request) { ... } // GET /api/articles
export async function POST(request: Request) { ... } // POST /api/articles
Step 5: フロントエンドから呼び出す
Reactコンポーネントから、作成したAPI Routeを呼び出します。
'use client';
import { useState, useEffect } from 'react';
export function TechFeed() {
const [articles, setArticles] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// API呼び出し
const fetchArticles = async () => {
setIsLoading(true);
setError(null);
try {
// 自分のAPI Routeを呼ぶ
const response = await fetch('/api/articles');
if (!response.ok) {
throw new Error('記事の取得に失敗しました');
}
const data = await response.json();
setArticles(data.articles);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
fetchArticles();
}, []);
// ローディング表示
if (isLoading) return <p>読み込み中...</p>;
// エラー表示
if (error) return <p>エラー: {error}</p>;
// 記事一覧を表示
return (
<div>
<h1>Qiita 記事一覧</h1>
{articles.map((article) => (
<article key={article.id}>
<h2>{article.title}</h2>
<p>いいね: {article.likes_count}</p>
<p>ストック: {article.stocks_count}</p>
<a href={article.url} target="_blank" rel="noopener noreferrer">
記事を読む
</a>
</article>
))}
</div>
);
}
ポイント解説
'use client'ディレクティブ
Next.js App RouterではデフォルトでServer Componentとして動作します。
useStateやuseEffectなどのReact Hooksを使う場合は、ファイルの先頭に'use client'と書く必要があります。
useEffectでAPI呼び出し
useEffect(() => {
fetchArticles();
}, []); // 空配列 = 初回レンダリング時のみ実行
完成したフォルダ構成
app/
├── api/
│ └── articles/
│ └── route.ts # API Route
├── components/
│ └── TechFeed.tsx # フロントエンド
├── lib/
│ └── fetchers/
│ ├── qiita-api.ts # Qiita API呼び出しロジック
│ └── types.ts # 型定義
└── page.tsx # メインページ
よくあるエラーと解決法
1. CORSエラー
Access to fetch at 'https://qiita.com/api/v2/items' from origin 'http://localhost:3000' has been blocked by CORS policy
原因: ブラウザから直接Qiita APIを呼ぶとCORSエラーになります。
解決: Next.js API Routeを経由する(この記事の方法)
2. レート制限エラー
{
"message": "Rate limit exceeded",
"type": "rate_limit"
}
原因: 1時間に60回以上リクエストした
解決:
-
revalidate: 300でキャッシュを活用 - 開発中は取得件数を減らす(
per_page=5など)
3. 型エラー
Type 'any' is not assignable to type 'QiitaItem[]'
原因: fetchの戻り値に型が付いていない
解決:
const items: QiitaItem[] = await response.json();
まとめ
実装の流れ
- 型定義: APIレスポンスの形をTypeScriptで定義
- Fetcher作成: Qiita APIを呼び出すクラスを作成
- API Route: Next.jsのAPI Routeで自分のAPIを作成
- フロントエンド: useEffectでAPI Routeを呼び出し
重要なポイント
✅ キャッシュ: revalidateでAPIリクエストを減らす
✅ エラーハンドリング: try-catchで適切に処理
✅ 型安全: TypeScriptで型を定義
参考リンク
おわりに
お疲れさまでした!
外部APIからのデータ取得、思ったよりシンプルではなかったでしょうか?
(間違いなどありましたらご指摘いただけると嬉しいです・・・🙏)
どなたかの参考になれば幸いです。