見出し画像

【Next.jsに触れてみよう。】 #4 API Routing

こんにちは!
ALH開発エンジニアのY.Nです。

さて、今回で4回目の【Next.jsに触れてみよう。】シリーズです。

シリーズ過去作は下記リンクより見れます。

■ 【Next.jsに触れてみよう。】 #1 Next.jsとは?

【Next.jsに触れてみよう。】 #2 とりあえず立ち上げてみる

【Next.jsに触れてみよう。 】 #3 動的ルーティング


Next.jsのAPI Routing

前回は以下のような友達紹介ページへの遷移を実装したかと思います。

では次にTaro君の情報をAPIでDBから取ってきたいと思います。
しかしAPIはrailsで書く予定で実装は後回しにしたいです。とりあえず仮のデータを送ってくれる疑似的なAPIを作って画面の表示だけサクッと確認したいな...
そんなときに便利なのが今回紹介するNextAPIです。

先に完成イメージです。

プロフィールへのツッコミはご勘弁。

本題に入ります。
Next.jsでAPIを定義するときは/pages/api配下にファイルを作成します。
仕様として対象の友達のデータを取得するリクエストは"/friends/[対象の友達ID]"とパスパラメータでアクセスするとします。
では早速APIを実装してみましょう。

と、その前に第3回で実装したコードを一部修正します。

前準備

pages/friends.tsx

<div className={styles.card}>
  <Link href="/friends/Taro?id=1">Taro Yamada</Link>
</div>
<div className={styles.card}>
  <Link href="/friends/Hanako?id=2">Hanako Tanaka</Link>
</div>

"/friends/Taro?id=1"、"/friends/Hanako?id=2"の部分ですね。
apiにリクエストを送る際のパスパラメータが名前というのは一意性がないので無理やりidを持たせます。
本来そもそもパスにTaroだのHanakoだの重複しそうな文字列が入らないようにすべきでしたね...
きれいなやり方ではないですが本筋には関係ないのでご了承を。

api実装

気を取り直してapiの実装です。
Next.jsでは/pages/api配下に作成するファイルがapiとして振るまい、
"api/[任意のエンドポイント]"で呼び出すことができます。

※ファイルの配置方法は二つあるので興味があれば「Next.js 日本語翻訳プロジェクト-動的APIルーティング」を確認してみてください。

"api/friends/[対象の友達ID]"というルートにキャッチされるには
"pages/api/friends/[friend_id].tsx"とファイルを作成します。
第3回で解説した通り、ファイル名を[friend_id]とすることで"api/friends/1"のルートでapiを呼び出し、queryにfriend_id="1"を持たせることができます

※本来ならば友達一覧を表示する際に"/api/friends"で友達の配列が返却される"pages/api/friends/index.tsx"apiがあるべきですが今回は省きます。

Next.js 日本語翻訳プロジェクト-動的APIルーティング
/api/posts/[postId].js のみを使うことは正しくありません。なぜなら、どんな状況でも動的ルーティング(すべてのルートのキャッチを含む-下記を参照)には 未定義 な状態はなく、GET api/posts は /api/posts/[postId].js に一致しないためです。

というわけでファイルを作成します。

pages/api/friends/[friends_id].tsx



import { NextApiRequest, NextApiResponse } from "next";

type Friend = {
  id: number,
  firstName: string,
  lastName: string,
  sex: "MEN" | "WOMEN",
  age: number,
  description: string
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const friendId = req.query.friend_id

  const friendList: Friend[] = [
    {
      id: 1,
      firstName: "Taro",
      lastName: "Yamada",
      sex: "MEN",
      age: 24,
      description: "はじめまして!野球部でキャッチャーやってます!みんなで騒ぐの大好き!よろしく!"
    },
    {
      id: 2,
      firstName: "Hanako",
      lastName: "Tanaka",
      sex: "WOMEN",
      age: 24,
      description: "はじめまして。田中花子といいます。吹奏楽部で部長を務めてさせてもらっています。人見知りですが仲良くしてください。"
    }
  ]

  const friend: Friend | undefined = friendList.find((friend) => friend.id === Number(friendId))
  console.log("API取得処理開始")
  console.log(`friendId: ${friendId}`)
  console.group('取得結果');
  console.log(`id: ${friend?.id}`);
  console.log(`firstName: ${friend?.firstName}`);
  console.log(`lastName: ${friend?.lastName}`);
  console.log(`sex: ${friend?.sex}`);
  console.log(`age: ${friend?.age}`);
  console.log(`description: ${friend?.description}`);
  console.groupEnd();
  res.status(200).json({ friend })
}

色々書いてありますが
typescriptを使用しているのでtype Friend = {...}でFriend型を作成し
疑似的なDBとして仮の友達情報の配列を作成

const friend: Friend | undefined = friendList.find((friend) => friend.id === Number(friendId))

でfriendIdに合致する一つの友達情報を取得

res.status(200).json({ friend })

でステータスコード200とともにfriendオブジェクトをjson形式で返却します。
参考:Next.jsのAPI Routesでどんなことができるのかを理解する

これだけで"api/friends/[対象の友達ID]"でリクエストした際に対象の友達情報を返却するapiを実装することができました。

