Next.js簡単なAPIルートの作成

2025-08-15

はじめに

Next.jsはフロントエンドだけでなく、バックエンドAPIの作成もサポートしています。pages/apiディレクトリにファイルを作成するだけで、簡単にAPIエンドポイントを作成できます。この記事では、Next.jsを使った基本的なAPIの作成方法を初心者向けに詳しく解説します。

APIルートとは?

Next.jsのAPIルートは、以下の特徴を持っています:

  • フロントエンドと同じプロジェクト内でAPIを作成可能
  • サーバーサイドコードをクライアントに公開せずに実行可能
  • 自動的にCORSが有効化
  • フルスタックアプリケーションの開発が容易
  • サーバーレス関数としてデプロイ可能

基本的なAPIルートの作成

1. 最小限のAPIエンドポイント

pages/api/hello.jsファイルを作成:

// req = リクエストオブジェクト(受信データ)
// res = レスポンスオブジェクト(返信データ)
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello World!' });
}

このAPIは以下のようにアクセスできます:

  • 開発環境: http://localhost:3000/api/hello
  • 本番環境: https://yourdomain.com/api/hello

2. リクエストメソッドの判別

異なるHTTPメソッド(GET, POSTなど)に対応する:

export default function handler(req, res) {
  if (req.method === 'GET') {
    // GETリクエストの処理
    res.status(200).json({ message: 'GETリクエストを受け取りました' });
  } else if (req.method === 'POST') {
    // POSTリクエストの処理
    res.status(200).json({ message: 'POSTリクエストを受け取りました' });
  } else {
    // その他のメソッド
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

リクエストデータの取得

1. クエリパラメータの取得

URLのクエリパラメータ(?key=value)を取得:

// /api/user?id=123 にアクセスした場合
export default function handler(req, res) {
  const { id } = req.query; // { id: '123' }
  res.status(200).json({ userId: id });
}

2. POSTリクエストのボディ取得

export default async function handler(req, res) {
  if (req.method === 'POST') {
    // ボディデータを取得
    const data = req.body;

    // データベースに保存などの処理...

    res.status(200).json({ 
      status: 'success', 
      data: data 
    });
  }
}

実際のAPIルート例

1. ユーザーデータ取得API

// pages/api/users/[id].js - 動的APIルート
export default function handler(req, res) {
  const { id } = req.query;

  // 実際のアプリではデータベースから取得
  const users = {
    '1': { name: '山田太郎', email: 'taro@example.com' },
    '2': { name: '佐藤花子', email: 'hanako@example.com' }
  };

  const user = users[id];

  if (user) {
    res.status(200).json(user);
  } else {
    res.status(404).json({ message: 'ユーザーが見つかりません' });
  }
}

2. フォームデータ処理API

// pages/api/contact.js
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'POSTメソッドのみ許可されています' });
  }

  try {
    const { name, email, message } = req.body;

    // バリデーション
    if (!name || !email || !message) {
      return res.status(400).json({ message: 'すべてのフィールドが必須です' });
    }

    // 実際のアプリではここでメール送信やデータベース保存を行う
    console.log('お問い合わせ内容:', { name, email, message });

    return res.status(200).json({ 
      status: 'success', 
      message: 'お問い合わせありがとうございます' 
    });
  } catch (error) {
    console.error('Error:', error);
    return res.status(500).json({ 
      message: 'サーバーエラーが発生しました' 
    });
  }
}

APIルートの活用例

1. 外部APIのプロキシ

// pages/api/proxy/weather.js
export default async function handler(req, res) {
  const { city } = req.query;

  try {
    const apiKey = process.env.WEATHER_API_KEY;
    const response = await fetch(
      `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}`
    );

    if (!response.ok) {
      throw new Error('天気情報の取得に失敗しました');
    }

    const data = await response.json();
    res.status(200).json(data);
  } catch (error) {
    res.status(500).json({ 
      message: error.message 
    });
  }
}

2. サーバーサイドのみの処理

// pages/api/secret-data.js
export default function handler(req, res) {
  // クライアントに公開したくないサーバーサイドのみの処理
  const secretKey = process.env.SECRET_KEY;
  const sensitiveData = doSomeSecureCalculation(secretKey);

  res.status(200).json({ 
    result: sensitiveData 
  });
}

function doSomeSecureCalculation(key) {
  // クライアントサイドに公開したくない処理
  return `Processed with ${key.substring(0, 4)}...`;
}

