db0 Lightweight SQL Connector
repository·main·Indexed 43 days ago
https://github.com/unjs/db0A lightweight, flexible SQL connector providing a unified query API across multiple databases including SQLite, PostgreSQL, MySQL, LibSQL, Cloudflare D1, Neon, and PlanetScale. Version 0.4.1 features a tagged template literal for SQL queries, support for custom connectors, and an opt-in query tracing system using Node.js diagnostics_channel.
What's inside db0
- DB0 is a lightweight SQL connector designed to provide a simple and elegant query API. It is built to be highly flexible, supporting multiple SQL connectors and allowing for easy integration with ORMs or embedding into various web frameworks.
Integrate DB0 with an ORM or framework
mainDB0 is designed to be integrated into your preferred ORM or framework. Currently, there are official integrations available for:
- Drizzle: For users wanting to use Drizzle ORM with DB0.
- Kysely: For users wanting to use Kysely with DB0.
Refer to the specific integration guides for setup instructions and configuration details.
Supported DB0 Connectors
mainDB0 provides a unified API that can be used with various database connectors. Currently supported connectors include:
- Bun
- Cloudflare D1
- LibSQL
- Neon
- PlanetScale
- PostgreSQL (including Prisma Postgres)
- MySQL
- SQLite
Why use the Neon connector instead of Postgres?
mainThe Neon connector is optimized for serverless environments. Unlike the standard Postgres connector which usesnode-postgresand requires a raw TCP connection, the Neon connector uses the@neondatabase/serverlessdriver. This driver communicates via WebSockets, which is preferred for serverless runtimes that may not support opening raw TCP sockets.Avoid column name collisions in DB0 queries
mainBecause DB0 connectors return rows as objects keyed by column name, selecting multiple columns with the same name (e.g., joining two tables that both have an
idcolumn) will cause a collision. DB0 will raise an error if a collision is detected.To resolve this, you must use unique aliases for the colliding columns or use the Drizzle relational query builder, which automatically handles unique aliasing.
const rows = await db .select({ userId: sql<number>`${users.id}`.as("user_id"), postId: sql<number>`${posts.id}`.as("post_id", }) .from(users) .leftJoin(posts, eq(posts.userId, users.id));How transactions work with Prisma and DB0
mainPrisma transactions (
$transaction) are supported only on DB0 connectors that maintain an open session.- Supported: Connectors that keep a session open.
- Unsupported: Connectors that open a new connection per query (e.g., Cloudflare D1, PlanetScale, or
libsqlover HTTP). These connectors reporttransactions: falseand will throw an error if a transaction is attempted.
Concurrency Behavior: Because a DB0 database wraps a single connection, transactions are serialized. If a second transaction is started while one is already in progress, the second transaction will wait for the first to either commit or roll back.
Use static parameters for identifiers like table names
mainDB0 supports Static Parameters, which allow you to inject string literals into parts of a SQL query where prepared statements are not supported, such as table names or column names.
WARNING: SECURITY RISK Static parameters are NOT SANITIZED. Never use static parameters with untrusted input (e.g., data from a request body), as this can lead to SQL injection vulnerabilities.
// Using static parameters for a table name const tableName = "users"; const userId = "1001"; const { rows } = await db.sql`SELECT * FROM {${tableName}} WHERE id = ${userId}`; console.log(rows);How PostgreSQL query parameter rewriting works
mainPostgreSQL natively uses numbered placeholders (
$1,$2, ...), but DB0 uses?. The connector automatically rewrites?to the appropriate$nformat before sending the query to the database.Important Rules:
- Only a
?in the SQL code is treated as a placeholder. - A
?inside a string literal, a quoted identifier, a comment, or a dollar-quoted body is treated as data and is left untouched. - Do not mix styles: You cannot combine hand-written
$nparameters with?placeholders in the same query. This will cause a collision and throw an error.
// DB0 rewrites this to: SELECT * FROM users WHERE id = $1 await db.sql`SELECT * FROM users WHERE id = ${userId}`; // Question marks in strings are untouched await db.sql`INSERT INTO posts (body) VALUES ('what? really?')`; // This will throw an error: await db.prepare("SELECT * FROM users WHERE id = $1 AND name = ?").all(id, name);- Only a
Create a custom connector for a SQL database
mainIf a built-in connector for your specific SQL database integration does not exist indb0, you can implement a custom one. To understand the required implementation details, you should examine the existing built-in connectors in thesrc/connectorsdirectory of the repository to see how they interface with thedb0core.Use the Cloudflare D1 connector
mainConnect DB0 to Cloudflare D1 within Cloudflare Workers. This connector requires D1 to be enabled in your worker environment. The database binding is read from the
envobject exported by thecloudflare:workersruntime.import { createDatabase } from "db0"; import cloudflareD1 from "db0/connectors/cloudflare-d1"; const db = createDatabase( cloudflareD1({ bindingName: "DB", }), );Use the Neon connector instead of Vercel Postgres
mainVercel Postgres has migrated to the Vercel Marketplace, and existing instances have been migrated to Neon. For the best integration withdb0, you should use the Neon Connector rather than attempting to use a Vercel-specific connector.Connect DB0 to MySQL using mysql2
mainTo use MySQL with DB0, you must install the
mysql2package as a dependency. You can then initialize the database by passing themysqlconnector fromdb0/connectors/mysql2to thecreateDatabasefunction.import { createDatabase } from "db0"; import mysql from "db0/connectors/mysql2"; const db = createDatabase( mysql({ /* options */ }), );