RORK LABEN
RORK MAX — Rork MaxがiPhone・iPad・Apple Watch・Apple TV・Vision Pro向けのネイティブSwiftアプリを生成できるようになりましたPUBLISH — Rork Maxは Xcode不要の「2クリックApp Store公開」に対応。申請・公開までの摩擦を大きく減らしますEXPO — 標準のRorkはReact Native(Expo)ベースで、プレーンな英語の説明からiOS/Androidネイティブアプリを生成しますPRICING — Rorkは無料で始められ、有料プランは月額$25から。個人開発で試しやすい価格帯ですFUNDING — Rorkがa16z(Andreessen Horowitz)から$2.8Mを調達。AIアプリビルダー領域への資金流入が続いていますREVIEW — 実運用では生成コードの可読性・保守性、Expo由来の制約、課金・プッシュ・広告SDKの組み込みやすさが評価の鍵になりますRORK MAX — Rork MaxがiPhone・iPad・Apple Watch・Apple TV・Vision Pro向けのネイティブSwiftアプリを生成できるようになりましたPUBLISH — Rork Maxは Xcode不要の「2クリックApp Store公開」に対応。申請・公開までの摩擦を大きく減らしますEXPO — 標準のRorkはReact Native(Expo)ベースで、プレーンな英語の説明からiOS/Androidネイティブアプリを生成しますPRICING — Rorkは無料で始められ、有料プランは月額$25から。個人開発で試しやすい価格帯ですFUNDING — Rorkがa16z(Andreessen Horowitz)から$2.8Mを調達。AIアプリビルダー領域への資金流入が続いていますREVIEW — 実運用では生成コードの可読性・保守性、Expo由来の制約、課金・プッシュ・広告SDKの組み込みやすさが評価の鍵になります
記事一覧/開発ツール
開発ツール/2026-06-15上級

Rork アプリの Neon × Drizzle バックエンドを本番で運用する — エッジ接続・無停止マイグレーション・型安全クエリの実装メモ

Rork で生成したアプリのバックエンドに Neon Serverless Postgres と Drizzle ORM を据えたあと、本番で詰まる箇所を実装ベースで整理しました。エッジでの接続モデルの選択、無停止マイグレーション、型安全クエリの設計までをまとめています。

rork57neondrizzle-ormcloudflare-workers2postgresserverlessバックエンド運用

プレミアム記事

Rork で出したアプリのバックエンドを Neon と Drizzle で組み、ローカルでは気持ちよく動いていました。本番に出して数日、特定の画面だけ時々レイテンシが跳ねる。原因は単純で、エッジから一回ごとに HTTP でクエリを投げていたのに、その画面だけ小さなクエリを連続で打っていたためでした。

ローカルの Postgres は同一マシン内なので往復が見えません。エッジに移すと、一回のリクエストで何回データベースに触れたかがそのまま体感速度になります。Neon と Drizzle の「最初の動くところ」までは記事も多いのですが、本番で効いてくるのはその先の判断です。個人開発で複数のアプリを運用していると、この「動いた後」の手当てこそが時間を食う部分でした。ここでは、実際に運用してみて手を入れた箇所を順に書いていきます。

まず決めるのは接続モデル — neon-http か WebSocket Pool か

@neondatabase/serverless には二つの入口があります。neon() が返す HTTP ドライバーと、Pool を使う WebSocket ドライバーです。チュートリアルの多くは前者だけを紹介しますが、本番ではここを最初に意思決定したほうがよいです。

HTTP ドライバーは一回のクエリを一回の fetch として送ります。コネクションを張りっぱなしにできない Cloudflare Workers のようなエッジ環境と相性がよく、コールドスタートの影響も受けにくい。半面、複数のクエリを一つのトランザクションとしてまとめる「対話的トランザクション」は扱えません。

// src/db/http.ts — 単発クエリ中心の経路はこちら
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";
 
export function createHttpDb(databaseUrl: string) {
  const sql = neon(databaseUrl);
  return drizzle(sql, { schema });
}

一方で、Stripe の決済確定と購入履歴レコードの作成を「どちらも成功するか、どちらも失敗するか」で縛りたい場面では、WebSocket の Pool を使ってトランザクションを開きます。決済まわりは中途半端な書き込みが残ると後始末が重いので、ここだけはトランザクションを妥協しません。

// src/db/pool.ts — トランザクションが要る経路はこちら
import { drizzle } from "drizzle-orm/neon-serverless";
import { Pool } from "@neondatabase/serverless";
import * as schema from "./schema";
 
