見出し画像

Next.jsのAPI Routesを使ってフロントエンドで完結するAPI処理を実装する

こんにちは。
少し前に「ONEPIECE FILM RED」を見た影響でウタの歌を永遠にリピートしながら仕事をしているmizukiです。
ウタカタララバイという歌のラップパートは鳥肌モノなので、よければ聞いてみてください!
(ちなみに僕はAdoさんのがなり声を真似しようと必死になっていたら喉を痛めました。。。)

と、雑談はほどほどに、弊社ではフロントエンドのコードをNext.jsへと移行しており、日に日にNext.jsを扱う機会が増えています。
詳しいことは以下の記事で佐伯さんが書いてくださっているので、興味ある方はこちらも覗いてみてください!

そんな中で、Next.jsのAPI Routesという機能を扱う機会があったので、今回復習も兼ねて書いていきたいと思います。
また、リダイレクトの処理をnext.configのrewritesを利用したので、そちらについても合わせて書いていこうと思います。

まずは用語について軽く説明

この記事で使用するNext.jsの「API Routes」と「rewrites設定」について軽く説明をします。

API Routes

API Routesとは、その名の通りNext.jsの中でAPIを実装できる機能を指します。
/pages/api 配下に置いたファイルで実装ができ、ファイルの配置された場所のパスへリクエストを送ることでAPIとして使用できます。

例えば、/pages/api/hoge/fuga.ts というファイルを配置した場合、/api/hoge/fuga にリクエストを送ることでfuga.tsの中に書かれた処理を実行することが可能です。
以下のように記述することで、reqオブジェクトとresオブジェクトを受け取りjsonを返却するAPIとして定義することができます。

// /pages/api/hoge/fuga.ts -> /api/hoge/fugaにリクエストを送ると以下の処理が実行される

export default function handler(req, res) {
  res.status(200).json({ name: 'mizuki' })
}

なお、build時には実行されないため、getStaticPropsなどの中では実行されないので注意が必要です。

rewrites設定

Next.jsのアプリではpages配下のディレクトリ構成がそのままルーティングとして使用できます。
しかし、next.configにrewrites設定を記述することで、リクエストされたURLから別のファイルを読み込ませることができます。
下記の例のように、sourceにリクエストされたURL、destinationに遷移させたいURLを記述します。
ダイナミックルーティングにも対応していたりクエリパラメータを付与できたりと、かなり柔軟に設定が可能です。

// next.config.js

module.exports = {
  async rewrites() {
    // 複数の設定を配列の形式で記述できます
    return [
      // 「/hoge」でリクエストがあった時に「/pages/fuga/index.tsx」を読み込みに行く
      {
        source: '/hoge',
        destination: '/fuga',
      },
      // 動的ルーティングの記述も可能
      {
        source: '/blog/:id',
        destination: 'news/:id',
      },
      // 正規表現の書き方も可能
      {
        source: 'docs/:path*',
        destination: '/:path*',
      },
      // クエリパラメータを付けることも可能
      {
        source: '/:first/:second',
        destination: '/:first?second=:second',
      },
    ]
  }
}

今回やること

今回は説明のために簡単なデモアプリを作成しました。
ユーザーが商品を自由に出品・購入ができるフリマアプリがあり、そこに以下の機能を追加実装したという想定で実装をしています。

  • 出品者が独自にクーポンURL( /coupon/<出品者のuserId> )を発行できる

  • クーポンURLからアクセスして購入した場合、出品者や購入者に特別な割引を効かせる

  • クーポンURLにはアクセスから購入までに1週間の有効期限がある

  • クーポンの情報はcookieで管理をする(クーポンURLでアクセスした際にcookieへ登録し、購入時にcookieの情報を参照して割引適用有無を判断する)

  • クーポンURLにアクセスされたら、その情報をcookieに保存しつつ、出品者の出品商品一覧画面( /seller/<出品者のuserId> )を表示する

以下がそのコードです。
※「フリマアプリです」というのは単なる想定の話なので、何も実装していません。
※あくまで今回するAPI Routesやrewritesの記述のみを実装しています。

実装方針

今回の実装の目標は、「 /coupon/<userId> 」にアクセスされたらcookieに情報を保存した上で「 /seller/<userId> 」のページを表示する、という処理を実装することです。

そのため、通常にNext.jsのルーティングのルールに則ると、
①「 /pages/coupon/[userId].tsx 」でcookieに情報を保存する
②UIは描画せずに「 /pages/seller/[userId].tsx 」にリダイレクトさせる
③ 「 /pages/seller/[userId].tsx 」で最終的なUIを描画
という手順で実装していくことになりそうです。

