Skip to content
Code Blue プログラミング学習ノート
Javascript/Next.js

Next.jsとPostgresDB接続ガイド

はじめに

Next.js アプリケーションでデータベースに接続する方法を、手順を追って詳しく説明します。Next.jsアプリケーションからPostgreSQL(およびその他のデータベース)へ接続する際の設計原則と接続方法は、主に「どの実行環境(Runtimes)から接続するか」「どのようにクエリを投げるか」の2つの観点で説明します。

このガイドでは PostgreSQL を例にしますが、他のデータベースでも同様の原則が適用できます。

前提条件

開発を進める前に、前提条件として、開発環境に Node.js がインストールされていることを確認してください。また、PostgreSQL をローカル環境にインストールするか、Supabase、Neon、AWS RDS などのクラウドデータベースサービスを利用できる環境を用意します。さらに、テーブルの作成やデータの取得・更新といった基本的な操作を理解できる程度の SQL の知識があると、内容をよりスムーズに理解しながら学習を進められます。

データベースの準備

ローカル PostgreSQL をセットアップ

PostgreSQL をインストール(まだの場合)

  • macOS: brew install postgresql
  • Windows: PostgreSQL 公式サイトからインストーラーをダウンロード
  • Linux (Ubuntu): sudo apt-get install postgresql postgresql-contrib

データベースとユーザーを作成

   sudo -u postgres psql
   CREATE DATABASE nextjs_demo;
   CREATE USER nextjs_user WITH PASSWORD 'securepassword';
   GRANT ALL PRIVILEGES ON DATABASE nextjs_demo TO nextjs_user;
   \q

クラウドサービスを使用する場合

Supabase や Neon などのサービスを使用する場合

Supabase や Neon などのクラウドデータベースサービスを利用する場合は、まずアカウントを作成し、新しいプロジェクトを作成します。

Supabase:https://supabase.com

Neon:https://neon.com

プロジェクトの作成後は、データベースへ接続するための接続文字列(Connection String)を確認し、後の設定で使用できるよう控えておきましょう。接続文字列には認証情報が含まれるため、公開リポジトリなどに誤って保存しないよう適切に管理することが重要です。

Next.js プロジェクトのセットアップ

まず、Next.js プロジェクトがまだ作成されていない場合は、新規プロジェクトを作成します。

新しい Next.js プロジェクトを作成(まだの場合)

   npx create-next-app@latest nextjs-database-demo
   cd nextjs-database-demo

必要な依存関係をインストール

続いて、PostgreSQL に接続するための pg パッケージや型定義、環境変数を管理するための dotenv など、必要な依存関係をインストールします。

   npm install pg @types/pg dotenv
   # または
   yarn add pg @types/pg dotenv

環境変数の設定

次に、プロジェクトルートに .env.local ファイルを作成し、データベースの接続文字列を環境変数として設定します。Supabase や Neon などのクラウドサービスを利用する場合は、それぞれが提供する接続文字列を使用してください。

プロジェクトルートに .env.local ファイルを作成

   POSTGRES_URL="postgres://nextjs_user:securepassword@localhost:5432/nextjs_demo"
   # クラウドサービスの場合は次のような形式になります:
   # POSTGRES_URL="postgres://user:password@host:port/database?options"

.gitignore.env.local が含まれていることを確認

機密情報を保護するため、.env.local が .gitignore に含まれていることを確認しましょう。

データベース接続の設定

最後に、lib/db.ts を作成し、pg の Pool を利用してデータベース接続を設定します。接続処理を共通化することで、アプリケーション全体から安全かつ効率的にデータベースへアクセスできるようになります。

lib/db.ts ファイルを作成

   import { Pool } from 'pg';
   import dotenv from 'dotenv';

   dotenv.config();

   const pool = new Pool({
     connectionString: process.env.POSTGRES_URL,
   });

   export default {
     query: (text: string, params?: any[]) => pool.query(text, params),
   };

データベース操作の実装

ユーザーテーブルの作成と操作

次のコードは、PostgreSQL データベースの初期化処理を行うスクリプトです。データベース接続設定を読み込み、users テーブルが存在しない場合に作成します。

初期セットアップスクリプトを作成 scripts/initDB.ts

テーブルには、ユーザーID、名前、メールアドレス、作成日時を保存するカラムを定義しています。また、処理中にエラーが発生した場合は内容を表示し、正常に完了した場合は成功メッセージを出力します。

   import db from '../lib/db';

   async function initDatabase() {
     try {
       await db.query(`
         CREATE TABLE IF NOT EXISTS users (
           id SERIAL PRIMARY KEY,
           name VARCHAR(100) NOT NULL,
           email VARCHAR(100) UNIQUE NOT NULL,
           created_at TIMESTAMP DEFAULT NOW()
         );
       `);
       console.log('Database initialized successfully');
     } catch (error) {
       console.error('Error initializing database:', error);
     }
   }

   initDatabase();

