Next.js アプリケーションでデータベースに接続する方法を、手順を追って詳しく説明します。このガイドでは PostgreSQL を例にしますが、他のデータベースでも同様の原則が適用できます。
前提条件
- Node.js がインストールされている
- PostgreSQL がインストールされているか、クラウドサービス(Supabase、Neon、AWS RDS など)のアカウントがある
- 基本的な SQL の知識がある
ステップ 1: データベースの準備
ローカル 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 などのサービスを使用する場合:
- アカウントを作成
- 新しいプロジェクトを作成
- 接続文字列をメモ(後で使用します)
ステップ 2: Next.js プロジェクトのセットアップ
- 新しい Next.js プロジェクトを作成(まだの場合):
npx create-next-app@latest nextjs-database-demo
cd nextjs-database-demo
- 必要な依存関係をインストール:
npm install pg @types/pg dotenv
# または
yarn add pg @types/pg dotenv
ステップ 3: 環境変数の設定
- プロジェクトルートに
.env.localファイルを作成:
POSTGRES_URL="postgres://nextjs_user:securepassword@localhost:5432/nextjs_demo"
# クラウドサービスの場合は次のような形式になります:
# POSTGRES_URL="postgres://user:password@host:port/database?options"
.gitignoreに.env.localが含まれていることを確認
ステップ 4: データベース接続の設定
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),
};
ステップ 5: データベース操作の実装
例: ユーザーテーブルの作成と操作
- 初期セットアップスクリプトを作成
scripts/initDB.ts:
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
ステップ 6: API ルートでのデータベース使用
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' });
}
}
ステップ 7: ページコンポーネントでのデータ取得
SSG (静的生成) でのデータ取得
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 (サーバーサイドレンダリング) でのデータ取得
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,
},
};
};
ステップ 8: Vercel へのデプロイ
- Vercel でプロジェクトを作成
- 環境変数を設定:
- Vercel ダッシュボードでプロジェクトを開く
- “Settings” → “Environment Variables” に移動
- POSTGRES_URL を追加(本番用のデータベース接続文字列)
vercel.jsonをプロジェクトルートに作成(必要に応じて):
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
]
}
- デプロイ:
vercel
# または GitHub と連携して自動デプロイ
ステップ 9: 接続プーリングの最適化(本番環境向け)
本番環境では、接続プーリングを適切に設定することが重要です。lib/db.ts を更新:
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, // 必要に応じてプール自体もエクスポート
};
ステップ 10: セキュリティのベストプラクティス
- 環境変数を適切に保護
- データベース接続に SSL を使用
- SQL インジェクションを防ぐために常にパラメータ化されたクエリを使用
- 必要な最小限の権限のみをデータベースユーザーに付与
- 本番環境では接続文字列を定期的にローテーション
代替データベースオプション
PostgreSQL 以外にも、Next.js でよく使用されるデータベース:
- MongoDB:
npm install mongodb
- MySQL:
npm install mysql2
- SQLite:
npm install better-sqlite3
- ORM を使用する場合:
- Prisma:
npm install prisma @prisma/client - Sequelize:
npm install sequelize pg pg-hstore - TypeORM:
npm install typeorm reflect-metadata pg
トラブルシューティング
- 接続エラーが発生する場合:
- 接続文字列を再確認
- データベースがリモート接続を許可しているか確認
- ファイアウォール設定を確認
- Vercel で環境変数が読み込まれない:
- Vercel の環境変数設定を再確認
- ビルド時に環境変数が利用可能か確認
- クエリが遅い:
- インデックスを追加
- EXPLAIN を使用してクエリを分析
- 接続プールの設定を調整
次の学習ステップ
- ORM (Prisma や TypeORM) の導入
- データベースマイグレーションの設定
- トランザクションの使用方法
- より複雑なクエリの実装(JOIN など)
- データベースの監視とパフォーマンスチューニング
このガイドで、Next.js アプリケーションからデータベースに接続する基本的な方法を理解できたはずです。実際のプロジェクトでは、要件に応じてさらに最適化やセキュリティ対策を追加してください。