export function createPoolDb(databaseUrl: string) {
  const pool = new Pool({ connectionString: databaseUrl });
  return drizzle(pool, { schema });
}

私の場合は、読み取りと単発の書き込みは HTTP、複数行をまとめて整合させたいエンドポイントだけ Pool、という二経路に分けることを推奨します。全部を Pool にしてしまうと WebSocket のハンドシェイク分だけ初回が遅くなり、全部を HTTP にすると整合性を ORM の外で手当てすることになります。判断軸は「このエンドポイントは複数の書き込みを一つの結果として成立させる必要があるか」です。必要がなければ HTTP のままが軽くて速い、というのが実際に測ってみての結論でした。

地理も無視できません。Neon のリージョンとアプリの主要ユーザーが離れていると、HTTP の一往復ごとに固定の遅延が乗ります。日本のユーザーが中心なら Neon 側も近いリージョンに寄せる。エッジが速くても、データベースまでの物理距離は縮みません。

一回のリクエストで何回 DB に触れているかを数える

跳ねるレイテンシの正体は、たいてい一画面あたりのクエリ本数です。Drizzle はクエリビルダーが素直なぶん、つい一覧を取ってからループの中で関連データを引きたくなります。これがそのまま N+1 になります。

// アンチパターン: 投稿N件に対して著者をN回引く
const posts = await db.select().from(postsTable).limit(20);
for (const p of posts) {
  // ← この1行が20回の往復になる
  p.author = await db.select().from(users).where(eq(users.id, p.userId));
}

エッジでは、この20往復が体感の数百ミリ秒になります。私の計測では、同じ画面を一回の結合に置き換えただけで応答が約3倍速くなりました。Drizzle の関連クエリ(relational query)にまとめると、必要な結合を一回で取りに行けます。

// 改善: 関連を宣言して一回で取る
const posts = await db.query.postsTable.findMany({
  limit: 20,
  orderBy: (p, { desc }) => [desc(p.createdAt)],
  with: {
    author: {
      columns: { id: true, displayName: true, avatarUrl: true },
    },
  },
});

ここで columns を絞っているのは見栄えの問題ではありません。一覧に出さない本文や内部フラグまで毎回引くと、転送量とシリアライズのコストが積み上がります。エッジは CPU 時間にも上限があるので、返すカラムを必要分に削るのは実利があります。

集計も同様です。投稿数のような数だけ欲しい値は、行を全部取ってから length を数えるのではなく、$count でデータベース側に数えさせます。

const postCount = await db.$count(postsTable, eq(postsTable.userId, userId));

運用で効いたのは、開発時にクエリ本数を可視化しておくことでした。Drizzle のロガーを有効にして、一リクエストあたりのクエリ数を出力に出しておくと、画面を一つ作るたびに「この一覧、いつのまにか N+1 になっていないか」を目で確認できます。本番に出してから気づくより、はるかに安いコストで直せます。

const db = drizzle(sql, { schema, logger: env.ENVIRONMENT !== "production" });

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
neon-http と WebSocket Pool をどちらに振るかを、トランザクション要件と地理的レイテンシから判断する基準
本番テーブルをロックせずにカラムを変える expand / contract の手順と、Drizzle Kit での落とし込み方
N+1 を生まない関連データ取得と、scale-to-zero 環境でクエリ数を測る実務的な観測の置き方
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-06-09
Rork Max でディープリンクを実装する — Universal Links と URL Schemes
Rork Max アプリにディープリンクを追加する実装ガイド。URL Scheme と Universal Links の使い分け、AASA/assetlinks の落とし穴、コールド起動の取りこぼしを実例つきで解説します。
開発ツール2026-05-25
Rork で作る iOS アプリに StoreKit 2 で買い切り課金を実装するノート
Rork が生成する Swift / SwiftUI コードに StoreKit 2 で買い切り課金を後付け実装する手順メモ。累計 5,000 万 DL の壁紙アプリで StoreKit 1 から 2 へ移行した経験を踏まえて、ProductID 設計・Transaction 検証・Paywall UI・本番ハマりどころまで実装サンプル込みで共有します。
開発ツール2026-05-24
Rork アプリでバックグラウンド復帰時に画面が真っ白/状態が消える原因の切り分け
Rork で作ったアプリをバックグラウンドに置いたまま数時間放置して戻ると、画面が真っ白になったり、編集途中のデータが消えていたり、再ログインを求められたりする。AppState・AsyncStorage 復元タイミング・iOS のメモリ回収・Navigation state 復元の4つの観点から切り分ける手順を、5,000万DL の壁紙・癒し系アプリを個人開発してきた経験を交えてまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →