
はじめに
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 でさまざまな認証方法を実現可能です。
バージョニング
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の機能だけでなく、以下のような観点も合わせて考慮する必要があります。
- セキュリティ(認証、認可、暗号化)
- パフォーマンス(キャッシュ、レート制限)
- 運用管理(ロギング、モニタリング、アラート)
- 開発者体験(ドキュメント、バージョニング)
- スケーラビリティ(負荷分散、フェイルオーバー)
本サイトの他の記事も合わせて参照しながら、検討してみてください。
参考ドキュメント
- https://nextjs.org/docs/pages/building-your-application/routing/api-routes
- https://nextjs.org/docs/pages/building-your-application/routing/middleware
本メディアはAPI特化の開発会社であるECU株式会社が運営しています。記事についてのご質問やAPIに関するご相談・お問い合わせはお気軽にお問い合わせフォームまで。
