Drizzle
Build a self-hosted Hot Updater server with Drizzle ORM.
Before you start
Use this recipe in the server project from the custom-server quick start or your existing backend. Make the database and storage environment variables available to the server and schema commands. The React Native project's deploy configuration is a separate step.
drizzleAdapter supports PostgreSQL, MySQL 8.0 or later, and SQLite, through
these Drizzle drivers:
| Database | Drivers |
|---|---|
| PostgreSQL | node-postgres, postgres-js, PGlite, Neon over WebSockets |
| MySQL | mysql2 |
| SQLite | libSQL, better-sqlite3, bun:sqlite |
Hot Updater runs every write in an interactive transaction. On first use, the
adapter checks that the driver can run one and throws
DrizzleTransactionUnsupportedError for a driver that cannot, such as Neon
over HTTP. CockroachDB and SQL Server are not supported.
Installation
Install Drizzle ORM and SQLite driver.
npm install @hot-updater/server @hot-updater/aws drizzle-orm better-sqlite3npm install --save-dev hot-updater drizzle-kit @types/better-sqlite3For other databases, see Drizzle's adapter documentation.
Setup
Create a database connection file:
import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
const sqlite = new Database("./data/database.db");
export const db = drizzle(sqlite);Hot Updater reads and writes through SQL, so the Drizzle instance does not need Hot Updater's schema.
Hot Updater configuration
Create the Hot Updater instance:
import { createHotUpdater } from "@hot-updater/server";
import { drizzleAdapter } from "@hot-updater/server/adapters/drizzle";
import { apiKeys, insights } from "@hot-updater/server/plugins";
import { s3Storage } from "@hot-updater/aws";
import { db } from "./drizzle";
export const hotUpdater = createHotUpdater({
database: drizzleAdapter({ db, provider: "sqlite" }),
plugins: [insights(), apiKeys()],
storage: s3Storage({
region: "auto",
endpoint: process.env.R2_ENDPOINT,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
bucketName: process.env.R2_BUCKET_NAME!,
}),
});drizzleAdapter takes db, a Drizzle database or a function that returns
one on first use, and provider: "postgresql", "mysql" or "sqlite",
matching the driver. It returns an engine database with the schema generator
and migrator that hot-updater db runs. On
better-sqlite3 and bun:sqlite, which run synchronously, the adapter runs one
statement at a time and begins each transaction with BEGIN IMMEDIATE.
insights() records app events, and apiKeys() protects the client routes
with the x-api-key header.
Schema generation
Generate the Drizzle schema for Hot Updater tables.
mkdir -p data
npx hot-updater db generate src/hotUpdater.ts --yesThis command writes hot-updater-schema.ts at the project root: every table,
key and index, with each column typed exactly as Hot Updater's SQL schema
declares it, and no foreign keys. The tables are core's and those of the
server's plugins.
Point Drizzle Kit at the generated file and the same SQLite database. If your
config already lists schema files, add ./hot-updater-schema.ts to its
schema array:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./hot-updater-schema.ts",
out: "./drizzle",
dialect: "sqlite",
dbCredentials: {
url: "./data/database.db",
},
});Apply the schema, then write the schema settings, before starting the server:
npx drizzle-kit push
npx hot-updater db migrate src/hotUpdater.ts --yesDrizzle Kit creates the tables. db migrate then writes only the settings
rows. It asks you to apply the tables first when they are missing, and it
refuses a database created by an older Hot Updater schema instead of
converting it. The server checks the settings rows before its first read or
write; until they exist, it throws HotUpdaterSchemaMigrationRequiredError
and its handlers answer 503. A running server checks again on its next
request. Run all three commands again after upgrading Hot Updater.
Need plain SQL to apply yourself? See SQL
Export for standalone SQL generation with
the --sql flag.
CLI configuration
After the schema is applied, create the client API key
using this same direct server configuration. Then
connect the React Native project's CLI to the
protected admin mount. Its storage configuration must match a server entry,
and its plugins must list the server's plugins.
API endpoints
The adapter persists data; your framework mounts the HTTP handlers. Continue with the Hono, Express or Elysia recipe. See the handler boundaries and repository contract for client and admin access.
Finish with Set up your app and Test an OTA update.