apiを呼び出す側の実装

次に友達の詳細情報を表示するページ、つまりapiを呼び出す側の実装をしていきます。

/pages/friends/[name].tsx


import type { NextPage } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import styles from '../../styles/Home.module.css'

type Friend = {
  id: number,
  firstName: string,
  lastName: string,
  sex: "MEN" | "WOMEN",
  age: number,
  description: string
}

const FriendDetail: NextPage = () => {
  const router = useRouter()
  const url = router.asPath
  const name = router.query.name;
  const id = router.query.id;
  const [friend, setFriend] = useState<Friend>();

  useEffect(() => {
    const fetchFriendData = async () => {
      const response = await fetch(`/api/friends/${id}`)
      const data = await response.json()
      setFriend(data.friend)
    }
    void fetchFriendData()
  }, [id, url])

  return (
    <div className={styles.container}>
      <Head>
        <title>Friends</title>
      </Head>
      <main className={styles.main}>
        <h1 className={styles.title}>
          {name}&apos;s Profile
        </h1>
        <div className={styles.description}>
          welcome to {name}&apos;s Page
        </div>
        <div>
          姓:{friend? friend.lastName : ""}
        </div>
        <div>
          名:{friend? friend.firstName : ""}
        </div>
        <div>
          性別:{friend? friend.sex : ""}
        </div><div>
          年齢:{friend? friend.age : ""}
        </div>
        <div>
          ひとこと:{friend? friend.description : ""}
        </div>
      </main>
      <footer className={styles.footer}>
      </footer>
    </div>
  )
}

export default FriendDetail

やっていることは単純で

useEffect(() => {
    const fetchFriendData = async () => {
      const response = await fetch(`/api/friends/${id}`)
      const data = await response.json()
      setFriends(data.friend)
    }
    void fetchFriendData()
  }, [id, url])

の部分で対象の友達情報を取得、friendsというstateに詰めて

<div>
  姓:{friend? friend.lastName : ""}
</div>

の部分でfriendがnullやundefinedでなければ各種情報を表示しているだけです。

useEffectに関する説明は長くなってしまう(解説に自信がない)ので今回省きます。
興味がある方は
"useEffect" "副作用フック"
などで調べてみてください。

注目したいのは

const fetchFriendData = async () => {
  const response = await fetch(`/api/friends/${id}`)
  const data = await response.json()
  setFriend(data.friend)
}

の部分です。
jsで外部リソースを取得する際のライブラリは「fetchAPI」「axios」などが主流ですが
今回はブラウザに標準搭載されていてnpmインストールが必要ないfetchAPIを使用します。
参考:【リソース取得APIの比較】fetchとaxiosの4つの相違点

fetchメソッドを使用して"/api/friends/${id}"にアクセスします。 ※idは「const id = router.query.id;」でTaroなら"1"、Hanakoなら"2"が入っています。
そして返却されたResponseオブジェクトをもとに、respons.json()でjavascript用のオブジェクトを生成します。
api実装でfriendというオブジェクトをjson形式でレスポンスオブジェクトに詰めているので
data.friendでfriendオブジェクトを参照できます。
setFriend(data.friend)でstateにオブジェクトを保持し、これで好きなようにfriendオブジェクトを使うことができるようになりました。
あとは上記でやっているように表示をさせて完成です!

結局、何が便利なのか

と、実装してみましたが結局何が便利なのか。
仮のデータが欲しいだけならハードコーディングでデータを用意すれば問題ありません。
しかしこのNextApiを利用することによって
実際のApiはまだ作っていないのに

const fetchFriendData = async () => {
  const response = await fetch(`/api/friends/${id}`)
  const data = await response.json()
  setFriend(data.friend)
}

と、ほぼほぼ実際にapiにリクエストを送る形でフロントエンドの実装を進めることができます。
バックエンドの実装が終わればフロントはfetch(/api/friends/${id})のリクエスト先を変えるだけで済みます。
これはなかなか手間の削減になりそうです。
もちろんモックAPIとしての役割だけでなく
webAPIからのレスポンスをNextAPIで取得し、データを成形してからフロントエンドに送る、というようなミドルウェアAPI的な使い方もできます。
どうでしょうか?とっても便利じゃないでしょうか。
個人的に感動したポイントなので熱く語りましたが、魅力が伝われば幸いです。

ちなみに

今回はGETの処理しか扱っていませんがPOST処理も当然実装できます。
がPOSTリクエストを送って実際に何らかのデータ構造を書き換えるにはかなり実装コストがかかるのでテスト目的でPOST処理を確認したい場合はコンソールにログとして出力するのが現実的かと思います。
Mock Service Workerなどのライブラリを使えばPOSTによるデータの書き換えも実現可能です。

終わり

一応この第4回でnext.jsを触ってみようは一区切りになりますが、また自分の知識が深まり次第更新していきたいです。
ここまで読んでいただきありがとうございました。

本記事で作成したデモページ:デモ





↓ ↓ ↓ 採用サイトはこちら ↓ ↓ ↓


↓ ↓ ↓ ALHについてはこちら ↓ ↓ ↓


↓ ↓ ↓ もっとALHについて知りたい? ↓ ↓ ↓