0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js App RouterでQiita API v2を使って記事を取得する方法

0
Posted at

はじめに

「外部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で型定義

app/lib/fetchers/types.ts
// 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を呼び出す関数をクラスで実装します。

app/lib/fetchers/qiita-api.ts
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を作成します。

app/api/articles/route.ts
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を呼び出します。

app/components/TechFeed.tsx
'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として動作します。
useStateuseEffectなどの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();

まとめ

実装の流れ

  1. 型定義: APIレスポンスの形をTypeScriptで定義
  2. Fetcher作成: Qiita APIを呼び出すクラスを作成
  3. API Route: Next.jsのAPI Routeで自分のAPIを作成
  4. フロントエンド: useEffectでAPI Routeを呼び出し

重要なポイント

キャッシュ: revalidateでAPIリクエストを減らす
エラーハンドリング: try-catchで適切に処理
型安全: TypeScriptで型を定義


参考リンク


おわりに

お疲れさまでした!
外部APIからのデータ取得、思ったよりシンプルではなかったでしょうか?
(間違いなどありましたらご指摘いただけると嬉しいです・・・🙏)

どなたかの参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?