TL;DR
- Supabase Freeプランは2プロジェクトまでという制限があり、PoCを並行して量産する用途だとすぐに詰まる。
- Neon Freeプランなら100プロジェクトまで作れる(2026年8月時点)ので、DBサービス単体としてはPoCの量産に向いている。
- ただしNeonはSupabase Authのようなオールインワン認証機能を前提にしていないため、認証は自前実装が必要になる。本記事では実際にNetlify Functions + Drizzle + jose で組んだ最小構成の認証を紹介する。
この記事について
対象読者
- 個人開発・PoCでDBサービスの選定に迷っている人
- Supabaseを使っていて、無料プランの上限に困っている人
- 認証基盤をフルで乗せるほどではないPoCで、DB選びと認証をどう最小化するか考えている人
前提環境
- OS: macOS(開発は macOS 26 / Apple Silicon)
- 言語: TypeScript 6.x
- ランタイム: Node.js 20.x / npm 10.x
- フロント: React 19.2 / Vite 8
- デプロイ先: Netlify(Functions + 静的ホスティング)
- DB: Neon(PostgreSQL)+ Drizzle ORM 0.45
扱うこと / 扱わないこと
-
扱うこと: SupabaseとNeonの無料プランの制限比較、Drizzle +
@neondatabase/serverless(neon-http)での接続実装、JWTによる最小構成の認証実装例 - 扱わないこと: Supabase Authの詳細な使い方、OAuthを使った本格的な認証基盤の構築、Neon Authの詳細(後述の通り2026年に追加されているが本記事の対象外)
問題(現象)
個人開発でPoCアプリを複数並行して作っていたところ、Supabaseの無料プランで「これ以上プロジェクトを作れません」という壁にぶつかった。
Supabase Freeプランは、無料で使えるアクティブプロジェクト数が2つまでに制限されている。3つ目のPoCを試そうとした時点で、既存のプロジェクトを一時停止または削除しない限り新規作成ができない。
複数のアイデアを同時並行で試したいPoC開発のスタイルとは、この制限が根本的に相性が悪かった。
原因(SupabaseとNeonの設計思想の違い)
なぜこの差が生まれるのかを整理すると、両者はそもそも「プロジェクト」の設計思想が異なる。
- Supabase: 1プロジェクトにDB・認証・ストレージ・Realtime・Edge Functionsをフルバンドルした「BaaS(Backend as a Service)」。プロジェクトが重量級な分、無料で持てる数が絞られている。
- Neon: PostgreSQLの提供に特化した「サーバーレスPostgres」。プロジェクト=軽量なPostgresインスタンスという位置づけで、無料でも数多く持てるように設計されている。
2026年8月時点で確認できた無料プランの比較は以下の通り。
| 項目 | Supabase Free | Neon Free |
|---|---|---|
| プロジェクト数上限 | 2プロジェクト | 100プロジェクト(1プロジェクトあたり10ブランチ) |
| DBストレージ | 500 MB(プロジェクト全体) | プロジェクトあたり0.5 GB |
| 非アクティブ時の挙動 | 1週間放置で自動一時停止 | 5分の非アクティブでコンピュートがスケールツーゼロ(プロジェクト自体は消えない) |
| コンピュート課金(有料プラン) | Proでも常時稼働課金。Neonのようなスケールツーゼロは非対応 | プロジェクトあたり月100 CU-hoursまで無料。使った分だけの従量制 |
| 認証機能 | Supabase Auth(DB・ストレージと統合済み) | Neon Authが追加済み(月間60,000 MAUまで無料)。ただし本記事の実装では未使用 |
この表からわかる通り、「1つのPoCに1つのSupabaseプロジェクトを使い切ってしまう」構成だと、3つ目以降でほぼ確実に詰まる。一方Neonは、プロジェクト数の上限が実質PoC開発では気にならない水準にあるため、DBサービスとしてはNeonに寄せた方が量産しやすい。
なお、Neonにも2026年にNeon Authという認証機能が追加されている。「Neonには認証機能がない」という前提は、2025年時点の情報としては正しかったが、現在は必ずしも正確ではない点は補足しておく。本記事では、利用者が自分1人のPoCという特性上、Neon Authも使わずさらに簡易な自前JWT認証を採用している。
解決策・実装
1. Drizzle + neon-httpドライバでの接続
DrizzleとNeonの組み合わせは、@neondatabase/serverless が提供するHTTPドライバ(drizzle-orm/neon-http)を使う。Netlify FunctionsのようなFaaS環境は実行が短命なため、コネクションプールを維持するWebSocket版(neon-serverless)よりも、リクエストごとに完結するHTTP版の方が相性が良い。
// src/db/client.ts
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import * as schema from "./schema.ts";
export function createDb() {
// 接続文字列はサーバー側の環境変数のみで管理する。
// フロントエンドから直接Neonに接続することはない。
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error("DATABASE_URL is not set");
}
// neon-http: リクエスト単位で完結するドライバ。
// Netlify Functionsのような短命な実行環境に向いている。
return drizzle(neon(databaseUrl), { schema });
}
ポイントは次の3つ。
- スキーマは
src/db/schema.tsにTypeScriptで定義し、設計ドキュメントのDDLと突き合わせておく - 接続文字列は
DATABASE_URLの1本のみで、サーバーサイドでしか使わない - WebSocketプール(
neon-serverless)ではなく、HTTPドライバ(neon-http)を選ぶ。理由は上記の通り実行環境の短命さに合わせるため
Bad / Good の比較
Bad(PoCなのにWebSocketプールを選んでしまう):
// FaaS環境でコネクションプールを維持しようとすると、
// 呼び出しごとにコールドスタート+接続のオーバーヘッドが発生しやすい
import { Pool } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-serverless";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool, { schema });
Good(neon-httpでリクエスト単位に完結させる):
// リクエストごとにHTTP経由でNeonにアクセスする。
// 常駐プロセスを前提としないFaaS環境と相性が良い。
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
const db = drizzle(neon(process.env.DATABASE_URL!), { schema });
2. 認証は自前のパスワード + JWT(jose)で最小構成に
利用者が自分1人のPoCであれば、OAuthやNextAuth / Auth.jsのような本格的な認証基盤を入れる必要性は薄い。今回は「環境変数のパスワード」と「JWT発行・検証」だけの最小構成にした。
- ログイン:
POST /api/auth/loginに{ password }を送り、環境変数AUTH_PASSWORDとcrypto.timingSafeEqualでタイミングセーフに比較する(ハッシュ化はせず平文比較) - JWT発行: ライブラリは
jsonwebtokenではなくjoseを使用。アルゴリズムはHS256、クレームはsub: "owner"(ユーザーテーブルは持たない)、有効期限は30日 - 署名鍵: 環境変数
JWT_SECRET - 以降のAPI:
/api/auth/login以外はすべてAuthorization: Bearer <JWT>を必須にし、ルーター側でrequireAuth()を先に通してからハンドラに渡す
// src/auth.ts
import { SignJWT } from "jose";
// JWTの有効期限(例: "30d")
const JWT_EXPIRES_IN = "30d";
export async function signAuthToken(): Promise<string> {
return new SignJWT({ sub: "owner" })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime(JWT_EXPIRES_IN)
.sign(getJwtSecret());
}
フロント側は、発行されたトークンを localStorage に保存し、APIリクエスト時に自動で Authorization ヘッダーへ付与する。401が返ってきたらトークンを削除してログイン画面に戻す。Cookie / HttpOnLyセッションは使っていない。
Bad / Good の比較
Bad(PoCの初期段階からNextAuthやSupabase Authをフル導入する):
- 複数のOAuthプロバイダ設定、セッションストア、ユーザーテーブル設計などが必要になり、PoCの検証速度が落ちる
- 利用者が自分1人しかいない段階では、認証基盤の複雑さに見合うリターンが薄い
Good(利用者1人のPoCなら共有パスワード + 自前JWTで十分):
- 環境変数1つとJWT発行・検証ロジックだけで完結する
- 必要になったタイミングで、後からNeon AuthやNextAuthに乗せ替える余地も残せる
まとめ
- Supabase Freeは2プロジェクトまでという制限があり、PoCを並行して量産するスタイルとは相性が悪い
- Neon Freeは100プロジェクトまで作れるため、DBサービス単体としてはPoCの量産に向いている(2026年8月時点の情報。プラン内容は変更される可能性があるため、利用前に必ず公式ページで最新情報を確認すること)
- ただしNeonを選ぶ場合、Supabase Authのような統合認証は前提にできない。利用者が少人数・自分だけのPoCであれば、
joseを使った自前JWT認証のような最小構成でも十分に機能する - Neonには2026年にNeon Authが追加されているため、「Neon=認証機能なし」という前提は今後変わっていく可能性がある。認証まで含めて統合的に選びたい場合は、その時点の公式ドキュメントを確認するとよい