|
| 1 | +--- |
| 2 | +title: Drizzle ORM |
| 3 | +description: Use Drizzle ORM with Nitro SQLite through the react-native-nitro-sqlite-drizzle driver. |
| 4 | +--- |
| 5 | + |
| 6 | +`react-native-nitro-sqlite-drizzle` runs [Drizzle ORM](https://orm.drizzle.team) queries on a Nitro SQLite connection. Pass an open connection to `drizzle()` and query it with Drizzle's API. Every query is asynchronous. |
| 7 | + |
| 8 | +## Install |
| 9 | + |
| 10 | +Install the driver with Drizzle and Nitro SQLite. Drizzle Kit and the Babel plugin are only needed for migrations. |
| 11 | + |
| 12 | +```bash |
| 13 | +npm install react-native-nitro-sqlite react-native-nitro-sqlite-drizzle drizzle-orm |
| 14 | +npm install --save-dev drizzle-kit babel-plugin-inline-import |
| 15 | +``` |
| 16 | + |
| 17 | +The driver supports `drizzle-orm` 0.45.4 and later 0.x releases. |
| 18 | + |
| 19 | +## Create a database |
| 20 | + |
| 21 | +```ts |
| 22 | +import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core' |
| 23 | +import { open } from 'react-native-nitro-sqlite' |
| 24 | +import { drizzle } from 'react-native-nitro-sqlite-drizzle' |
| 25 | + |
| 26 | +export const users = sqliteTable('users', { |
| 27 | + id: integer('id').primaryKey(), |
| 28 | + name: text('name').notNull(), |
| 29 | +}) |
| 30 | + |
| 31 | +export const db = drizzle(open({ name: 'app.sqlite' })) |
| 32 | + |
| 33 | +await db.insert(users).values({ id: 1, name: 'Ada' }) |
| 34 | +const rows = await db.select().from(users) |
| 35 | +``` |
| 36 | + |
| 37 | +Create `db` once, outside your components. `drizzle(connection, config)` accepts Drizzle's `schema`, `logger`, and `casing` options. `db.$client` is the connection passed to `drizzle()`. |
| 38 | + |
| 39 | +SQLite stores binary values as BLOBs. When you pass a `Buffer` or another typed-array view through Drizzle, the driver binds its bytes as an `ArrayBuffer`, preserving the view's byte range. |
| 40 | + |
| 41 | +## Migrations |
| 42 | + |
| 43 | +Drizzle Kit generates SQL migration files from your schema. Set `driver: 'expo'` so it also writes a `migrations.js` file that bundles them for React Native: |
| 44 | + |
| 45 | +```ts title="drizzle.config.ts" |
| 46 | +import { defineConfig } from 'drizzle-kit' |
| 47 | + |
| 48 | +export default defineConfig({ |
| 49 | + schema: './db/schema.ts', |
| 50 | + out: './drizzle', |
| 51 | + dialect: 'sqlite', |
| 52 | + driver: 'expo', |
| 53 | +}) |
| 54 | +``` |
| 55 | + |
| 56 | +Run `npx drizzle-kit generate` after each schema change. |
| 57 | + |
| 58 | +`migrations.js` imports `.sql` files. Add the Babel plugin so they load as strings: |
| 59 | + |
| 60 | +```js title="babel.config.js" |
| 61 | +module.exports = { |
| 62 | + presets: ['module:@react-native/babel-preset'], |
| 63 | + plugins: [['inline-import', { extensions: ['.sql'] }]], |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +Add `sql` to Metro's source extensions: |
| 68 | + |
| 69 | +```js title="metro.config.js" |
| 70 | +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config') |
| 71 | + |
| 72 | +const defaultConfig = getDefaultConfig(__dirname) |
| 73 | + |
| 74 | +module.exports = mergeConfig(defaultConfig, { |
| 75 | + resolver: { sourceExts: [...defaultConfig.resolver.sourceExts, 'sql'] }, |
| 76 | +}) |
| 77 | +``` |
| 78 | + |
| 79 | +Run the migrations when the app starts with `useMigrations`. It applies the migrations this database has not run yet, in one transaction, and records them in the `__drizzle_migrations` table: |
| 80 | + |
| 81 | +```tsx |
| 82 | +import { Text } from 'react-native' |
| 83 | +import { useMigrations } from 'react-native-nitro-sqlite-drizzle' |
| 84 | +import migrations from './drizzle/migrations' |
| 85 | +import { db } from './db' |
| 86 | + |
| 87 | +export function App() { |
| 88 | + const { success, error } = useMigrations(db, migrations) |
| 89 | + |
| 90 | + if (error) return <Text>Migration failed: {error.message}</Text> |
| 91 | + if (!success) return <Text>Migrating...</Text> |
| 92 | + return <Text>Ready</Text> |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +To run migrations outside a component, such as in startup code or a test, call `migrate` and await it: |
| 97 | + |
| 98 | +```ts |
| 99 | +import { migrate } from 'react-native-nitro-sqlite-drizzle' |
| 100 | + |
| 101 | +await migrate(db, migrations) |
| 102 | +``` |
| 103 | + |
| 104 | +Calls using the same connection wait for earlier migration calls before checking which migrations are pending. React Strict Mode can replay `useMigrations` without applying the same migration twice. If a migration fails, its transaction rolls back and a later call can retry it. |
| 105 | + |
| 106 | +## Transactions |
| 107 | + |
| 108 | +Run every query inside a transaction through `tx`: |
| 109 | + |
| 110 | +```ts |
| 111 | +import { eq } from 'drizzle-orm' |
| 112 | + |
| 113 | +await db.transaction(async (tx) => { |
| 114 | + await tx.insert(users).values({ id: 2, name: 'Grace' }) |
| 115 | + await tx.update(users).set({ name: 'Ada L.' }).where(eq(users.id, 1)) |
| 116 | +}) |
| 117 | +``` |
| 118 | + |
| 119 | +A transaction holds the connection until its callback finishes. A query awaited through `db` inside the callback, including a query prepared from `db`, waits for the transaction and never completes. Nested `tx.transaction()` calls use savepoints. |
| 120 | + |
| 121 | +`tx.rollback()` rolls back the transaction and rejects it with a `NitroSQLiteError` whose message is `Rollback`, not Drizzle's `TransactionRollbackError`. The `behavior` option of `db.transaction()` is ignored: transactions start with `BEGIN`. See [transactions](/docs/guides/transactions) for how Nitro SQLite runs them. |
0 commit comments