|
| 1 | +# Nuxt EdgeDB |
| 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 3](https://nuxt.com) integration for [EdgeDB](https://www.edgedb.com) that aims at being the fastest way to add a fully typed database layer to your Nuxt 3 project. |
| 9 | + |
| 10 | +- [✨ Release Notes](/CHANGELOG.md) |
| 11 | + |
| 12 | +## Features |
| 13 | + |
| 14 | +- 🍱 Zero-config setup; just add `nuxt-edgedb` to your `modules` |
| 15 | +- 🧙 [EdgeDB CLI install](https://www.edgedb.com/docs/cli/index) and [project init](https://www.edgedb.com/docs/cli/edgedb_project/edgedb_project_init) wizards |
| 16 | +- 🎩 Watchers on `dbschema/*`, `queries/*`, `dbschema/migrations/*` bringing _HMR_ to your database |
| 17 | +- 🛟 Auto-imported typed database query client provided by [`@edgedb/generate`](https://www.edgedb.com/docs/clients/js/generation) |
| 18 | +- 🍩 [`edgedb ui`](https://www.edgedb.com/docs/cli/edgedb_ui) injected into [Nuxt Devtools](https://github.com/nuxt/devtools) |
| 19 | + |
| 20 | +## Quick Setup |
| 21 | + |
| 22 | +1. Add `nuxt-edgedb` dependency to your project |
| 23 | + |
| 24 | +```bash |
| 25 | +# Using pnpm |
| 26 | +pnpm add -D nuxt-edgedb |
| 27 | + |
| 28 | +# Using yarn |
| 29 | +yarn add --dev nuxt-edgedb |
| 30 | + |
| 31 | +# Using npm |
| 32 | +npm install --save-dev nuxt-edgedb |
| 33 | +``` |
| 34 | + |
| 35 | +2. Add `nuxt-edgedb` to the `modules` section of `nuxt.config.ts` |
| 36 | + |
| 37 | +```js |
| 38 | +export default defineNuxtConfig({ |
| 39 | + modules: [ |
| 40 | + 'nuxt-edgedb' |
| 41 | + ], |
| 42 | + // Optional, all options has sufficient defaults. |
| 43 | + edgedb: { |
| 44 | + devtools: true, |
| 45 | + watch: true, |
| 46 | + dbschemaDir: 'dbschema', |
| 47 | + queriesDir: 'queries', |
| 48 | + generateInterfaces: true, |
| 49 | + generateQueries: true, |
| 50 | + generateQueryBuilder: true, |
| 51 | + } |
| 52 | +}) |
| 53 | +``` |
| 54 | + |
| 55 | +That's it! You can now use Nuxt EdgeDB in your Nuxt app. ✨ |
| 56 | + |
| 57 | +If you do not already have [EdgeDB](https://www.edgedb.com) installed on your machine, the install wizard will prompt you to install it. 🧙 |
| 58 | + |
| 59 | +## Usage |
| 60 | + |
| 61 | +The module provides 2 auto-imported composables available anywhere inside `server/` context of your Nuxt app. |
| 62 | + |
| 63 | +### useEdgeDb |
| 64 | + |
| 65 | +`useEdgeDb` exposes the raw client from `edgedb` import, that properly uses your Nuxt environment configuration. |
| 66 | + |
| 67 | +```typescript |
| 68 | +// server/api/blogpost/[id].ts |
| 69 | +import { defineEventHandler, getRouterParams } from 'h3' |
| 70 | + |
| 71 | +export default defineEventHandler(async (req) => { |
| 72 | + const params = getRouterParams(req) |
| 73 | + |
| 74 | + const id = params.id |
| 75 | + |
| 76 | + const client = useEdgeDb() |
| 77 | + |
| 78 | + const blogpost = await client.querySingle(` |
| 79 | + select BlogPost { |
| 80 | + title, |
| 81 | + description |
| 82 | + } filter .id = ${id} |
| 83 | + `) |
| 84 | + |
| 85 | + return blogpost |
| 86 | +}) |
| 87 | +``` |
| 88 | + |
| 89 | +### useEdgeDbQueries |
| 90 | + |
| 91 | +`useEdgeDbQueries` exposes all your queries from `dbschema/queries.ts` except you do not need to pass them a client. |
| 92 | + |
| 93 | +They will use the one generated by `useEdgeDb` and is scoped to the current request. |
| 94 | + |
| 95 | +```esdl |
| 96 | +// queries/getBlogPost.edgeql |
| 97 | +select BlogPost { |
| 98 | + title, |
| 99 | + description |
| 100 | +} filter .id = <uuid>$blogpost_id |
| 101 | +``` |
| 102 | + |
| 103 | +```typescript |
| 104 | +// server/api/blogpost/[id].ts |
| 105 | +import { defineEventHandler, getRouterParams } from 'h3' |
| 106 | + |
| 107 | +export default defineEventHandler(async (req) => { |
| 108 | + const params = getRouterParams(req) |
| 109 | + |
| 110 | + const id = params.id |
| 111 | + |
| 112 | + const { getBlogpPost } = useEdgeDbQueries() |
| 113 | + |
| 114 | + const blogPost = await getBlogpost({ blogpost_id: id }) |
| 115 | + |
| 116 | + return blogpost |
| 117 | +}) |
| 118 | +``` |
| 119 | + |
| 120 | +You can still import [queries](https://www.edgedb.com/docs/clients/js/queries) directly from `@db/queries` and pass them the client from `useEdgeDb()`. |
| 121 | + |
| 122 | +```typescript |
| 123 | +// server/api/blogpost/[id].ts |
| 124 | +import { getBlogPost } from '@db/queries' |
| 125 | +import { defineEventHandler, getRouterParams } from 'h3' |
| 126 | + |
| 127 | +export default defineEventHandler(async (req) => { |
| 128 | + const params = getRouterParams(req) |
| 129 | + |
| 130 | + const id = params.id |
| 131 | + |
| 132 | + const client = useEdgeDb() |
| 133 | + |
| 134 | + const blogPost = await getBlogpost(client, { blogpost_id: id }) |
| 135 | + |
| 136 | + return blogpost |
| 137 | +}) |
| 138 | +``` |
| 139 | + |
| 140 | +### useEdgeDbQueryBuilder |
| 141 | + |
| 142 | +`useEdgeDbQueryBuilder` exposes the generated [query builder](https://www.edgedb.com/docs/clients/js/querybuilder) directly to your `server/` context. |
| 143 | + |
| 144 | +```typescript |
| 145 | +// server/api/blogpost/[id].ts |
| 146 | +import { defineEventHandler, getRouterParams } from 'h3' |
| 147 | + |
| 148 | +export default defineEventHandler(async (req) => { |
| 149 | + const params = getRouterParams(req) |
| 150 | + |
| 151 | + const id = params.id |
| 152 | + |
| 153 | + const client = useEdgeDb() |
| 154 | + const e = useEdgeDbQueryBuilder() |
| 155 | + |
| 156 | + const blogPostQuery = e.select( |
| 157 | + e.BlogPost, |
| 158 | + (blogPost) => ({ |
| 159 | + id: true, |
| 160 | + title: true, |
| 161 | + description: true, |
| 162 | + filter_single: { id } |
| 163 | + }) |
| 164 | + ) |
| 165 | + |
| 166 | + const blogPost = await blogPostQuery.run(client) |
| 167 | + |
| 168 | + return blogpost |
| 169 | +}) |
| 170 | +``` |
| 171 | + |
| 172 | +### Typings |
| 173 | + |
| 174 | +All the interfaces generated by EdgeDB are available throuh imports via `@db/interfaces`. |
| 175 | + |
| 176 | +```vue |
| 177 | +<script setup lang="ts"> |
| 178 | +import type { BlogPost } from '@db/interfaces' |
| 179 | +
|
| 180 | +defineProps<{ blogPost: BlogPost }>() |
| 181 | +</script> |
| 182 | +``` |
| 183 | + |
| 184 | +## Production |
| 185 | + |
| 186 | +If you want to get out of development and deploy your database to prodution, you must follow [EdgeDB guides](https://www.edgedb.com/docs/guides/deployment/index). |
| 187 | + |
| 188 | +[EdgeDB](https://www.edgedb.com) is an open-source database that is designed to be self-hosted. |
| 189 | + |
| 190 | +However, they also offer a [Cloud](https://www.edgedb.com/docs/guides/cloud), which is fully compatible with this module thanks to environment variables. |
| 191 | + |
| 192 | +## Q&A |
| 193 | + |
| 194 | +### Will my database client be exposed in userland? |
| 195 | + |
| 196 | +No, `useEdgeDb` and `useEdgeDbQueries` are only available in [server/](https://nuxt.com/docs/guide/directory-structure/server) context of Nuxt. |
| 197 | + |
| 198 | +You can, as an **opt-in** feature, import queries from `@dbschema/queries` on the client. |
| 199 | + |
| 200 | +You will need to provide these queries with a client from `createClient()`. |
| 201 | + |
| 202 | +```vue |
| 203 | +<script setup lang="ts"> |
| 204 | +import { createClient } from 'edgedb' |
| 205 | +import { getUser } from '@dbschema/queries' |
| 206 | +
|
| 207 | +const client = createClient() |
| 208 | +
|
| 209 | +const user = await getUser(client, 42) |
| 210 | +</script> |
| 211 | +``` |
| 212 | + |
| 213 | +You can also, still as an **opt-in** feature, import the query builder to the client. |
| 214 | + |
| 215 | +I guess that can be useful for a super-admin/internal dashboard, but use it at your own risks in terms of security access. |
| 216 | + |
| 217 | +```vue |
| 218 | +<script setup lang="ts"> |
| 219 | +import e, { type $infer } from '@db/builder' |
| 220 | +
|
| 221 | +const query = e.select(e.Movie, () => ({ id: true, title: true })); |
| 222 | +type result = $infer<typeof query>; |
| 223 | +// ^ { id: string; title: string }[] |
| 224 | +</script> |
| 225 | +``` |
| 226 | + |
| 227 | +Be careful with these imports, as if you import wrong queries, you might end up with write operations available to the client, potentially exposing your database. |
| 228 | + |
| 229 | +### How do I run my migrations in production? |
| 230 | + |
| 231 | +- Clone your Nuxt project on your production environment |
| 232 | +- Ensure you have [EdgeDB CLI](https://www.edgedb.com/docs/cli/index) installed on the server |
| 233 | +- Add `edgedb migrate --quiet` to your CLI script |
| 234 | + |
| 235 | +### Should I version generated files? |
| 236 | + |
| 237 | +No, as they are generated with your Nuxt client, you should add them to your `.gitignore` |
| 238 | + |
| 239 | +```.gitignore |
| 240 | +**/*.edgeql.ts |
| 241 | +dbschema/queries.* |
| 242 | +dbschema/query-builder |
| 243 | +``` |
| 244 | + |
| 245 | +You must change these paths accordingly if you change the `**Dir` options. |
| 246 | + |
| 247 | +### Is HMR for my database schema really safe? |
| 248 | + |
| 249 | +Well, it depends on when you want to use it. |
| 250 | + |
| 251 | +I would suggest keeping `watchPrompt` enabled while you casually dev on your project. |
| 252 | + |
| 253 | +That will prevent from running any unwanted migration, and will only prompt when you add new things to your schemas. |
| 254 | + |
| 255 | +If you want to go fast and know what you are doing, you can set `watchPrompt` to false, and profit from automatic migration creation and applying on any change on your schemas. |
| 256 | + |
| 257 | +If you do not want any of these features, just set `watch` to false and you are free to feel safe about changes applied to your development database. |
| 258 | + |
| 259 | +> HMR on your database obviously has **NO** effect in production context. |
| 260 | +
|
| 261 | +## Development |
| 262 | + |
| 263 | +```bash |
| 264 | +# Install dependencies |
| 265 | +npm install |
| 266 | + |
| 267 | +# Generate type stubs |
| 268 | +npm run dev:prepare |
| 269 | + |
| 270 | +# Develop with the playground |
| 271 | +npm run dev |
| 272 | + |
| 273 | +# Build the playground |
| 274 | +npm run dev:build |
| 275 | + |
| 276 | +# Run ESLint |
| 277 | +npm run lint |
| 278 | + |
| 279 | +# Run Vitest |
| 280 | +npm run test |
| 281 | +npm run test:watch |
| 282 | + |
| 283 | +# Release new version |
| 284 | +npm run release |
| 285 | +``` |
| 286 | + |
| 287 | +<!-- Badges --> |
| 288 | +[npm-version-src]: https://img.shields.io/npm/v/nuxt-edgedb/latest.svg?style=flat&colorA=18181B&colorB=28CF8D |
| 289 | +[npm-version-href]: https://npmjs.com/package/nuxt-edgedb |
| 290 | + |
| 291 | +[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-edgedb.svg?style=flat&colorA=18181B&colorB=28CF8D |
| 292 | +[npm-downloads-href]: https://npmjs.com/package/nuxt-edgedb |
| 293 | + |
| 294 | +[license-src]: https://img.shields.io/npm/l/nuxt-edgedb.svg?style=flat&colorA=18181B&colorB=28CF8D |
| 295 | +[license-href]: https://npmjs.com/package/nuxt-edgedb |
| 296 | + |
| 297 | +[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js |
| 298 | +[nuxt-href]: https://nuxt.com |
0 commit comments