ミドルウェアの使用

APIルートでミドルウェアを使用して、共通処理をまとめることができます。

1. CORS設定

// pages/api/with-cors.js
import Cors from 'cors';

// CORSミドルウェアを初期化
const cors = Cors({
  methods: ['GET', 'HEAD'],
});

// ミドルウェアを実行するヘルパー関数
function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => {
      if (result instanceof Error) {
        return reject(result);
      }
      return resolve(result);
    });
  });
}

export default async function handler(req, res) {
  // CORSミドルウェアを実行
  await runMiddleware(req, res, cors);

  // APIロジック
  res.json({ message: 'CORSが有効化されています' });
}

2. 認証ミドルウェア

// utils/auth-middleware.js
export function withAuth(handler) {
  return async (req, res) => {
    // 認証トークンを確認
    const token = req.headers.authorization?.split(' ')[1];

    if (token !== process.env.API_TOKEN) {
      return res.status(401).json({ message: '認証が必要です' });
    }

    // 認証成功したら元のハンドラーを実行
    return handler(req, res);
  };
}

// pages/api/protected.js
import { withAuth } from '../../utils/auth-middleware';

function handler(req, res) {
  res.status(200).json({ 
    secretData: 'これは認証が必要なデータです' 
  });
}

export default withAuth(handler);

エラーハンドリングのベストプラクティス

APIルートでは適切なエラーハンドリングが重要です。

1. 統一されたエラーレスポンス

// utils/api-error.js
export class ApiError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
  }
}

// utils/handle-error.js
export function handleError(res, error) {
  if (error instanceof ApiError) {
    return res.status(error.statusCode).json({ 
      message: error.message 
    });
  }

  console.error('Unexpected error:', error);
  return res.status(500).json({ 
    message: '予期せぬエラーが発生しました' 
  });
}

// pages/api/products/[id].js
import { ApiError } from '../../utils/api-error';
import { handleError } from '../../utils/handle-error';

export default async function handler(req, res) {
  try {
    const { id } = req.query;

    if (!isValidId(id)) {
      throw new ApiError(400, '無効なID形式です');
    }

    const product = await getProductFromDatabase(id);

    if (!product) {
      throw new ApiError(404, '製品が見つかりません');
    }

    res.status(200).json(product);
  } catch (error) {
    handleError(res, error);
  }
}

APIルートのテスト

1. ブラウザで直接テスト

GETリクエストはブラウザのアドレスバーに直接入力してテスト可能:
http://localhost:3000/api/hello

2. cURLでテスト

# GETリクエスト
curl http://localhost:3000/api/hello

# POSTリクエスト
curl -X POST -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:3000/api/users

3. フロントエンドから呼び出す

// Reactコンポーネント内
async function fetchData() {
  try {
    const response = await fetch('/api/hello');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

// POSTリクエストの例
async function postData() {
  try {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ name: 'John' }),
    });
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

よくある質問

Q1: APIルートはどこで実行されますか?

Next.jsのAPIルートは以下の環境で実行されます:

  • 開発時: Node.jsサーバー
  • 本番環境(Vercel): サーバーレス関数
  • セルフホスティング: Node.jsサーバー

Q2: データベースに接続するには?

APIルートで直接データベースに接続できます:

import { connectToDatabase } from '../../lib/mongodb';

export default async function handler(req, res) {
  const { db } = await connectToDatabase();

  const data = await db.collection('posts').find().toArray();

  res.status(200).json(data);
}

Q3: APIルートの制限は?

  • Vercelの無料プランでは1リクエストあたり10秒のタイムアウト
  • 大規模なバックエンドには向かない(専用バックエンドサービスの検討を)

まとめ

Next.jsのAPIルートについて学びました。主なポイントは:

  1. pages/apiディレクトリにファイルを作成するだけでAPIエンドポイントが作成できる
  2. リクエストメソッド(GET, POSTなど)ごとに処理を分岐可能
  3. クエリパラメータやリクエストボディを簡単に取得できる
  4. ミドルウェアを使用して共通処理をまとめられる
  5. フロントエンドと同じプロジェクトでバックエンド処理が書ける

APIルートは小規模なバックエンド処理やプロキシ、フォーム処理などに最適です。ただし、複雑なバックエンドシステムが必要な場合は、専用のバックエンドサービスと組み合わせることも検討しましょう。

Next.jsの公式ドキュメントも参考にしてください:
https://nextjs.org/docs/api-routes/introduction