|
| 1 | +# @witchcraft/nuxt-postgres |
| 2 | + |
| 3 | +[![npm version][npm-version-src]][npm-version-href] |
| 4 | +[![npm downloads][npm-downloads-src]][npm-downloads-href] |
| 5 | +[![License][license-src]][license-href] |
| 6 | +[![Nuxt][nuxt-src]][nuxt-href] |
| 7 | + |
| 8 | +Nuxt module to connect to postgres with drizzle. Also has support for having a client [PGlite](https://pglite.dev/) db and a server pglite instance for testing. |
| 9 | + |
| 10 | +# Setup |
| 11 | + |
| 12 | +```ts |
| 13 | +// nuxt.config.ts |
| 14 | +export default defineNuxtConfig({ |
| 15 | + modules: ["@witchcraft/nuxt-postgres"], |
| 16 | + postgres: { |
| 17 | + connectionsOptions: { |
| 18 | + ... |
| 19 | + }, |
| 20 | + } |
| 21 | +}) |
| 22 | +``` |
| 23 | + |
| 24 | +Use the included drizzle config if you want, it will ensure you define the right variables: |
| 25 | +```ts |
| 26 | +```ts [drizzleConfig.ts] |
| 27 | +// careful with imports, esm is borked, see nuxt-postgres/src/drizzleConfig.ts |
| 28 | +// special workarounds were created for the these imports so they work |
| 29 | +import { drizzleConfig } from "@witchcraft/nuxt-postgres/drizzleConfig.js" |
| 30 | +import { ensureEnv } from "@witchcraft/nuxt-utils/utils/ensureEnv.js" |
| 31 | +import { defineConfig } from "drizzle-kit" |
| 32 | +import path from "path" |
| 33 | +
|
| 34 | +// you can ensure futher env vars here |
| 35 | +ensureEnv(process.env, [ |
| 36 | + "ROOT_DIR", |
| 37 | +] as const) |
| 38 | +
|
| 39 | +export default defineConfig({ |
| 40 | + ...drizzleConfig, |
| 41 | + schema: path.resolve(process.env.ROOT_DIR, "db/schema.ts"), |
| 42 | + // change if you changed it |
| 43 | + // out: "./db/migrations", |
| 44 | +}) |
| 45 | +``` |
| 46 | + |
| 47 | +To migrate the db when starting the server: |
| 48 | +```ts [server/plugins/init.ts] |
| 49 | +import { migrate } from "#postgres" |
| 50 | +
|
| 51 | +export default defineNitroPlugin(() => { |
| 52 | + // there's no way to await this yet |
| 53 | + // see https://github.com/nitrojs/nitro/issues/915 |
| 54 | + void migrate({ |
| 55 | + // initialization script |
| 56 | + // e.g. create extensions |
| 57 | + preMigrationScript: `CREATE EXTENSION IF NOT EXISTS pg_uuidv7;` |
| 58 | + }) |
| 59 | +}) |
| 60 | +``` |
| 61 | + |
| 62 | +```ts |
| 63 | +// in api handler |
| 64 | +
|
| 65 | +export default defineEventHandler(async event => { |
| 66 | + const pg = event.context.$postgres |
| 67 | +}) |
| 68 | +``` |
| 69 | + |
| 70 | +// or |
| 71 | +```ts |
| 72 | +import { postgres } from "#postgres" |
| 73 | +
|
| 74 | +``` |
| 75 | + |
| 76 | +## Local Postgres Client |
| 77 | + |
| 78 | +The module also supports having a local postgres client using PGlite, just enable the `postgres.useClientDb` option. |
| 79 | + |
| 80 | +Then define a `db/client-schema.ts` file and create a `drizzle-client.config.ts` file in the root of your project (there is an equivelant `clientDrizzleConfig` you can import like above). |
| 81 | + |
| 82 | +Modify the package.json scripts: |
| 83 | + |
| 84 | +```json |
| 85 | +{ |
| 86 | + "scripts": { |
| 87 | + "db:generate": "drizzle-kit generate && drizzle-kit generate --config drizzle-client.config.ts", |
| 88 | + "db:migrate": "drizzle-kit migrate", |
| 89 | + } |
| 90 | +} |
| 91 | +``` |
| 92 | +Migrate does not have a client equivalent. The module handles migrations by generating a `clientMigration.json` file which you can then import. |
| 93 | + |
| 94 | +Now you can use your client side db. |
| 95 | + |
| 96 | +```ts |
| 97 | +// db is only defined if import.meta.client is true |
| 98 | +
|
| 99 | +const db = await useClientDb("client", |
| 100 | +// options only required on the first use |
| 101 | +{ |
| 102 | + // module client postgres options |
| 103 | + // this is so that it can also be used where nuxt isn't available |
| 104 | + ...useRuntimeConfig().public.postgres, |
| 105 | + clientMigrationOptions: { |
| 106 | + migrationJson: (await import("~~/db/client-migrations/clientMigration.json")).default, |
| 107 | + migrationsLogger: useLogger(), |
| 108 | + }, |
| 109 | + clientPgliteOptions: { |
| 110 | + // additional pglite options |
| 111 | + } |
| 112 | +}, { |
| 113 | + // clientDatabaseManager options |
| 114 | +}) |
| 115 | +await db?.query(...) |
| 116 | +// somewhere else |
| 117 | +
|
| 118 | +const db = useClientDb(/*"client" by default*/) |
| 119 | +// if your db is not named "client", you must use useSwitchDefaultDatabase to have the default useDb use that instance |
| 120 | +useSwitchDefaultDatabase("myDb") |
| 121 | +const db = useClientDb(/* now "myDb"*/) |
| 122 | +
|
| 123 | +
|
| 124 | +// useClientDb also adds window.db and window.sql in dev mode |
| 125 | +// for easier debugging |
| 126 | +await window.dbs.client?.query(...) |
| 127 | +await window.dbs.custom_name?.query(...) |
| 128 | +``` |
| 129 | + |
| 130 | +By default, the module will automatically migrate the client side db on first use of `useClientDb` if `migrationsJson` is passed, hence the await on `useClientDb`. |
| 131 | + |
| 132 | +To disable this, just don't pass `migrationsJson` to `useClientDb`. |
| 133 | + |
| 134 | +You will then need to migrate manually and make sure nothing tries to use the db before then or that your app can handled the db schema being out of date or non-existent: |
| 135 | + |
| 136 | +```ts |
| 137 | +import {migrate} from "#postgres-client" |
| 138 | +
|
| 139 | +// only migrate on the client |
| 140 | +if (import.meta.client) { |
| 141 | + // careful, you can't use useRuntimeConfig().public.postgres.clientMigrationConfig.migrationJsonPath here |
| 142 | + const migrationjson = (await import("~~/db/client-migrations/clientMigration.json")).default |
| 143 | + await migrate({ |
| 144 | + migrationjson |
| 145 | + }) |
| 146 | +} |
| 147 | +const db = useClientDb("client") |
| 148 | +``` |
| 149 | + |
| 150 | +There are several things to keep in mind when using the client side db: |
| 151 | + |
| 152 | +- While the resolved migrationJson location is added to the public runtime config, it cannot be used to import it dynamically since dynamic imports don't work with variables. |
| 153 | +- **The client options are exposed to the public runtime config.** There is no such thing as the private runtimeConfig client side. |
| 154 | +- `migrate` will try to skip migrations if at all possible. Doing a drizzle migration, even if nothing needs to be done, is expensive (~1500ms), so `migrate` stores a localstorage key `db:lastMigrationHash` (configurable) to prevent unnecessary calls to drizzle's migrate. If the database is configured to use indexedDb, it exists, and the last known hash matches the last migration hash, migration is skipped, reducing the time for non-migrations to around 4-5ms. |
| 155 | + |
| 156 | +## Using a Local Server Database for Testing |
| 157 | + |
| 158 | +You can change to use an in-memory pglite database by setting `usePgLiteOnServer` to `true` or some env variable (e.g. `VITEST`) to only change it when that variable is true. You can make it local by specifying `serverPgLiteDataDir`. |
| 159 | + |
| 160 | +## Usage in Other Contexts |
| 161 | + |
| 162 | +The client db can be used in electron or other contexts that support it. You will probably need to specify some options differently for those environments: |
| 163 | + |
| 164 | + |
| 165 | + |
| 166 | +Electron example using [@witchcraft/nuxt-electron](TODO): |
| 167 | +```ts |
| 168 | +const db = await useClientDb("name", { |
| 169 | + clientMigrationOptions: { |
| 170 | + migrationJson, |
| 171 | + migrationsLogger: useElectronLogger(), |
| 172 | + }, |
| 173 | + ...STATIC.ELECTRON_RUNTIME_CONFIG.postgres, |
| 174 | + // we need to override the filepath so it can write to disk |
| 175 | + clientPgLitePath: path.join(userDataDir, "db.pglite"), |
| 176 | +}, { |
| 177 | + logger: useElectronLogger(), |
| 178 | + // import.meta.client is not defined |
| 179 | + bypassEnvCheck: true, |
| 180 | + // there is no window in main |
| 181 | + addToWindowInDev: false, |
| 182 | +} |
| 183 | +
|
| 184 | +``` |
| 185 | + |
| 186 | +### Proxying to Other Contexts |
| 187 | + |
| 188 | +You can use a proxy instead of the default client with the `drizzleProxy` option. |
| 189 | + |
| 190 | +```ts |
| 191 | + await useClientDb("client", { |
| 192 | + ...useRuntimeConfig().public.postgres, |
| 193 | + useWebWorker: false, |
| 194 | + drizzleProxy: isElectron() |
| 195 | + ? async (name: string, sql: string, params: any[], method: "all" | "run" | "get" | "values") => { |
| 196 | + // however you choose to proxy it |
| 197 | + const res = await window.electron.api.db(name, sql, params, method) |
| 198 | + return res |
| 199 | + } |
| 200 | + : undefined |
| 201 | + }) |
| 202 | +``` |
| 203 | + |
| 204 | +<!-- Badges --> |
| 205 | +[npm-version-src]: https://img.shields.io/npm/v/@witchcraft/nuxt-postgres/latest.svg?style=flat&colorA=020420&colorB=00DC82 |
| 206 | +[npm-version-href]: https://npmjs.com/package/@witchcraft/nuxt-postgres |
| 207 | + |
| 208 | +[npm-downloads-src]: https://img.shields.io/npm/dm/@witchcraft/nuxt-postgres.svg?style=flat&colorA=020420&colorB=00DC82 |
| 209 | +[npm-downloads-href]: https://npmjs.com/package/@witchcraft/nuxt-postgres |
| 210 | + |
| 211 | +[license-src]: https://img.shields.io/npm/l/@witchcraft/nuxt-postgres.svg?style=flat&colorA=020420&colorB=00DC82 |
| 212 | +[license-href]: https://npmjs.com/package/@witchcraft/nuxt-postgres |
| 213 | + |
| 214 | +[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js |
| 215 | +[nuxt-href]: https://nuxt.com |
| 216 | + |
0 commit comments