スクリプトを実行

   npx ts-node scripts/initDB.ts

API ルートでのデータベース使用

次のコードは、Next.js の API ルートを利用してデータベースのユーザー情報を操作する処理を実装しています。GET リクエストでは users テーブルからユーザー一覧を取得し、作成日時の新しい順に返します。

POST リクエストでは、送信された名前とメールアドレスを確認したうえで、新しいユーザー情報をデータベースへ登録します。また、対応していない HTTP メソッドへのアクセスや、処理中に発生したエラーに対して適切なステータスコードとメッセージを返すようエラーハンドリングも実装されています。

pages/api/users.ts を作成

import { NextApiRequest, NextApiResponse } from 'next';
import db from '../../lib/db';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    switch (req.method) {
      case 'GET':
        const { rows } = await db.query('SELECT * FROM users ORDER BY created_at DESC');
        res.status(200).json(rows);
        break;
      
      case 'POST':
        const { name, email } = req.body;
        if (!name || !email) {
          return res.status(400).json({ message: 'Name and email are required' });
        }
        
        const result = await db.query(
          'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
          [name, email]
        );
        res.status(201).json(result.rows[0]);
        break;
      
      default:
        res.setHeader('Allow', ['GET', 'POST']);
        res.status(405).end(`Method ${req.method} Not Allowed`);
    }
  } catch (error) {
    console.error('API Error:', error);
    res.status(500).json({ message: 'Internal server error' });
  }
}

ページコンポーネントでのデータ取得

SSG (静的生成) でのデータ取得

下のコードは、Next.js の静的生成機能を利用してデータベースからユーザー情報を取得し、一覧表示するページを実装しています。getStaticProps で PostgreSQL からユーザーデータを取得し、ページの props として渡します。

また、ISR(Incremental Static Regeneration)を利用し、60秒ごとにページを自動的に再生成することで、表示速度を維持しながら最新のデータを反映できます。取得したユーザー情報は、コンポーネント内でリスト形式に表示されます。

pages/users/index.tsx:

import { GetStaticProps } from 'next';
import db from '../../lib/db';

interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export default function UsersPage({ users }: { users: User[] }) {
  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export const getStaticProps: GetStaticProps = async () => {
  const { rows } = await db.query('SELECT * FROM users ORDER BY created_at DESC');
  return {
    props: {
      users: rows,
    },
    revalidate: 60, // ISR: 60秒ごとに再生成
  };
};

SSR (サーバーサイドレンダリング) でのデータ取得

下のコードは、Next.js の SSR(サーバーサイドレンダリング)を利用して、データベースから取得したユーザー情報をページ表示する処理を実装しています。

getServerSideProps がリクエストごとに実行され、PostgreSQL から最新のユーザーデータを取得してページへ渡します。取得したデータはコンポーネント内で一覧表示され、常に最新の情報を表示できます。動的に変化するデータや、リアルタイム性が求められるページに適した実装方法です。

pages/users/ssr.tsx:

import { GetServerSideProps } from 'next';
import db from '../../lib/db';

interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export default function UsersSSRPage({ users }: { users: User[] }) {
  return (
    <div>
      <h1>Users (SSR)</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async () => {
  const { rows } = await db.query('SELECT * FROM users ORDER BY created_at DESC');
  return {
    props: {
      users: rows,
    },
  };
};

Vercel へのデプロイ

1. Vercel でプロジェクトを作成する

まず、Vercel のダッシュボードへアクセスし、Next.js プロジェクトを登録します。GitHub などのリポジトリと連携している場合は、対象のリポジトリを選択してプロジェクトを作成します。必要に応じて、ビルド設定やデプロイ設定を確認してください。

2. 環境変数を設定する

本番環境でデータベースへ接続するために、環境変数を設定します。

  1. Vercel ダッシュボードで対象のプロジェクトを開きます。
  2. 左側メニューから 「Settings」 を選択します。
  3. 「Environment Variables」 の項目へ移動します。
  4. 新しい環境変数として以下を追加します。
POSTGRES_URL

値には、本番環境で使用する PostgreSQL の接続文字列を設定します。データベースの認証情報が含まれるため、公開されないよう適切に管理してください。

3. vercel.json を設定する(必要な場合)

プロジェクトの動作やデプロイ設定を細かく制御したい場合は、プロジェクトルートに vercel.json ファイルを作成します。

このファイルでは、ビルド設定や関数の設定など、Vercel 上での動作に関する追加設定を記述できます。基本的な Next.js プロジェクトでは必須ではありませんが、特定の要件がある場合に利用します。

   {
     "version": 2,
     "builds": [
       {
         "src": "package.json",
         "use": "@vercel/next"
       }
     ]
   }
  1. デプロイ:
   vercel
   # または GitHub と連携して自動デプロイ

接続プーリングの最適化(本番環境向け)

本番環境では、接続プーリングを適切に設定することが重要です。下のコードは、Node.js から PostgreSQL へ接続するためのデータベース接続設定を実装しています。

pg の Pool を利用して接続を管理し、環境に応じて SSL 設定を切り替えています。また、最大接続数やタイムアウト時間を設定することで、データベースへの負荷を制御します。終了時には接続を解放し、リソースリークを防止しています。query メソッドを通じて、アプリケーションから安全に SQL を実行できます。

import { Pool } from 'pg';
import dotenv from 'dotenv';

dotenv.config();

const isProduction = process.env.NODE_ENV === 'production';

const connectionString = isProduction
  ? process.env.POSTGRES_URL
  : process.env.POSTGRES_URL;

const pool = new Pool({
  connectionString,
  ssl: isProduction ? { rejectUnauthorized: false } : false,
  max: 20, // 最大接続数
  idleTimeoutMillis: 30000, // アイドル状態の接続を閉じるまでの時間
  connectionTimeoutMillis: 2000, // 新しい接続のタイムアウト
});

// 接続のリークを防ぐためのクリーンアップ
process.on('exit', () => {
  pool.end();
});

export default {
  query: (text: string, params?: any[]) => pool.query(text, params),
  pool, // 必要に応じてプール自体もエクスポート
};

セキュリティのベストプラクティス

  1. 環境変数を適切に保護
  2. データベース接続に SSL を使用
  3. SQL インジェクションを防ぐために常にパラメータ化されたクエリを使用
  4. 必要な最小限の権限のみをデータベースユーザーに付与
  5. 本番環境では接続文字列を定期的にローテーション

代替データベースオプション

PostgreSQL 以外にも、Next.js でよく使用されるデータベースについて言及します。

MongoDB

以下のコマンドを実行して MongoDB 用の公式ドライバをインストールします。

   npm install mongodb

MongoDB はドキュメント指向データベースで、JSON に近い形式でデータを保存できます。テーブル設計が不要で、データ構造の変更が多いアプリケーションや、柔軟なデータ管理が必要なサービスに適しています。また、大量データの処理やスケールアウトにも対応しやすい特徴があります。

MySQL

MySQL に接続するための Node.js ドライバをインストールします。

   npm install mysql2

MySQL は広く利用されているリレーショナルデータベースで、豊富な実績と安定性があります。SQL を利用したデータ管理が可能で、複雑な検索やデータの関連付けに適しています。また、多くのホスティングサービスで利用できるため、運用環境を構築しやすい点もメリットです。

SQLite

軽量な SQLite データベースを利用するため、以下のライブラリをインストールします。

   npm install better-sqlite3

SQLite はサーバーを必要としないファイルベースのデータベースです。設定が簡単で、開発環境や小規模なアプリケーションに適しています。データベースサーバーを別途用意する必要がないため、素早く開発を開始できます。

ORM を使用する場合

ORM を導入すると、SQL を直接記述する代わりにプログラムのオブジェクトとしてデータベースを操作できます。型安全性や保守性を高められるため、大規模なアプリケーション開発でよく利用されます。

  • Prisma: npm install prisma @prisma/client
  • Sequelize: npm install sequelize pg pg-hstore
  • TypeORM: npm install typeorm reflect-metadata pg

プロジェクトの規模やデータ構造、将来的な拡張性を考慮して、最適なデータベース環境とライブラリを選択しましょう。

トラブルシューティング

接続エラーが発生する場合

  • 接続文字列を再確認
  • データベースがリモート接続を許可しているか確認
  • ファイアウォール設定を確認

Vercel で環境変数が読み込まれない

  • Vercel の環境変数設定を再確認
  • ビルド時に環境変数が利用可能か確認

クエリが遅い

  • インデックスを追加
  • EXPLAIN を使用してクエリを分析
  • 接続プールの設定を調整

まとめ

Next.js アプリケーションでデータベースを運用する際は、基本的な接続方法に加え、実践的な技術の習得が重要です。ORM(Prisma や TypeORM)を導入することで、データ操作を効率化し、保守性を高められます。

また、データベースマイグレーションを設定してスキーマ変更を安全に管理し、トランザクションを活用してデータの整合性を確保することも欠かせません。

さらに、JOIN を用いた複雑なクエリの実装や、監視・パフォーマンスチューニングによる運用改善も重要なポイントです。本ガイドで基本的な接続方法を理解したうえで、実際のプロジェクトでは要件に応じた最適化やセキュリティ対策を取り入れ、より堅牢で効率的なアプリケーションを構築しましょう。