ただ、UIを描画することがないコンポーネントがルーティングのためだけにpages直下にいるのは少し違和感があります。
そこで、まずcookieに登録する処理は「 /pages/api/coupon/[userId].ts 」にAPIとして切り出してしまいます。
その上で、「 /coupon/<userId> 」にアクセスされたら「 /pages/api/coupon/[userId].ts 」に記述したAPIの処理を読みにいくという設定をrewrites設定で行います。
そうすることでFEで完結するロジック部分をうまいことAPIとして切り出して実装ができるようになります。

ということで詳しい実装の説明に入ります。

実装手順

事前準備

まずは必要なライブラリをインストールします。
今回は、日付の取得や計算に使う「dayjs」とcookie操作に使う「nookies」というライブラリをインストールします。

次に、最終的に表示させたい出品者の出品商品一覧画面をマークアップします。

// /pages/seller/[userId].tsx

import { FC } from "react";
import { GetServerSideProps } from "next";

type PageProps = { data: { userId: string } };

export const getServerSideProps: GetServerSideProps = async (context) => {
  const userId = context.params?.userId ?? "";
  return { props: { data: { userId } } };
};

const Seller: FC<PageProps> = ({ data }) => {
  const { userId } = data;

  // 今回の主題はAPI Routesの説明なので、最低限のマークアップのみです
  return (
    <>
      <h1>出品者(id: {userId})の商品一覧ページです</h1>
      <p>クーポンURLから遷移した場合はcookieにデータが保存されています</p>
    </>
  );
};

export default Seller;

API Routesの実装

ここからがこの記事の主題です。
まずはAPIの処理を記述します。

// /pages/api/coupon/[userId].ts

import { NextApiRequest, NextApiResponse } from "next";
import { setCookie } from "nookies";
import dayjs from "dayjs";

/**
 * クーポンURLにアクセスされたらこのAPIの処理が動く。
 * cookieに出品者のuserIdとクーポンの有効期限の情報を設定した上で
 * 出品者の商品一覧ページへリダイレクトする。
 */
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  // 今回はdemoなのでlocalhostのみで動かす想定
  const ORIGIN = "http://localhost:3000";

  const { userId } = req.query;

  // URLの妥当性をチェック(今回はdemoなので簡単に記述)
  if (!userId) {
    throw Error("URL is invalid");
  }

  // cookieにクーポンの情報を登録
  const key = `coupon_${userId}`;
  const expires = dayjs().add(1, "week").toDate();
  const options = { domain: "localhost", expires, path: "/" };
  setCookie({ res }, key, expires.getTime().toString(), options);

  // 対象ページへリダイレクト
  const redirectUrl = `${ORIGIN}/seller/${userId}`;
  res.redirect(redirectUrl);
};

export default handler;

APIとなる関数を定義し、そのpropsとしてreqオブジェクトとresオブジェクトを受け取ります。
reqオブジェクトに存在するqueryパラメータの情報からURLに含まれているuserIdを取得し、cookie登録やリダイレクト処理に使用します。
cookie情報を登録した後は、res.redirect()で最終的に表示させたい「/seller/<userId>」のページにリダイレクトをさせます。

Next.configのrewrites設定

ここまででAPIの記述はできましたが、これだとクーポンURLを「/api/coupon/<userId>」としないといけなくなり、少し見栄えが悪いのでrewrites設定をしていきます。

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  async rewrites() {
    return [
      // クーポンURLでアクセスがあった場合はAPI Routesの処理に飛ばすように設定しています
      {
        source: "/coupon/:userId",
        destination: "/api/coupon/:userId",
      },
    ];
  },
};

module.exports = nextConfig;

こちらの記述で「/coupon/<userId>」でアクセスされた場合に「/api/coupon/<userId>」に遷移させることができ、正しくAPIの処理が読み込まれるようになりました。

完成!

これで全て実装が完成です。
実際に「 /coupon/12345 」にアクセスしてみると以下のようにリダイレクトしてcookieにも情報が保存されています。

アクセス後の画面

おわりに

今回はNext.jsのAPI Routesとrewrites設定について書いてみました。
この記事で紹介したのはあくまで一例で、API Routesもrewrites設定も使い方によってはさまざまな実装が可能になる機能だなと感じたので、今後も実装の選択肢の一つとして持っておきたいと思いました。

そして、弊社では一緒に働くメンバーを募集しています。
少しでも興味を持ってもらえた方は採用ページも覗いてみてください!
(最近コーポレートサイトをリニューアルして、UIもすごくカッコよくなっています!笑)



この記事が気に入ったらサポートをしてみませんか?