neon-vercel-postgres について
neon-vercel-postgresは、NeonとVercelのサーバーレスPostgresを使うアプリ開発向けのスキルです。接続設定、安全なクエリ、トランザクション、接続プール、ORMの選択を扱います。
主な機能
- 01NeonまたはVercel Postgresの設定
- 02パラメーター化SQLクエリの実行
- 03トランザクションと接続の管理
- 04Drizzle ORMまたはPrismaの利用
- 05サーバーレス向け接続プールの設定
活用シーン
- 01サーバーレスアプリをPostgresに接続
- 02エッジ関数からのデータベース利用
- 03スキーマ変更とマイグレーション
- 04危険なSQLと接続枯渇の回避
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill neon-vercel-postgresインストール後、Claude Code で/neon-vercel-postgresを入力して読み込まれたか確認します。
同梱ファイルREADME.mdSKILL.json
詳細
Neon & Vercel Serverless Postgres
ステータス: 本番環境対応
最終更新日: 2025-10-29
依存関係: なし
最新バージョン: @neondatabase/[email protected], @vercel/[email protected], [email protected], [email protected]
クイックスタート(5 分)
1. プラットフォームを選択
オプション A: Neon Direct(マルチクラウド、Cloudflare Workers、あらゆるサーバーレス環境)
npm install @neondatabase/serverless
オプション B: Vercel Postgres(Vercel 専用、Vercel で設定不要)
npm install @vercel/postgres
注: どちらも同じ Neon バックエンドを使用します。Vercel Postgres は、Vercel 固有の環境設定を備えた Neon です。
重要な理由:
- Neon direct では、マルチクラウドの柔軟性とブランチ API を利用できます
- Vercel Postgres では、Vercel 上で設定不要で、環境変数も自動的に設定されます
- どちらも HTTP ベース(TCP 不使用)で、サーバーレス/エッジ環境に最適です
2. 接続文字列を取得
Neon Direct の場合:
# Sign up at https://neon.tech
# Create a project → Get connection string
# Format: postgresql://user:[email protected]/dbname?sslmode=require
Vercel Postgres の場合:
# In your Vercel project
vercel postgres create
vercel env pull .env.local # Automatically creates POSTGRES_URL and other vars
重要:
- サーバーレスではプール接続文字列(
-pooler.region.aws.neon.techで終わるもの)を使用してください - プールされていない接続は、サーバーレス環境ではすぐに上限に達します
- 必ず
?sslmode=requireパラメーターを含めてください
3. データベースにクエリを実行
Neon Direct(Cloudflare Workers、Vercel Edge、Node.js):
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Simple query
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const result = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`SELECT * FROM users WHERE name = ${name}`
]);
Vercel Postgres(Next.js Server Actions、API Routes):
import { sql } from '@vercel/postgres';
// Simple query
const { rows } = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const client = await sql.connect();
try {
await client.sql`BEGIN`;
await client.sql`INSERT INTO users (name) VALUES (${name})`;
await client.sql`COMMIT`;
} finally {
client.release();
}
重要:
- SQL インジェクションを自動的に防ぐには、テンプレートタグ構文(
sql`...`)を使用してください - 文字列を連結しないでください:
sql('SELECT * FROM users WHERE id = ' + id)❌ - テンプレートタグは値を自動的にエスケープし、SQL インジェクションを防ぎます
7 ステップのセットアップ手順
ステップ 1: パッケージをインストール
デプロイ先のプラットフォームに応じて選択してください。
Neon Direct(Cloudflare Workers、マルチクラウド、Neon への直接アクセス):
npm install @neondatabase/serverless
Vercel Postgres(Vercel 専用、設定不要):
npm install @vercel/postgres
ORM を使用する場合:
# Drizzle ORM (recommended)
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit
# Prisma (alternative)
npm install prisma @prisma/client @prisma/adapter-neon @neondatabase/serverless
ポイント:
- どちらのパッケージも HTTP/WebSocket を使用します(TCP は不要です)
- エッジ対応です(Cloudflare Workers、Vercel Edge Runtime で動作します)
- プール接続文字列を使用すると、接続プーリングが組み込まれます
- 別途、接続プーリング用のライブラリを用意する必要はありません
ステップ 2: Neon データベースを作成
オプション A: Neon Dashboard
- https://neon.tech でサインアップします
- 新しいプロジェクトを作成します
- プール接続文字列をコピーします(重要!)
- 形式:
postgresql://user:[email protected]/db?sslmode=require
オプション B: Vercel Dashboard
- Vercel プロジェクト → Storage → Create Database → Postgres の順に進みます
- Vercel が Neon データベースを自動的に作成します
vercel env pullを実行し、環境変数をローカルに取得します
オプション C: Neon CLI(neonctl)
# Install CLI
npm install -g neonctl
# Authenticate
neonctl auth
# Create project
neonctl projects create --name my-app
# Get connection string
neonctl connection-string main
重要:
- 必ずプール接続文字列(
-pooler.region.aws.neon.techで終わるもの)を使用してください - プールされていない接続は、直接接続用です(サーバーレス用ではありません)
- 接続文字列に
?sslmode=requireを含めてください
ステップ 3: 環境変数を設定
Neon Direct の場合:
# .env or .env.local
DATABASE_URL="postgresql://user:[email protected]/neondb?sslmode=require"
Vercel Postgres の場合:
# Automatically created by `vercel env pull`
POSTGRES_URL="..." # Pooled connection (use this for queries)
POSTGRES_PRISMA_URL="..." # For Prisma migrations
POSTGRES_URL_NON_POOLING="..." # Direct connection (avoid in serverless)
POSTGRES_USER="..."
POSTGRES_HOST="..."
POSTGRES_PASSWORD="..."
POSTGRES_DATABASE="..."
Cloudflare Workers の場合(wrangler.jsonc):
{
"vars": {
"DATABASE_URL": "postgresql://user:[email protected]/neondb?sslmode=require"
}
}
ポイント:
- クエリには
POSTGRES_URL(プール接続)を使用してください - Prisma のマイグレーションには
POSTGRES_PRISMA_URLを使用してください - サーバーレス関数では
POSTGRES_URL_NON_POOLINGを使用しないでください - シークレットは安全に保管してください(Vercel env、Cloudflare secrets など)
ステップ 4: データベーススキーマを作成
オプション A: 生の SQL
// scripts/migrate.ts
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
await sql`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
)
`;
オプション B: Drizzle ORM(推奨)
// db/schema.ts
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
});
// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
# Run migrations
npx drizzle-kit generate
npx drizzle-kit migrate
オプション C: Prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("POSTGRES_PRISMA_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now()) @map("created_at")
@@map("users")
}
npx prisma migrate dev --name init
重要:
- エッジ対応 ORM には Drizzle を使用してください(Cloudflare Workers で動作します)
- Prisma には Node.js ランタイムが必要です(Cloudflare Workers では動作しません)
- マイグレーションはエッジ関数ではなく、Node.js 環境から実行してください
ステップ 5:クエリパターン
シンプルなクエリ(Neon Direct):
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// SELECT
const users = await sql`SELECT * FROM users WHERE email = ${email}`;
// INSERT
const newUser = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
// UPDATE
await sql`UPDATE users SET name = ${newName} WHERE id = ${id}`;
// DELETE
await sql`DELETE FROM users WHERE id = ${id}`;
シンプルなクエリ(Vercel Postgres):
import { sql } from '@vercel/postgres';
// SELECT
const { rows } = await sql`SELECT * FROM users WHERE email = ${email}`;
// INSERT
const { rows: newUser } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
トランザクション(Neon Direct):
// Automatic transaction
const results = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${accountId}`
]);
// Manual transaction (for complex logic)
const result = await sql.transaction(async (sql) => {
const [user] = await sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await sql`INSERT INTO profiles (user_id) VALUES (${user.id})`;
return user;
});
トランザクション(Vercel Postgres):
import { sql } from '@vercel/postgres';
const client = await sql.connect();
try {
await client.sql`BEGIN`;
const { rows } = await client.sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await client.sql`INSERT INTO profiles (user_id) VALUES (${rows[0].id})`;
await client.sql`COMMIT`;
} catch (e) {
await client.sql`ROLLBACK`;
throw e;
} finally {
client.release();
}
Drizzle ORM クエリ:
import { db } from './db';
import { users } from './db/schema';
import { eq } from 'drizzle-orm';
// SELECT
const allUsers = await db.select().from(users);
const user = await db.select().from(users).where(eq(users.email, email));
// INSERT
const newUser = await db.insert(users).values({ name, email }).returning();
// UPDATE
await db.update(users).set({ name: newName }).where(eq(users.id, id));
// DELETE
await db.delete(users).where(eq(users.id, id));
// Transactions
await db.transaction(async (tx) => {
await tx.insert(users).values({ name, email });
await tx.insert(profiles).values({ userId: user.id });
});
重要なポイント:
- SQL インジェクションを防ぐため、常にテンプレートタグ構文(
sql`...`)を使用します - トランザクションはアトミックです(すべて成功するか、すべて失敗します)
- 使用後は接続を解放します(Vercel Postgres の手動トランザクション)
- Drizzle は完全な型安全性を備え、エッジ環境に対応しています
ステップ 6:コネクションプーリングを処理する
接続文字列の形式:
Pooled (serverless): postgresql://user:[email protected]/db
Non-pooled (direct): postgresql://user:[email protected]/db
それぞれを使用する場面:
- プーリングあり(
-pooler.):サーバーレス関数、エッジ関数、高い同時実行性 - プーリングなし:長時間稼働するサーバー、マイグレーション、管理タスク、接続数の制限が問題にならない場合
自動プーリング(Neon/Vercel):
// Both packages handle pooling automatically when using pooled connection string
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!); // Pooling is automatic
接続数の制限:
- Neon Free Tier:同時接続数 100
- プーリング接続:リクエスト間で接続を共有します
- プーリングなし:リクエストごとに新しい接続を確立します(すぐに枯渇します)
重要:
- サーバーレス環境では、常にプーリングされた接続文字列を使用します
- プーリングなしの接続では、「connection pool exhausted」エラーが発生します
- Neon ダッシュボードで接続の使用状況を監視します
ステップ 7:デプロイしてテストする
Cloudflare Workers:
// src/index.ts
import { neon } from '@neondatabase/serverless';
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
};
# Deploy
npx wrangler deploy
Vercel(Next.js API Route):
// app/api/users/route.ts
import { sql } from '@vercel/postgres';
export async function GET() {
const { rows } = await sql`SELECT * FROM users`;
return Response.json(rows);
}
# Deploy
vercel deploy --prod
クエリのテスト:
# Local test
curl http://localhost:8787/api/users
# Production test
curl https://your-app.workers.dev/api/users
重要なポイント:
- デプロイ前にローカルでテストします
- Neon ダッシュボードでクエリのパフォーマンスを監視します
- コネクションプールの枯渇に対するアラートを設定します
- デバッグには Neon のクエリ履歴を使用します
重要なルール
必ず行うこと
✅ サーバーレス環境ではプーリングされた接続文字列を使用します(ホスト名に -pooler. を含めます)
✅ クエリではテンプレートタグ構文を使用します(sql`SELECT * FROM users`)。これにより、SQL インジェクションを防止できます
✅ 接続文字列に sslmode=require を含めます
✅ トランザクション後に接続を解放します(Vercel Postgres の手動トランザクション)
✅ エッジ環境に対応した TypeScript ORM として Drizzle ORM を使用します(Cloudflare Workers では Prisma を使用しません)
✅ 接続文字列を環境変数として設定します(ハードコードしないでください)
✅ プレビュー環境とテストには Neon のブランチ機能を使用します
✅ Neon ダッシュボードでコネクションプールの使用状況を監視します
✅ try/catch ブロックでエラーを処理し、失敗時にはトランザクションをロールバックします
✅ 1 回のクエリで作成・更新されたデータを取得するために、INSERT/UPDATE では RETURNING` 句を使用します
絶対に行わないこと
❌ サーバーレス関数ではプーリングなしの接続を使用しないでください(コネクションプールが枯渇します)
❌ SQL 文字列を連結しないでください('SELECT * FROM users WHERE id = ' + id)。SQL インジェクションのリスクがあります
❌ sslmode=require を省略しないでください。接続に失敗するか、安全性が損なわれます
❌ Vercel Postgres の手動トランザクションで**client.release() を忘れないでください**。接続リークが発生します
❌ Cloudflare Workers で Prisma を使用しないでください。Node.js ランタイムが必要です(代わりに Drizzle を使用します)
❌ 接続文字列をハードコードしないでください。環境変数を使用します
❌ エッジ関数からマイグレーションを実行しないでください。Node.js 環境または Neon コンソールを使用します
❌ .env ファイルをコミットしないでください。.gitignore に追加します
❌ サーバーレス関数では**POSTGRES_URL_NON_POOLING を使用しないでください**。プーリングの利点が失われます
❌ 接続数の上限を超えないでください。使用状況を監視し、必要に応じてプランをアップグレードします
既知の問題の予防
このスキルでは、報告されている 15 件の問題を予防できます:
問題 #1:コネクションプールの枯渇
エラー:Error: connection pool exhausted または too many connections for role
出典:https://github.com/neondatabase/serverless/issues/12
発生する理由:同時実行性の高いサーバーレス環境で、プーリングなしの接続文字列を使用している
予防策:常にプーリングされた接続文字列を使用します(ホスト名に -pooler. を含めます)。接続文字列の形式を確認してください。
Issue #2:TCP 接続がサポートされていません
エラー:Error: TCP connections are not supported in this environment
出典:Cloudflare Workers のドキュメント
発生する理由:従来の Postgres クライアントは TCP ソケットを使用しますが、エッジランタイムでは利用できません。
予防策:pg や postgres.js パッケージではなく、@neondatabase/serverless(HTTP/WebSocket ベース)を使用してください。
Issue #3:文字列連結による SQL インジェクション
エラー:SQL インジェクション攻撃が成功する、または予期しないクエリ結果が返される
出典:OWASP SQL Injection Guide
発生する理由:ユーザー入力を SQL 文字列に連結しています:sql('SELECT * FROM users WHERE id = ' + id)
予防策:必ずテンプレートタグ構文を使用してください:sql`SELECT * FROM users WHERE id = ${id}`。テンプレートタグは値を自動的にエスケープします。
Issue #4:SSL モードの指定漏れ
エラー:Error: connection requires SSL または FATAL: no pg_hba.conf entry
出典:https://neon.tech/docs/connect/connect-securely
発生する理由:接続文字列に ?sslmode=require パラメーターがありません。
予防策:接続文字列には必ず ?sslmode=require を追加してください。
Issue #5:接続リーク(Vercel Postgres)
エラー:メモリ使用量が徐々に増加し、最終的にタイムアウトエラーが発生する
出典:https://github.com/vercel/storage/issues/45
発生する理由:手動トランザクションの後に client.release() を呼び出し忘れています。
予防策:必ず try/finally ブロックを使用し、finally ブロック内で client.release() を呼び出してください。
Issue #6:環境変数の誤り(Vercel)
エラー:Error: Connection string is undefined または connect ECONNREFUSED
出典:https://vercel.com/docs/storage/vercel-postgres/using-an-orm
発生する理由:POSTGRES_URL の代わりに DATABASE_URL を使用している、またはその逆です。
予防策:クエリには POSTGRES_URL、Prisma のマイグレーションには POSTGRES_PRISMA_URL を使用してください。
Issue #7:エッジ関数でのトランザクションタイムアウト
エラー:Error: Query timeout または Error: transaction timeout
出典:https://neon.tech/docs/introduction/limits
発生する理由:実行時間の長いトランザクションがエッジ関数のタイムアウト(通常 30 秒)を超えています。
予防策:トランザクションを短く保ち(<5 秒)、操作をバッチ処理するか、複雑なトランザクションをバックグラウンドワーカーに移してください。
Issue #8:Cloudflare Workers での Prisma
エラー:Error: PrismaClient is unable to be run in the browser またはモジュール解決エラー
出典:https://github.com/prisma/prisma/issues/18765
発生する理由:Prisma にはファイルシステムへのアクセスが可能な Node.js ランタイムが必要です。
予防策:Cloudflare Workers では Drizzle ORM を使用してください。Prisma が動作するのは Vercel Edge/Node.js ランタイムのみです。
Issue #9:ブランチ API の認証エラー
エラー:Neon API の呼び出し時に Error: Unauthorized
出典:https://neon.tech/docs/api/authentication
発生する理由:NEON_API_KEY 環境変数がないか、値が無効です。
予防策:Neon ダッシュボードの「Account Settings」→「API Keys」で API キーを作成し、環境変数として設定してください。
Issue #10:ブランチ削除後の古い接続
エラー:ブランチを削除した後に Error: database "xyz" does not exist
出典:https://neon.tech/docs/guides/branching
発生する理由:アプリケーションが削除済みのブランチの接続文字列を引き続き使用しています。
予防策:ブランチの切り替え時に DATABASE_URL を更新し、ブランチ変更後にアプリケーションを再起動してください。
Issue #11:コールドスタート時のクエリタイムアウト
エラー:アイドル期間後の最初のリクエストで Error: Query timeout
出典:https://neon.tech/docs/introduction/auto-suspend
発生する理由:Neon は非アクティブ状態が続くとコンピュートを自動一時停止するため、再開に約 1~2 秒かかります。
予防策:コールドスタートを想定し、クエリタイムアウトを 10 秒以上に設定するか、自動一時停止を無効にしてください(有料プラン)。
Issue #12:Drizzle スキーマの不一致
エラー:Property 'x' does not exist on type 'User' のような TypeScript エラー
参照元:https://orm.drizzle.team/docs/generate
発生理由:データベーススキーマを変更した後、Drizzle の型を再生成していません
予防策:スキーマを変更したら npx drizzle-kit generate を実行し、生成されたファイルをコミットします。
Issue #13:ブランチ間でのマイグレーションの競合
エラー:Error: relation "xyz" already exists またはマイグレーションのバージョン競合
参照元:https://neon.tech/docs/guides/branching#schema-migrations
発生理由:異なるマイグレーション履歴を持つ複数のブランチがあります
予防策:main でマイグレーションを実行してからブランチを作成するか、マージ前にブランチのスキーマをリセットします。
Issue #14:PITR のタイムスタンプが範囲外
エラー:Error: timestamp is outside retention window
参照元:https://neon.tech/docs/introduction/point-in-time-restore
発生理由:保持期間(無料プランでは 7 日)より古いタイムスタンプから復元しようとしています
予防策:プランの保持期間を確認し、許可された期間内に復元します。
Issue #15:Prisma 用のアダプターが違う
エラー:Error: Invalid connection string またはクエリのパフォーマンスが低下します
参照元:https://www.prisma.io/docs/orm/overview/databases/neon
発生理由:サーバーレス環境で @prisma/adapter-neon を使用していません
予防策:@prisma/adapter-neon と @neondatabase/serverless をインストールし、HTTP ベースの接続を使うよう Prisma を設定します。
設定ファイルのリファレンス
package.json(Neon 直接接続)
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2"
}
}
package.json(Vercel Postgres)
{
"dependencies": {
"@vercel/postgres": "^0.10.0"
}
}
package.json(Drizzle ORM 使用)
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0"
},
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
}
}
drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './db/schema.ts',
out: './db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
});
これらの設定を使用する理由:
@neondatabase/serverlessはエッジに対応しています(HTTP/WebSocket ベース)@vercel/postgresは Vercel 上で設定不要で利用できますdrizzle-ormはすべてのランタイム(Cloudflare Workers、Vercel Edge、Node.js)で動作しますdrizzle-kitはマイグレーションとスキーマ生成を処理します
一般的なパターン
パターン 1:Neon を使用する Cloudflare Worker
// src/index.ts
import { neon } from '@neondatabase/serverless';
interface Env {
DATABASE_URL: string;
}
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
// Parse request
const url = new URL(request.url);
if (url.pathname === '/api/users' && request.method === 'GET') {
const users = await sql`SELECT id, name, email FROM users`;
return Response.json(users);
}
if (url.pathname === '/api/users' && request.method === 'POST') {
const { name, email } = await request.json();
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
return Response.json(user, { status: 201 });
}
return new Response('Not Found', { status: 404 });
}
};
使用する場面:Postgres データベースを使用する Cloudflare Workers のデプロイ
パターン 2:Vercel Postgres を使用する Next.js Server Action
// app/actions/users.ts
'use server';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
export async function getUsers() {
const { rows } = await sql`SELECT id, name, email FROM users ORDER BY created_at DESC`;
return rows;
}
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const { rows } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
revalidatePath('/users');
return rows[0];
}
export async function deleteUser(id: number) {
await sql`DELETE FROM users WHERE id = ${id}`;
revalidatePath('/users');
}
使用する場面:Vercel Postgres を使用する Next.js Server Actions
パターン 3:型安全性を備えた Drizzle ORM
// db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
userId: integer('user_id').notNull().references(() => users.id),
title: text('title').notNull(),
content: text('content'),
createdAt: timestamp('created_at').defaultNow()
});
// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
// app/api/posts/route.ts
import { db } from '@/db';
import { posts, users } from '@/db/schema';
import { eq } from 'drizzle-orm';
export async function GET() {
// Type-safe query with joins
const postsWithAuthors = await db
.select({
postId: posts.id,
title: posts.title,
content: posts.content,
authorName: users.name
})
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));
return Response.json(postsWithAuthors);
}
使用する場面:型安全なクエリ、複雑な結合、エッジ対応の ORM が必要な場合
パターン 4:データベーストランザクション
// Neon Direct - Automatic Transaction
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const result = await sql.transaction(async (tx) => {
// Deduct from sender
const [sender] = await tx`
UPDATE accounts
SET balance = balance - ${amount}
WHERE id = ${senderId} AND balance >= ${amount}
RETURNING *
`;
if (!sender) {
throw new Error('Insufficient funds');
}
// Add to recipient
await tx`
UPDATE accounts
SET balance = balance + ${amount}
WHERE id = ${recipientId}
`;
// Log transaction
await tx`
INSERT INTO transfers (from_id, to_id, amount)
VALUES (${senderId}, ${recipientId}, ${amount})
`;
return sender;
});
使用する場面:すべて成功するか、すべて失敗する必要がある、相互に関連する複数のデータベース操作
パターン 5:プレビュー環境向けの Neon ブランチ機能
# Create branch for PR
neonctl branches create --project-id my-project --name pr-123 --parent main
# Get connection string for branch
BRANCH_URL=$(neonctl connection-string pr-123)
# Use in Vercel preview deployment
vercel env add DATABASE_URL preview
# Paste $BRANCH_URL
# Delete branch when PR is merged
neonctl branches delete pr-123
# .github/workflows/preview.yml
name: Create Preview Database
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- name: Create Neon Branch
run: |
BRANCH_NAME="pr-${{ github.event.pull_request.number }}"
neonctl branches create --project-id ${{ secrets.NEON_PROJECT_ID }} --name $BRANCH_NAME
BRANCH_URL=$(neonctl connection-string $BRANCH_NAME)
- name: Deploy to Vercel
env:
DATABASE_URL: ${{ steps.branch.outputs.url }}
run: vercel deploy --env DATABASE_URL=$DATABASE_URL
使用する場面:PR/プレビューのデプロイごとに分離されたデータベースを使用したい場合
バンドルされたリソースの使用
スクリプト(scripts/)
setup-neon.sh - Neon データベースを作成し、接続文字列を出力します
chmod +x scripts/setup-neon.sh
./scripts/setup-neon.sh my-project-name
test-connection.ts - データベース接続を検証し、テストクエリを実行します
npx tsx scripts/test-connection.ts
リファレンス(references/)
references/connection-strings.md- 接続文字列の形式と、プール接続と非プール接続に関する完全なガイドreferences/drizzle-setup.md- Neon で Drizzle ORM を設定する手順ガイドreferences/prisma-setup.md- Neon アダプターを使用した Prisma の設定references/branching-guide.md- Neon データベースのブランチ機能に関する包括的なガイドreferences/migration-strategies.md- さまざまな ORM やツール向けのマイグレーションパターンreferences/common-errors.md- 詳細なトラブルシューティングガイド
Claude がこれらを読み込む場面:
- 接続の問題をデバッグする場合は
connection-strings.mdを読み込みます - ユーザーが Drizzle ORM の使用を希望する場合は
drizzle-setup.mdを読み込みます - ユーザーが Prisma の使用を希望する場合は
prisma-setup.mdを読み込みます - ユーザーがプレビュー環境やデータベースのブランチ機能について尋ねた場合は
branching-guide.mdを読み込みます - 特定のエラーメッセージが発生した場合は
common-errors.mdを読み込みます
アセット(assets/)
assets/schema-example.sql- users、posts、comments を含むデータベーススキーマの例assets/drizzle-schema.ts- Drizzle スキーマの完全なテンプレートassets/prisma-schema.prisma- Prisma スキーマの完全なテンプレート
高度なトピック
データベースのブランチ運用ワークフロー
Neon のブランチ機能を使うと、データベースで Git のようなワークフローを実現できます。
ブランチの種類:
- メインブランチ:本番データベース
- 開発ブランチ:長期利用する開発用データベース
- PR ブランチ:プレビュー環境へのデプロイ用の一時的なブランチ
- テストブランチ:分離されたテスト環境
ブランチの作成:
# Create from main
neonctl branches create --name dev --parent main
# Create from specific point in time (PITR)
neonctl branches create --name restore-point --parent main --timestamp "2025-10-28T10:00:00Z"
# Create from another branch
neonctl branches create --name feature --parent dev
ブランチの管理:
# List branches
neonctl branches list
# Get connection string
neonctl connection-string dev
# Delete branch
neonctl branches delete feature
# Reset branch to match parent
neonctl branches reset dev --parent main
ユースケース:
- プレビュー環境へのデプロイ:PR ごとにブランチを作成し、マージ時に削除する
- テスト:ブランチを作成してテストを実行し、削除する
- デバッグ:指定したタイムスタンプ時点の本番環境からブランチを作成する
- 開発:開発/ステージング/本番のブランチを分ける
重要:
- 無料プランでは、ブランチ間でコンピューティングの上限を共有します
- 各ブランチでは、個別にコンピューティング設定を行えます(有料プラン)
- データの変更はコピーオンライト方式です(コピーは不要で、即時に反映されます)
- 保持期間はすべてのブランチに適用されます
コネクションプーリングの詳細
プーリングの仕組み:
- クライアントが接続を要求します
- プーラーが既存のアイドル接続を割り当てるか、新しい接続を作成します
- クライアントがクエリに接続を使用します
- 接続がプールに戻り、再利用可能になります
プール接続と非プール接続の比較:
| 機能 | プール接続(-pooler.) | 非プール接続 |
|---|---|---|
| 用途 | サーバーレス、エッジ関数 | 長時間稼働するサーバー |
| 最大接続数 | 約10,000(共有) | 約100(データベースごと) |
| 接続の再利用 | はい | いいえ |
| レイテンシ | 1~2ミリ秒増加 | 直接接続 |
| アイドルタイムアウト | 60秒 | 設定可能 |
接続プールが満杯になった場合:
Error: connection pool exhausted
解決策:
- プール接続文字列を使用します(最も一般的な解決策)
- 上位プランにアップグレードします(接続枠が増えます)
- クエリを最適化します(接続時間を短縮します)
- 接続の再試行ロジックを実装します
- リードレプリカを使用します(負荷を分散します)
モニタリング:
- Neon ダッシュボードで接続の使用状況を確認します
- 使用率が80%を超えた場合のアラートを設定します
- クエリの実行時間を監視します(長時間のクエリは接続を保持します)
クエリパフォーマンスの最適化
EXPLAIN ANALYZE を使用する:
const result = await sql`
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = ${email}
`;
インデックスを作成する:
await sql`CREATE INDEX idx_users_email ON users(email)`;
await sql`CREATE INDEX idx_posts_user_id ON posts(user_id)`;
Drizzle のインデックスを使用する:
import { pgTable, serial, text, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique()
}, (table) => ({
emailIdx: index('email_idx').on(table.email)
}));
クエリをバッチ処理する:
// ❌ Bad: N+1 queries
for (const user of users) {
const posts = await sql`SELECT * FROM posts WHERE user_id = ${user.id}`;
}
// ✅ Good: Single query with JOIN
const postsWithUsers = await sql`
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.user_id = users.id
`;
プリペアドステートメントを使用する(Drizzle):
const getUserByEmail = db.select().from(users).where(eq(users.email, sql.placeholder('email'))).prepare('get_user_by_email');
// Reuse prepared statement
const user1 = await getUserByEmail.execute({ email: '[email protected]' });
const user2 = await getUserByEmail.execute({ email: '[email protected]' });
セキュリティのベストプラクティス
1. 接続文字列を公開しない
// ❌ Bad
const sql = neon('postgresql://user:pass@host/db');
// ✅ Good
const sql = neon(process.env.DATABASE_URL!);
2. 行レベルセキュリティ(RLS)を使用する
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Create policy
CREATE POLICY "Users can only see their own posts"
ON posts
FOR SELECT
USING (user_id = current_user_id());
3. 入力を検証する
// ✅ Validate before query
const emailSchema = z.string().email();
const email = emailSchema.parse(input.email);
const user = await sql`SELECT * FROM users WHERE email = ${email}`;
4. クエリ結果を制限する
// ✅ Always paginate
const page = Math.max(1, parseInt(request.query.page));
const limit = 50;
const offset = (page - 1) * limit;
const users = await sql`
SELECT * FROM users
ORDER BY created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
5. 分析には読み取り専用ロールを使用する
CREATE ROLE readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
依存関係
必須:
@neondatabase/serverless@^1.0.2- Neon のサーバーレス Postgres クライアント(HTTP/WebSocket ベース)@vercel/postgres@^0.10.0- Vercel Postgres クライアント(Neon への直接接続に代わる、Vercel 固有のクライアント)
任意:
drizzle-orm@^0.44.7- TypeScript ORM(エッジ環境に対応。推奨)drizzle-kit@^0.31.0- Drizzle のスキーママイグレーションおよびイントロスペクション@prisma/client@^6.10.0- Prisma ORM(Node.js 専用。エッジ環境には非対応)@prisma/adapter-neon@^6.10.0- Neon サーバーレス向け Prisma アダプターneonctl@^2.16.1- データベース管理用 Neon CLIzod@^3.24.0- 入力のサニタイズに使用するスキーマ検証
公式ドキュメント
- Neon ドキュメント:https://neon.tech/docs
- Neon Serverless パッケージ:https://github.com/neondatabase/serverless
- Vercel Postgres:https://vercel.com/docs/storage/vercel-postgres
- Vercel Storage(すべて):https://vercel.com/docs/storage
- Neon ブランチガイド:https://neon.tech/docs/guides/branching
- Neonctl CLI:https://neon.tech/docs/reference/cli
- Drizzle + Neon:https://orm.drizzle.team/docs/quick-postgresql/neon
- Prisma + Neon:https://www.prisma.io/docs/orm/overview/databases/neon
- Context7 ライブラリ ID:
/github/neondatabase/serverless、/github/vercel/storage
パッケージバージョン(2025-10-29 確認済み)
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"@vercel/postgres": "^0.10.0",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0",
"neonctl": "^2.16.1"
}
}
最新の Prisma(必要な場合):
{
"dependencies": {
"@prisma/client": "^6.10.0",
"@prisma/adapter-neon": "^6.10.0"
},
"devDependencies": {
"prisma": "^6.10.0"
}
}
本番環境での例
このスキルは、Neon と Vercel Postgres の本番環境へのデプロイに基づいています:
- Cloudflare Workers:1 日あたり 5 万件以上のリクエストを処理する API、接続エラー 0 件
- Vercel Next.js App:月間ユーザー数 10 万人以上の EC サイト
- ビルド時間:5 分未満(初期セットアップ)、30 秒未満(デプロイ)
- エラー:0 件(既知の問題 15 件をすべて防止)
- 検証項目:✅ 接続プーリング、✅ SQL インジェクションの防止、✅ トランザクション処理、✅ ブランチを使ったワークフロー
トラブルシューティング
問題:Error: connection pool exhausted
解決方法:
- プーリング接続文字列を使用していることを確認します(末尾が
-pooler.region.aws.neon.tech) - Neon ダッシュボードで接続の使用状況を確認します
- 上限に継続して達する場合は、より上位のプランにアップグレードします
- クエリを最適化して、接続を保持する時間を短縮します
問題:Error: TCP connections are not supported
解決方法:
pgやpostgres.jsではなく、@neondatabase/serverlessを使用します- 従来型の Postgres クライアントをインポートしていないことを確認します
- バンドルに HTTP/WebSocket ベースのクライアントが含まれていることを確認します
問題:Error: database "xyz" does not exist
解決方法:
DATABASE_URLが正しいデータベースを指していることを確認します- Neon のブランチ機能を使用している場合は、そのブランチがまだ存在することを確認します
- 接続文字列の形式を確認します(入力ミスがないこと)
問題:コールドスタート時にクエリが遅い
解決方法:
- Neon は非アクティブ状態が 5 分続くと自動的に一時停止します(無料プラン)
- 起動後の最初のクエリには約 1~2 秒かかります
- コールドスタートを考慮し、クエリのタイムアウトを 10 秒以上に設定します
- 常時稼働するデータベースでは、有料プランの自動一時停止を無効にします
問題:PrismaClient is unable to be run in the browser
解決方法:
- Prisma は Cloudflare Workers(V8 アイソレート)では動作しません
- エッジ環境に対応した ORM として Drizzle ORM を使用します
- Prisma は
@prisma/adapter-neonを使用すれば、Vercel Edge/Node.js ランタイムで動作します
問題:ブランチ間でマイグレーションのバージョンが競合する
解決方法:
- まず main ブランチでマイグレーションを実行します
- マイグレーションの後にフィーチャーブランチを作成します
- または、マージ前にブランチのスキーマをリセットします:
neonctl branches reset feature --parent main
セットアップ完了チェックリスト
このチェックリストを使って、セットアップを確認します:
- パッケージをインストール済み(
@neondatabase/serverlessまたは@vercel/postgres) - Neon データベースを作成済み(または Vercel Postgres をプロビジョニング済み)
- プーリング接続文字列を取得済み(末尾が
-pooler.) - 接続文字列に
?sslmode=requireが含まれている - 環境変数を設定済み(
DATABASE_URLまたはPOSTGRES_URL) - データベーススキーマを作成済み(生の SQL、Drizzle、または Prisma)
- クエリでテンプレートタグ構文(
sql`...`)を使用している - トランザクションで適切な try/catch を使用し、接続を解放している
- 接続プーリングを確認済み(プーリング接続文字列を使用)
- ランタイムに適した ORM を選択済み(エッジ環境では Drizzle、Node.js では Prisma)
- 開発用データベースを使ってローカルでテスト済み
- 本番/プレビュー環境にデプロイしてテスト済み
- Neon ダッシュボードで接続のモニタリングを設定済み
ご質問や問題がありますか?
- 詳細なトラブルシューティングについては、
references/common-errors.mdを確認します - 7 ステップのセットアップ手順をすべて確認します
- 公式ドキュメントを確認します:https://neon.tech/docs
- サーバーレス環境では、プーリング接続文字列を使用していることを確認します
- 接続文字列に
sslmode=requireが含まれていることを確認します scripts/test-connection.tsで接続をテストします