Next.js 14で外部APIからデータを取得する (Route Handlers使用)

## 目次

[1. はじめに](#1-はじめに)  
[2. Route Handlers とは](#2-route-handlers-とは)  
[3. まずは Client Components だけで作ってみる](#3-まずは-client-components-だけで作ってみる)  
[4. Route Handlers を使って修正していく](#4-route-handlers-を使って修正していく)  
[5. 参考](#5-参考)

## 1. はじめに

Next.js の学習のため、外部 API からデータを取得して表示する機能を作りました。コンポーネントの役割分けや Route Handlers など、色々と調べながら実装したのでまとめておきたいと思います。

### 作りたいもの

作りたいものは、シンプルなお天気アプリです。ユーザーが都市名を入力すると、その都市の天気情報を外部 API から取得して表示します。以下のようなイメージです。

![weather_app.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3137271/287761c8-48c2-43d0-8a4d-1142ad5b1454.jpeg)

### 使用する外部 API

[Weather API](https://openweathermap.org/api) から、現在の天気を取得する [Current weather data](https://openweathermap.org/current) を使います。  
使用するには OpenWeather のアカウントと API Key の発行が必要です。

### 実装方針

#### Server Components と Client Components

実装でつまった箇所として、**Server Components と Client Components の違い**がありました。これについては別の記事に書きたいなと思っています。  
Next.js には Server Components と Client Components の 2 種類のコンポーネントがあり、アプリを作っていく時にはこの 2 種類のコンポーネントで役割分けをすることが重要です。**サーバー側で行う処理は Server Components に、ユーザーからのアクションが必要な処理は Client Components に任せるのが望ましいです。**

Server Components と Client Components については、公式サイトに詳しい説明があります。

[Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components)  
[Client Components](https://nextjs.org/docs/app/building-your-application/rendering/client-components)  
[Server and Client Composition Patterns](https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns)

ということで、今回作るお天気アプリは、**ユーザーから都市名の入力を受け取る部分は Client Components、外部 API からデータを取得する部分は Server Components** が適切かと思います。なのですがこの記事では、**Server Components ではなく Route Handlers に、外部 API からデータを取得する機能を実装**してみたいと思います。

## 2. Route Handlers とは

公式サイトに詳細な説明やサンプルコードがあります。

[Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)

### どんなもの？何のために使う？

Route Handlers は、API アプリケーションのような機能を提供してくれます。**Route Handlers がエンドポイントを提供し、Client Components はそのエンドポイントに HTTP リクエストを送る**ことができます。  
Route Handlers に書いた処理は、Server Components と同じようにサーバーサイドで実行されます。  
ということは、Client Components はユーザーからのアクションを受け取り Route Handlers が提供するエンドポイントにリクエストを送って、サーバー側ではそれに応じてさまざまな処理を行う、という実装ができるということですね。

### Server Components との違いは？

外部 API からデータを取得する処理は、Server Components に書くことももちろんできます。その場合は、取得したデータを **Props として Client Components に渡す**ことになります。  
今回使用する Route handlers の場合は、取得したデータを**レスポンスとして Client Components に渡し**ます。

どちらを使うこともできますが、Server Components から Props として渡す方が一般的なようです。  
ユーザーからのリクエストが頻繁に起きる場合や、クライアントに渡す前に何らかの前処理（フィルタリング、整形など）を行いたい場合に Route Handlers が適しているみたいです。今回はユーザーからのリクエストのたびに天気を取得するということで、Route Handlers を使ってみることにします。

## 3. まずは Client Components だけで作ってみる

さて、サーバー側の処理は Server Components や Route Handlers に任せるべきなのですが、まずはそれを無視して Client Components に全部書いてしまいます。  
これだと何がまずいのかを、見ていきましょう。

※ これはダメなコードの例です！  
※ 分かりやすさ重視のためエラー処理などは書いていません。

```jsx:app/page.jsx
"use client";

import { useState } from "react";

export default function Page() {
  const [city, setCity] = useState("");
  const [weather, setWeather] = useState("");

  const key = "xxxxx";

  async function getWeather() {
    const res = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}`
    );
    const data = await res.json();
    setCity(data.name);
    setWeather(data.weather[0].main);
  }

  return (
    <div className="text-center mt-8">
      <input
        type="text"
        placeholder="Enter city name"
        className="border p-2 mr-3 mb-5"
        onChange={(e) => {
          setCity(e.target.value);
        }}
        value={city}
      />
      <button className="bg-gray-200 p-2" onClick={getWeather}>
        Get weather info
      </button>
      <h1>City: {city}</h1>
      <p>Weather: {weather}</p>
    </div>
  );
}
```

### このコードではなぜだめか？

このように、Client Components でも `fetch` は書けますし、`key` の部分に正しい API Key を入れれば外部 API からデータ取得もできます。  
ですが先ほども書きましたが、サーバー側でできる処理は Server Components に任せるべきです。それはサーバー側の方が基本的に処理速度が速い場合が多いことと、**セキュリティの問題**があるためです。

このコードは、**API キーと外部 API の URL をそのまま書いてしまっている点**がかなり問題です。Client Components にこのような情報を書いてしまうと、誰からでも見ることができてしまいます。  
そこで API キーをどうにか隠したいところですが、Client Components ではその管理が難しいのです。

## 4. Route Handlers を使って修正していく

ではコードを修正していきたいと思います。実装方針としては、以下のようにします。

1. ユーザーからの入力を Client Components で受け取る。
2. Client Components から Route Handlers にリクエストを送る。
3. Route Handlers が外部 API と通信してデータを取得する。
4. Route Handlers は取得したデータを Client Components にレスポンスとして返す。
5. Client Components が受取った情報を表示する。

### Route Handlers はどこにどう書く？

Route Handlers の構成は、ディレクトリ構造がそのままパスになる App Router と似ています。

- Route Handlers ファイル名は、`route.js(ts)` にします。
- フォルダ名がそのままエンドポイントになるので、例えばファイルを `app/api/weather/route.js` のようにすると、エンドポイントは `/api/weather` になります。
- 関数名は GET、POST など、受け付ける HTTP リクエストの名前にします。

### Route Handlers サンプルコード

以下は Route Handlers からシンプルな JSON を返すサンプルコードです。  
ユーザからの入力は、Client Components からの URL に含めてクエリストリングで渡しています。  
Route Handlers 側では、URL からその値を取得する処理を書いています。

#### Route Handlers

```js:app/api/sample/route.js
export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const q = searchParams.get("q");
  return Response.json({ message: `routeから${q}へのレスポンスです` });
}
```

#### Client Components

```jsx:app/page.jsx
"use client";

import { useState } from "react";

export default function Page() {
  const [data, setData] = useState("");

  async function getData() {
    const res = await fetch("/api/sample?q=client");
    const data = await res.json();
    setData(data.message);
  }

  return (
    <div className="text-center mt-8">
      <button className="bg-gray-200 p-2 mb-5" onClick={getData}>
        データを取得
      </button>
      <p>{data}</p>
    </div>
  );
}
```

### 修正後のコード

Route Handlers を使って、以下のようにお天気アプリのコードを修正しました。Client Components ではユーザーからのアクションに関しての処理、Route Handlers でサーバー側の処理というように役割を分けることができました。  
また、API キーは環境変数として隠すことができました。

※ 分かりやすさ重視のためエラー処理などは書いていません。

#### Route Handlers

```js:app/api/weather/route.js
export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const city = searchParams.get("city");
  const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER_API_KEY}`;

  const res = await fetch(url);
  const weather = await res.json();
  return Response.json({ weather });
}
```

#### Client Components

```jsx:app/page.jsx
"use client";

import { useState } from "react";

export default function Page() {
  const [city, setCity] = useState("");
  const [weather, setWeather] = useState("");

  async function getWeather() {
    const res = await fetch(`/api/weather?city=${city}`);
    const data = await res.json();
    setCity(data.weather.name);
    setWeather(data.weather.weather[0].main);
  }

  return (
    <div className="text-center mt-8">
      <input
        type="text"
        placeholder="Enter city name"
        className="border p-2 mr-3 mb-5"
        onChange={(e) => {
          setCity(e.target.value);
        }}
        value={city}
      />
      <button className="bg-gray-200 p-2" onClick={getWeather}>
        Get weather info
      </button>
      <h1>City: {city}</h1>
      <p>Weather: {weather}</p>
    </div>
  );
}
```

## 5. 参考

[Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)  
[Next.js 13 の Route Handlers に移行したぞ！](https://www.zenryoku-kun.com/post/nextjs-route-handler#basic)