Skip to content

Search docs

Jump between documentation pages.

Browse docs

Database hosting & serverless data providers

DaloyJS doesn't ship a database. It runs on Node, Bun, Deno, Cloudflare Workers, AWS Lambda, Vercel, Netlify, Azure Functions, and Fastly Compute, so the right database depends on which adapter you target.

The pages in this section cover where your data lives: managed hosts and their drivers, separately from how you query it (see ORMs and ODMs). Most providers here pair with Drizzle or Prisma rather than replacing them.

How each runtime reaches the database

Long-lived servers (Node, Bun, Deno) and Node-based serverless functions (Lambda, Vercel, Netlify, Azure Functions) open ordinary TCP connections, so the standard pg and mysql2 drivers work there.

Cloudflare Workers can open outbound TCP too, through the connect() API from cloudflare:sockets, and pg (8.16.3 or later, per Cloudflare's Postgres tutorial) and mysql2 run on Workers with the nodejs_compat flag. A Worker cannot keep a connection open between requests, though, so every request would pay a fresh TCP, TLS and auth handshake to a database that may be far away. Cloudflare recommends putting Hyperdrive in front of Postgres or MySQL: it keeps a warm connection pool near the database and can cache reads. The other two options on Workers are an HTTP / WebSocket driver (Neon, PlanetScale MySQL, Turso, Supabase) and a Workers binding (D1).

What sits between your handler and the data
DaloyJS app on a runtimeSame route code on any adapter
NodeBun / DenoWorkersLambdaVercelAzure Functions
Driver styleHow the runtime reaches the database
TCP (pg, mysql2)TCP via HyperdriveHTTP / WebSocketWorkers bindingEmbedded
Database host or engineWhere your data actually lives
NeonPlanetScaleSupabaseTursoDuckDBD1Aurora DSQLAzure
The driver style decides where a host can run. On Workers, use TCP through Hyperdrive, an HTTP driver, or a binding; elsewhere, plain TCP drivers work.

Supported providers

  • Neon: serverless Postgres with branching, scale-to-zero, and an HTTP/WebSocket driver (@neondatabase/serverless). Also reachable with pg over TCP or through Hyperdrive.
  • PlanetScale: managed MySQL on Vitess with an HTTP driver (@planetscale/database), plus PlanetScale Postgres, which you reach with standard Postgres drivers.
  • Supabase: hosted Postgres plus auth, storage, and realtime via the fetch-based @supabase/supabase-js.
  • Turso: libSQL (SQLite fork) via @libsql/client, over HTTP on edge runtimes.
  • DuckDB: embedded OLAP SQL engine for analytics, local files, Parquet, and ad hoc reporting inside Node.js services.
  • Cloudflare D1: SQLite-compatible database built into Workers, accessed through a runtime binding.
  • AWS Aurora DSQL: distributed PostgreSQL on AWS with IAM token auth, a natural fit for the Lambda adapter.
  • Azure databases: Azure Database for PostgreSQL (pg), Azure SQL Database (mssql), and Cosmos DB (@azure/cosmos) with managed identity and Microsoft Entra tokens instead of passwords, for the Azure Functions adapter or Container Apps.
  • Cloudflare Hyperdrive: not a database but a pooling and caching layer that lets a Worker use pg or mysql2 against any of the Postgres or MySQL hosts above (and RDS, Aurora, or your own server).

Runtime compatibility

"Yes" means the provider's documented driver runs there. Vercel, Netlify, and Azure Functions run Node.js by default, so Node-compatible drivers are expected to work, but the combinations marked "Not verified" have not been tested for this guide. Fastly Compute is a WebAssembly runtime with no TCP and outbound fetch only to configured backends.

ProviderDriver styleNode.jsBun / DenoCloudflare WorkersAWS LambdaVercelNetlifyAzure FunctionsFastly Compute
NeonHTTP / WebSocket, or TCP (pg)YesYesYes (HTTP, or TCP via Hyperdrive)YesNot verifiedNot verifiedNot verifiedNot verified
PlanetScale MySQLHTTP (@planetscale/database)YesYesYes (HTTP, or TCP via Hyperdrive)YesNot verifiedNot verifiedNot verifiedNot verified
PlanetScale PostgresTCP (pg)YesYesYes (TCP via Hyperdrive)YesNot verifiedNot verifiedNot verifiedNo
Supabasefetch-basedYesYesYes (HTTP)YesNot verifiedNot verifiedNot verifiedNot verified
Turso (libSQL)HTTP / WebSocketYesYesYes (HTTP)YesNot verifiedNot verifiedNot verifiedNot verified
DuckDBembedded native libraryYesLimitedNoYesNot verifiedNot verifiedNot verifiedNo
Cloudflare D1Workers bindingREST API, or local dev via WranglerREST API onlyYes (binding)REST API onlyREST API onlyREST API onlyREST API onlyREST API only
Aurora DSQLTCP (pg) + IAM tokenYesNot verifiedNot covered by this guideYesNot verifiedNot verifiedNot verifiedNo
Azure Database for PostgreSQLTCP (pg) + Entra tokenYesNot verifiedNot verified (Hyperdrive needs a password role)Not verifiedNot verifiedNot verifiedYesNo
Azure SQL DatabaseTCP (mssql, tedious) + Entra tokenYesNot verifiedNot verifiedNot verifiedNot verifiedNot verifiedYesNo
Azure Cosmos DB for NoSQLHTTPS (@azure/cosmos) + Entra tokenYesNot verifiedNot verifiedNot verifiedNot verifiedNot verifiedYesNot verified
Any Postgres / MySQLTCP (pg, mysql2)YesYesYes (TCP via Hyperdrive)YesNot verifiedNot verifiedNot verifiedNo

The D1 REST API is meant for administration and tooling, not for serving application traffic from outside Workers.

Choosing one

  • You target Cloudflare Workers exclusively: D1 (built-in), any Postgres or MySQL through Hyperdrive, or Neon / PlanetScale / Turso over HTTP.
  • You want MySQL with database branching: PlanetScale.
  • You want auth + storage + realtime in one package: Supabase.
  • You're all-in on AWS with the Lambda adapter: Aurora DSQL or RDS Postgres via standard pg.
  • You're on Azure Functions or Container Apps: Azure Database for PostgreSQL, Azure SQL, or Cosmos DB with a managed identity.
  • You need SQLite-style data close to users: Turso or D1.
  • You need embedded analytics over files or local data: DuckDB on Node.js or Lambda.

Connection lifecycle

How you open and close connections depends on whether the process outlives the request.

Who owns the connection
your adapterProcess lifetimedoes it outlive one request?
node / bun / denoLong-lived serverone pool per process · app.onClose() drains it
lambda / vercel / netlify / azureServerless functionmodule-scope pool · small max · idle timeout · no shutdown hook
workersPer-request isolateclient per request, or Hyperdrive · HTTP drivers · bindings
Long-lived servers own a pool and close it on shutdown. Serverless functions reuse a module-scope pool without a shutdown hook. Workers open per request or go through Hyperdrive.
  • Long-lived servers. Create one pool per process at startup, app.decorate() it, and close it with app.onClose(). onClose() and onShutdown() run only on the Node, Bun, and Deno adapters, after in-flight requests drain. See Lifecycle & health.
  • Serverless functions (Lambda, Vercel, Netlify, Azure Functions). There is no shutdown hook to rely on: a warm container is frozen and later discarded without notice. Create the pool at module scope so warm invocations reuse it, keep max small (each concurrent instance holds its own connections), set an idle timeout so frozen connections are dropped, and set a maximum connection lifetime below any server-side limit (Aurora DSQL closes connections after 60 minutes). Prefer the provider's pooled endpoint (Neon -pooler, PlanetScale PgBouncer on port 6432, RDS Proxy) when there is one.
  • Cloudflare Workers. A socket cannot be shared across requests. Use an HTTP driver or a D1 binding, create and close a TCP client inside the request, or let Hyperdrive hold the pool for you.

Readiness checks for the database

app.readinesscheck() reports 503 while the app is draining or a plugin is still registering. Pass checks to also gate it on the database: each check gets an AbortSignal, the checks run concurrently, and a check that throws, rejects, returns false, or runs past checkTimeoutMs (default 2000 ms) fails the probe with 503. The body stays { status: "not-ready" }; the failure details are logged server-side only.

src/ready.tsts
import type { App } from "@daloyjs/core";
import { pool } from "./db/pool.ts";

export function readiness(app: App) {
  app.readinesscheck({
    token: process.env.READY_TOKEN,
    checks: {
      db: async (signal) => {
        await pool.query("select 1");
      },
    },
    checkTimeoutMs: 2000,
  });
}

Keep the checks cheap, and point only the readiness probe at /readyz, not the liveness probe: a database outage should take an instance out of rotation, not restart every replica at once. See Lifecycle & health.

Credentials

Keep connection strings and tokens out of the repository and out of logs. On Workers, store them with pnpm wrangler secret put DATABASE_URL (or use a Hyperdrive binding, which keeps the credentials in the Hyperdrive config). On AWS, read them from Secrets Manager or SSM Parameter Store at cold start, or skip passwords entirely with IAM auth (Aurora DSQL, RDS IAM). Never echo driver errors to clients: they can contain hostnames, usernames, and SQL. DaloyJS redacts 5xx detail in production, so let unexpected errors throw and log them server-side.

Region placement

Latency between the function and the database usually dominates. Put serverless functions in the same region as the primary database (Vercel and Lambda let you pick it), use read replicas for far-away readers, and on Workers combine Hyperdrive with Smart Placement so the Worker runs near the database instead of near the user when it makes several queries per request.

Local development

Run Postgres or MySQL in Docker for TCP drivers, use a Neon or PlanetScale development branch, turso dev or a local SQLite file for libSQL, and wrangler dev for D1 (local SQLite) and Hyperdrive (localConnectionString). Keep the same driver and schema in development as in production so dialect differences surface early.

Backups and branching

Every managed host here has point-in-time restore or snapshots (D1 calls it Time Travel), and Neon and PlanetScale also offer database branches, which pair well with preview deployments. Check the retention window on your plan before you depend on it, and test a restore at least once.

Migrations

Run migrations from CI or a one-off task with a privileged role, not from the request path, and let the app connect with a role that can only read and write data. Use the tooling of your query layer: Drizzle Kit, Prisma Migrate, wrangler d1 migrations for D1, and PlanetScale deploy requests for PlanetScale MySQL.

Once you've picked a host, layer your query API on top: Drizzle ORM works with every provider above except DuckDB, and Prisma works with most of them through driver adapters.

Edit this page on GitHub

Last updated