2024-11-08

Next.jsアプリケーションに素早く公開APIを追加実装する

Next.jsアプリケーションに素早く公開APIを追加実装する

はじめに

Next.jsの API Routes を活用することで、サードパーティのAPIクライアントに公開するAPIを実装できます。この記事では、Next.jsアプリケーションに素早く公開APIを追加実装する際のアプローチについて紹介します。

Next.jsで公開APIを開発する前に

Next.jsを使ってフロントエンド・バックエンド両方のロジックを実装することができますが、基本的にはNext.jsはWebブラウザにで表示するUIを持ったWebアプリケーションの開発に主眼を置いたフレームワークになります。そのため、Next.jsが外部APIを開発する際に利用するフレームワークとして、優先的な選択肢になる状況は限られています。

例外的に、以下の条件を満たす場合は、Next.jsで公開APIを開発することが有力な選択肢になり得ます。

  • すでにNext.jsを使って開発しているWebアプリケーションに対して、それらの既存コードを活用しながら、低工数・スモールスタートで外部APIを追加したい場合
  • 組織として扱う技術スタックやフレームワークの種類を増やすことで、学習コストを増やしたくない
  • 既存のアーキテクチャを活用しながら、公開APIを試験的にスモールスタートで公開したい

上記条件に当てはまらない場合はExpress.js、NestJS、Hono など、バックエンド開発に特化した他のフレームワークの使用も検討してください。

参考:Next.js で公開APIを開発を行っている事例

APIエンドポイントの実装

Next.jsには2つのルーティング方式があり、それぞれでAPIの実装方法が異なります。

Pages Routerを使用する場合

Pages Routerでは、pages/api/ディレクトリ配下にファイルを配置することでAPIエンドポイントを定義します。

例) pages/api/users.ts

import type { NextApiRequest, NextApiResponse } from 'next'

type User = {
  id: number
  name: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<User[] | { error: string }>
) {
  switch (req.method) {
    case 'GET':
      return handleGet(req, res)
    case 'POST':
      return handlePost(req, res)
  }
}

// GETリクエストの処理
async function handleGet(req: NextApiRequest, res: NextApiResponse<User[]>) {
  const users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' }
  ]
  res.status(200).json(users)
}

// POSTリクエストの処理
async function handlePost(req: NextApiRequest, res: NextApiResponse<User>) {
  const { name } = req.body
  // 新規ユーザーの作成処理
  const newUser = { id: Date.now(), name }
  res.status(201).json(newUser)
}

App Routerを使用する場合

App Routerでは、app/api/ディレクトリ配下にRoute Handlersを実装します。HTTPメソッドごとに別々の関数として定義できます。

例) app/api/users/route.ts

type User = {
  id: number
  name: string
}

// GETリクエストの処理
export async function GET(request: NextRequest) {
  const users: User[] = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' }
  ]

  return Response.json(users)
}

// POSTリクエストの処理
export async function POST(request: NextRequest) {
  const data = await request.json()
  const newUser: User = {
    id: Date.now(),
    name: data.name
  }

  return NextResponse.json(newUser, { status: 201 })
}

認証

APIのユースケースや要件に応じた適切な認証方式を選択し、実装することが重要です。ここでは、Next.jsを使って認証ロジックを実装するいくつかのパターンについて紹介します。

Edge Middlewareを使用した認証

Next.jsの標準ミドルウェアを使用して、Edge Runtime上で動作する認証ロジックを実装できます。バックエンドサーバーのデータベースにアクセスすることはできないため、採用できる認証ロジックは固定のAPI Keyとの検証など、シンプルなものに限られます。

例)middleware.ts

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const apiKey = request.headers.get('x-api-key')

  // 静的なAPI Keyとの比較のみ可能
  if (!apiKey || apiKey !== process.env.VALID_API_KEY) {
    return new NextResponse(
      JSON.stringify({ error: 'Invalid API key' }),
      {
        status: 401,
        headers: { 'content-type': 'application/json' },
      }
    )
  }

  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*',
}

データベースアクセスを伴う認証

データベースアクセスが必要な認証を実装する場合は、いくつか方法がありますが、例えば以下のようなサードパーティのミドルウェアライブラリを使用する方法があります。

例)next-api-middlewareを使用した認証の実装

import { label, Middleware } from "next-api-middleware"
import { prisma } from '@/lib/prisma'
import type { NextApiRequest, NextApiResponse } from 'next'

// 認証ミドルウェアの定義
const authMiddleware: Middleware = async (req, res, next) => {
  const apiKey = req.headers['x-api-key'] as string

  try {
    const apiKeyRecord = await prisma.apiKey.findUnique({
      where: { key: apiKey },
    })

    if (!apiKeyRecord) {
      return res.status(401).json({ error: 'Invalid API key' })
    }

    // 次のミドルウェアまたはAPIハンドラーを実行
    await next()
  } catch (error) {
    return res.status(500).json({ error: 'Internal server error' })
  }
}

// ミドルウェアをラベル付けして設定
const withMiddleware = label({
  auth: authMiddleware
})

// APIハンドラーの実装
async function handler(req: NextApiRequest, res: NextApiResponse) {
  // APIのメインロジック
  res.status(200).json({ message: 'Success' })
}

export default withMiddleware('auth')(handler);

OAuthのサポート

OAuth 2.0 の認証方式を実装することもできます。以下のようなライブラリを活用することで、Next.js でさまざまな認証方法を実現可能です。

https://next-auth.js.org/

バージョニング

APIのバージョン管理は、APIの進化と後方互換性の維持のために重要です。Next.jsでは、ディレクトリ構造を活用して簡単にURIベースのバージョニングを実装できます。

Pages Router使用時のディレクトリ構造例

pages/api/
  ├── v1/
  │   ├── users.ts
  │   └── posts.ts
  └── v2/
      ├── users.ts
      └── posts.ts

App Router使用時のディレクトリ構造例

app/api/
  ├── v1/
  │   ├── users/
  │   │   └── route.ts
  │   └── posts/
  │       └── route.ts
  └── v2/
      ├── users/
      │   └── route.ts
      └── posts/
          └── route.ts

セキュリティ

APIが基本的な認証方式をサポートすることに追加して、さらにセキュリティを強化する方法が存在します。ここでは、Edge Middleware を使用して、シンプルなIPアドレス制限をAPIに加えるサンプルを紹介します。

例) middleware/ipFilter.ts

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const ALLOWED_IPS = process.env.ALLOWED_IPS?.split(',') || []

export function middleware(request: NextRequest) {
  const ip = request.ip ?? '127.0.0.1'

  // 特定のIPアドレスのみ許可
  if (!ALLOWED_IPS.includes(ip)) {
    return new NextResponse(
      JSON.stringify({ error: 'Access denied' }),
      {
        status: 403,
        headers: { 'content-type': 'application/json' },
      }
    )
  }

  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*',
}

この他にも、mTLSの採用やデータの暗号化など、さまざまなセキュリティ強化のための方法が存在しますが、本題とは外れるためここでは割愛します。

まとめ

Next.jsアプリケーションに素早く公開APIを追加実装するアプローチについて紹介しました。

Next.jsは、フロントエンドアプリケーションの開発に特化したフレームワークですが、API Routesの機能を活用することで、シンプルなAPIサーバの構築に活用することができます。

実際にAPIサービスとして運用する場合は、本記事で紹介したNext.jsの機能だけでなく、以下のような観点も合わせて考慮する必要があります。

  • セキュリティ(認証、認可、暗号化)
  • パフォーマンス(キャッシュ、レート制限)
  • 運用管理(ロギング、モニタリング、アラート)
  • 開発者体験(ドキュメント、バージョニング)
  • スケーラビリティ(負荷分散、フェイルオーバー)

本サイトの他の記事も合わせて参照しながら、検討してみてください。

参考ドキュメント

本メディアはAPI特化の開発会社であるECU株式会社が運営しています。記事についてのご質問やAPIに関するご相談・お問い合わせはお気軽にお問い合わせフォームまで。

SNSでシェア

はてなブックマーク