From 11301c781eadd9c847d8e61ccf6ff19c23497872 Mon Sep 17 00:00:00 2001 From: mrkaashee Date: Sun, 14 Dec 2025 20:54:43 +0500 Subject: [PATCH 1/4] docs: restructure docs, update routes, redirects, and navigation links - Reorganized docs folder structure - Updated navigation links in AppHeader and SideLinks - Added redirects and updated route rules in nuxt.config.ts --- docs/app/app.vue | 28 +- docs/app/assets/main.css | 6 +- docs/app/components/AppHeader.vue | 17 +- docs/app/layouts/docs.vue | 71 +- docs/content/docs/2.features/0.database.md | 836 ------------------ .../docs/2.features/1.blob/.navigation.yml | 4 + docs/content/docs/2.features/1.blob/setup.md | 350 ++++++++ .../2.features/{0.blob.md => 1.blob/usage.md} | 389 +------- .../docs/2.features/2.cache/.navigation.yml | 4 + .../{0.cache.md => 2.cache/setup.md} | 5 +- .../2.features/3.database/.navigation.yml | 5 + .../docs/2.features/3.database/1.setup.md | 334 +++++++ .../docs/2.features/3.database/2.schema.md | 172 ++++ .../docs/2.features/3.database/3.query.md | 104 +++ .../2.features/3.database/4.migrations.md | 145 +++ .../content/docs/2.features/3.database/cli.md | 88 ++ .../docs/2.features/4.kv/.navigation.yml | 4 + .../2.features/{0.kv.md => 4.kv/setup.md} | 149 +--- docs/content/docs/2.features/4.kv/usage.md | 146 +++ docs/nuxt.config.ts | 7 +- 20 files changed, 1487 insertions(+), 1377 deletions(-) delete mode 100644 docs/content/docs/2.features/0.database.md create mode 100644 docs/content/docs/2.features/1.blob/.navigation.yml create mode 100644 docs/content/docs/2.features/1.blob/setup.md rename docs/content/docs/2.features/{0.blob.md => 1.blob/usage.md} (57%) create mode 100644 docs/content/docs/2.features/2.cache/.navigation.yml rename docs/content/docs/2.features/{0.cache.md => 2.cache/setup.md} (97%) create mode 100644 docs/content/docs/2.features/3.database/.navigation.yml create mode 100644 docs/content/docs/2.features/3.database/1.setup.md create mode 100644 docs/content/docs/2.features/3.database/2.schema.md create mode 100644 docs/content/docs/2.features/3.database/3.query.md create mode 100644 docs/content/docs/2.features/3.database/4.migrations.md create mode 100644 docs/content/docs/2.features/3.database/cli.md create mode 100644 docs/content/docs/2.features/4.kv/.navigation.yml rename docs/content/docs/2.features/{0.kv.md => 4.kv/setup.md} (57%) create mode 100644 docs/content/docs/2.features/4.kv/usage.md diff --git a/docs/app/app.vue b/docs/app/app.vue index 355d62f5..0662f381 100644 --- a/docs/app/app.vue +++ b/docs/app/app.vue @@ -1,5 +1,6 @@ diff --git a/docs/app/layouts/docs.vue b/docs/app/layouts/docs.vue index 036a7271..afbd2839 100644 --- a/docs/app/layouts/docs.vue +++ b/docs/app/layouts/docs.vue @@ -3,18 +3,67 @@ import type { ContentNavigationItem } from '@nuxt/content' const navigation = inject>('navigation') -const asideNavigation = computed(() => navigation.value.find(item => item.path === '/docs')?.children || []) +const route = useRoute() + +const overviewMap: Record = { + '/docs/getting-started': [ + '/docs/getting-started', + '/docs/getting-started/installation', + '/docs/getting-started/deploy', + '/docs/getting-started/migration' + ], + '/docs/guides': [ + '/docs/guides', + '/docs/guides/pre-rendering', + '/docs/guides/realtime' + ] +} + +const asideNavigation = computed(() => { + const section = (navigation.value.find(l => l.path === '/docs')?.children ?? []) + .find(l => route.path.startsWith(l.path)) + + if (!section?.children) { + return [] + } + + const links = section.children + const overviewPaths = overviewMap[section.path] + + // Section has no overview grouping → return normal links + if (!overviewPaths) { + return links + } + + const overviewChildren = links + .filter(item => overviewPaths.includes(item.path)) + + const otherLinks = links + .filter(item => !overviewPaths.includes(item.path)) + + return [ + { + title: 'Overview', + path: section.path, + children: overviewChildren, + defaultOpen: true + }, + ...otherLinks + ] +}) diff --git a/docs/content/docs/2.features/0.database.md b/docs/content/docs/2.features/0.database.md deleted file mode 100644 index bc3bf62e..00000000 --- a/docs/content/docs/2.features/0.database.md +++ /dev/null @@ -1,836 +0,0 @@ ---- -title: SQL Database -navigation.title: Database -seo.title: Nuxt SQL Database -description: Access a SQL database with Drizzle ORM in Nuxt to store and retrieve relational data with full type-safety. ---- - -NuxtHub Database provides a type-safe SQL database powered by [Drizzle ORM](https://orm.drizzle.team), supporting PostgreSQL, MySQL, and SQLite with smart detection and automatic migrations at build time. - -## Getting started - -::steps{level="3"} -### Install dependencies - -Install Drizzle ORM, Drizzle Kit, and the appropriate driver(s) for the database you are using: - -::tabs{sync="database-dialect"} - :::tabs-item{label="PostgreSQL" icon="i-simple-icons-postgresql"} - :pm-install{name="drizzle-orm drizzle-kit postgres @electric-sql/pglite"} - ::callout - NuxtHub automatically detects your database connection using environment variables: - - Uses `PGlite` (embedded PostgreSQL) if no environment variables are set. - - Uses `postgres-js` driver if you set `DATABASE_URL`, `POSTGRES_URL`, or `POSTGRESQL_URL` environment variable. - :: - ::: - :::tabs-item{label="MySQL" icon="i-simple-icons-mysql"} - :pm-install{name="drizzle-orm drizzle-kit mysql2"} - ::callout - NuxtHub automatically detects your database connection using environment variables: - - Uses `mysql2` driver if you set `DATABASE_URL` or `MYSQL_URL` environment variable. - - Requires environment variable (no local fallback) - :: - ::: - :::tabs-item{label="SQLite" icon="i-simple-icons-sqlite"} - :pm-install{name="drizzle-orm drizzle-kit @libsql/client"} - ::callout - NuxtHub automatically detects your database connection using environment variables: - - Uses `libsql` driver for [Turso](https://turso.tech) if you set `TURSO_DATABASE_URL` and `TURSO_AUTH_TOKEN` environment variables. - - Uses `libsql` locally with file at `.data/db/sqlite.db` if no environment variables are set. - :: - ::: -:: - -### Set SQL dialect - -Enable the database in your `nuxt.config.ts` by setting the `db` property to your desired SQL dialect: - -::tabs{sync="database-dialect"} - :::tabs-item{label="PostgreSQL" icon="i-simple-icons-postgresql"} - ```ts [nuxt.config.ts] - export default defineNuxtConfig({ - hub: { - db: 'postgresql' - } - }) - ``` - ::: - :::tabs-item{label="MySQL" icon="i-simple-icons-mysql"} - ```ts [nuxt.config.ts] - export default defineNuxtConfig({ - hub: { - db: 'mysql' - } - }) - ``` - ::: - :::tabs-item{label="SQLite" icon="i-simple-icons-sqlite"} - ```ts [nuxt.config.ts] - export default defineNuxtConfig({ - hub: { - db: 'sqlite' - } - }) - ``` - ::: -:: - -### Database schema - -Create your database schema with full TypeScript support using Drizzle ORM: - -::tabs{sync="database-dialect"} - :::tabs-item{label="PostgreSQL" icon="i-simple-icons-postgresql"} - ```ts [server/db/schema.ts] - import { pgTable, text, serial, timestamp } from 'drizzle-orm/pg-core' - - export const users = pgTable('users', { - id: serial().primaryKey(), - name: text().notNull(), - email: text().notNull().unique(), - password: text().notNull(), - avatar: text().notNull(), - createdAt: timestamp().notNull().defaultNow(), - }) - ``` - ::: - - :::tabs-item{label="MySQL" icon="i-simple-icons-mysql"} - ```ts [server/db/schema.ts] - import { mysqlTable, text, serial, timestamp } from 'drizzle-orm/mysql-core' - - export const users = mysqlTable('users', { - id: serial().primaryKey(), - name: text().notNull(), - email: text().notNull().unique(), - password: text().notNull(), - avatar: text().notNull(), - createdAt: timestamp().notNull().defaultNow(), - }) - ``` - ::: - - :::tabs-item{label="SQLite" icon="i-simple-icons-sqlite"} - ```ts [server/db/schema.ts] - import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core' - - export const users = sqliteTable('users', { - id: integer().primaryKey({ autoIncrement: true }), - name: text().notNull(), - email: text().notNull().unique(), - password: text().notNull(), - avatar: text().notNull(), - createdAt: integer({ mode: 'timestamp' }).notNull(), - }) - ``` - ::: -:: - -::callout{to="#schema-definition"} -Learn more about defining the **database schema files**. -:: - -### Generate migrations - -Generate the database migrations from your schema: - -```bash [Terminal] -npx nuxt db generate -``` - -This creates SQL migration files in `server/db/migrations/{dialect}/` which are automatically applied during deployment and development. - -::tip{icon="i-lucide-rocket"} -That's it! You can now start your development server and query your database using the `db` instance from `hub:db`. -:: - -::important -Make sure to run `npx nuxt db generate` to generate the database migrations each time you change your database schema and restart the development server. -:: - -:: - -## Schema definition - -NuxtHub supports defining the database schema in multiple files and directories, allowing you to organize your schema files in a way that makes sense for your project, but also open the possibility to Nuxt modules to extend the database schema. - -### Schema files - -Database schema can be defined in a single file or in multiple files, these files are scanned and automatically imported following this glob pattern: -- `server/db/schema.ts` -- `server/db/schema.{dialect}.ts` -- `server/db/schema/*.ts` -- `server/db/schema/*.{dialect}.ts` - -The merged schema is exported in `hub:db:schema` or via the `schema` object in the `hub:db` namespace: - -```ts -import * as schema from 'hub:db:schema' -// or -import { schema } from 'hub:db' -``` - -::callout{icon="i-lucide-lightbulb"} -You can locate the generated schema file at `.nuxt/hub/db/schema.mjs`. -:: - -::note{to="https://orm.drizzle.team/docs/sql-schema-declaration" external} -Learn more about [Drizzle ORM schema](https://orm.drizzle.team/docs/sql-schema-declaration) on the Drizzle documentation. -:: - -### Nuxt layers - -Database schema is scanned and automatically imported for each [Nuxt layer](https://nuxt.com/docs/getting-started/layers). - -This meands that you can also define schema in the `layers` directory: - -```bash [Directory structure] -layers/cms/server/db/schema.ts -layers/products/server/db/schema/products.ts -``` - -### Nuxt modules - -If you are a Nuxt module developer, you can also extend the database schema by using the `hub:db:schema:extend` hook: - -```ts [modules/cms/index.ts] -import { defineNuxtModule, createResolver } from '@nuxt/kit' - -export default defineNuxtModule({ - setup(options, nuxt) { - const { resolvePath } = createResolver(import.meta.url) - nuxt.hook('hub:db:schema:extend', async ({ dialect, paths }) => { - // Add your module drizzle schema files for the given dialect - // e.g. ./schema/pages.postgresql.ts if hub.db is 'postgresql' - paths.push(await resolvePath(`./schema/pages.${dialect}`)) - }) - } -}) -``` - -### Sharing types with Vue - -Types inferred from your database schema are only available on the server-side by default. To share these types with your Vue application, you can use the [`shared/`](https://nuxt.com/docs/guide/directory-structure/shared) directory which is auto-imported across both server and client. - -Create a types file in the `shared/types/` directory: - -```ts [shared/types/db.ts] -import { users, posts } from 'hub:db:schema' - -// Select types (for reading data) -export type User = typeof users.$inferSelect -export type Post = typeof posts.$inferSelect - -// Insert types (for creating data) -export type NewUser = typeof users.$inferInsert -export type NewPost = typeof posts.$inferInsert -``` - -These types are now auto-imported and available in your Vue components, composables, and API routes: - -::code-group -```vue [pages/users.vue] - -``` - -```ts [server/api/users.post.ts] -import { db, schema } from 'hub:db' - -export default eventHandler(async (event) => { - const body = await readBody(event) - - return await db.insert(schema.users).values(body).returning() -}) -``` -:: - -::tip -You can also create more specific types by using `Pick` and `Omit` TypeScript's built-in utility types. - -```ts [shared/types/db.ts] -// User without password for public API responses -export type PublicUser = Omit - -// Only the fields needed for user creation form -export type UserForm = Pick -``` -:: - -## Query database - -Now that you have your database schema and migrations set up, you can start querying your database. - -The `hub:db` module provides access to the database through a [Drizzle ORM](https://orm.drizzle.team) instance. - -```ts -import { db } from 'hub:db' -``` - -::tip -`db` is auto-imported on server-side, you can directly use it without importing it from `hub:db`. -:: - -### SQL Select - -```ts [server/api/users.get.ts] -import { db, schema } from 'hub:db' - -export default eventHandler(async (event) => { - return await db.query.users.findMany() - // or - return await db.select().from(schema.users) -}) -``` - -::callout{to="https://orm.drizzle.team/docs/select" external} -Learn more about [Drizzle ORM select](https://orm.drizzle.team/docs/select) on the Drizzle documentation. -:: - -### SQL Insert - -```ts [server/api/users.post.ts] -import { db, schema } from 'hub:db' - -export default eventHandler(async (event) => { - const { name, email } = await readBody(event) - - return await db - .insert(schema.users) - .values({ - name, - email, - createdAt: new Date() - }) - .returning() -}) -``` - -::callout{to="https://orm.drizzle.team/docs/insert" external} -Learn more about [Drizzle ORM insert](https://orm.drizzle.team/docs/insert) on the Drizzle documentation. -:: - -### SQL Update - -```ts [server/api/users/[id\\].patch.ts] -import { db, schema } from 'hub:db' - -export default eventHandler(async (event) => { - const { id } = getRouterParams(event) - const { name } = await readBody(event) - - return await db - .update(schema.users) - .set({ name }) - .where(eq(tables.users.id, Number(id))) - .returning() -}) -``` - -::callout{to="https://orm.drizzle.team/docs/update" external} -Learn more about [Drizzle ORM update](https://orm.drizzle.team/docs/update) on the Drizzle documentation. -:: - -### SQL Delete - -```ts [server/api/users/[id\\].delete.ts] -import { db, schema } from 'hub:db' - -export default eventHandler(async (event) => { - const { id } = getRouterParams(event) - - const deletedUser = await db - .delete(schema.users) - .where(eq(schema.users.id, Number(id))) - .returning() - - if (!deletedUser) { - throw createError({ - statusCode: 404, - message: 'User not found' - }) - } - - return { deleted: true } -}) -``` - -::callout{to="https://orm.drizzle.team/docs/delete" external} -Learn more about [Drizzle ORM delete](https://orm.drizzle.team/docs/delete) on the Drizzle documentation. -:: - -## Database migrations - -Database migrations provide version control for your database schema. NuxtHub supports SQL migration files (`.sql`) and automatically applies them during development and deployment. Making them fully compatible with Drizzle Kit generated migrations. - -::note -Create dialect-specific migrations with `..sql` suffix (e.g., `0001_create-todos.postgresql.sql`). -:: - -### Migrations Directories - -NuxtHub scans `server/db/migrations` for migrations in each [Nuxt layer](https://nuxt.com/docs/getting-started/layers). - -To scan additional directories, specify them in your config: - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - hub: { - db: { - dialect: 'postgresql', - migrationsDirs: [ - 'server/db/custom-migrations/' - ] - } - } -}) -``` - -For more control (e.g., in Nuxt modules), use the `hub:db:migrations:dirs` hook: - -::code-group -```ts [modules/auth/index.ts] -import { createResolver, defineNuxtModule } from '@nuxt/kit' - -export default defineNuxtModule({ - meta: { - name: 'my-auth-module' - }, - setup(options, nuxt) { - const { resolve } = createResolver(import.meta.url) - - nuxt.hook('hub:db:migrations:dirs', (dirs) => { - dirs.push(resolve('./db-migrations')) - }) - } -}) -``` -```sql [modules/auth/db-migrations/0001_create-users.sql] -CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - email TEXT NOT NULL -); -``` -:: - -::tip -All migration files are copied to `.data/db/migrations` when you run Nuxt, giving you a consolidated view. -:: - -### Automatic migrations - -Migrations are automatically applied when you: -- Start the development server (`npx nuxi dev`) -- Build the application (`npx nuxi build`) - -Applied migrations are tracked in the `_hub_migrations` database table. - -### Generating migrations - -Once you have updates your database schema, you can generate new migrations using the following command: - -```bash [Terminal] -npx nuxt db generate -``` - -This will generate new migrations files in `server/db/migrations/{dialect}/` which are automatically applied during development and deployment. - -### Applying migrations - -Once you have generated new migrations, you can apply them using the following command: - -```bash [Terminal] -npx nuxt db migrate -``` - -This will apply the new migrations to your database. - -::tip -When running the development server, NuxtHub will automatically apply the migrations for you. -:: - -### Post-migration queries - -::important -Advanced use case: These queries run after migrations but aren't tracked in `_hub_migrations`. Ensure they're idempotent. -:: - -Use the `hub:db:queries:paths` hook to run additional queries after migrations: - -::code-group -```ts [modules/admin/index.ts] -import { createResolver, defineNuxtModule } from '@nuxt/kit' - -export default defineNuxtModule({ - meta: { - name: 'my-auth-module' - }, - setup(options, nuxt) { - const { resolve } = createResolver(import.meta.url) - - nuxt.hook('hub:db:queries:paths', (paths, dialect) => { - paths.push(resolve(`./db-queries/seed-admin.${dialect}.sql`)) - }) - } -}) -``` -```sql [modules/admin/db-queries/seed-admin.sql] -INSERT OR IGNORE INTO admin_users (id, email, password) VALUES (1, 'admin@nuxt.com', 'admin'); -``` -:: - -::tip -All migrations queries are copied to `.data/db/queries` when you run Nuxt, giving you a consolidated view. -:: - -### Foreign-key constraints - -For Cloudflare D1 with Drizzle ORM migrations, replace: - -```diff [Example] --PRAGMA foreign_keys = OFF; -+PRAGMA defer_foreign_keys = on; - -ALTER TABLE ... - --PRAGMA foreign_keys = ON; -+PRAGMA defer_foreign_keys = off; -``` - -::callout{to="https://developers.cloudflare.com/d1/sql-api/foreign-keys/#defer-foreign-key-constraints" external} -Learn more about [defer foreign key constraints](https://developers.cloudflare.com/d1/sql-api/foreign-keys/#defer-foreign-key-constraints) in Cloudflare D1. -:: - -## Database seed - -You can populate your database with initial data using [Nitro Tasks](https://nitro.build/guide/tasks): - -::steps{level="3"} - -### Enable Nitro tasks - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - nitro: { - experimental: { - tasks: true - } - } -}) -``` - -### Create a seed task - -```ts [server/tasks/seed.ts] -import { db, schema } from 'hub:db' - -export default defineTask({ - meta: { - name: 'db:seed', - description: 'Seed database with initial data' - }, - async run() { - console.log('Seeding database...') - - const users = [ - { - name: 'John Doe', - email: 'john@example.com', - password: 'hashed_password', - avatar: 'https://i.pravatar.cc/150?img=1', - createdAt: new Date() - }, - { - name: 'Jane Doe', - email: 'jane@example.com', - password: 'hashed_password', - avatar: 'https://i.pravatar.cc/150?img=2', - createdAt: new Date() - } - ] - - await db.insert(schema.users).values(users) - - return { result: 'Database seeded successfully' } - } -}) -``` - -### Execute the task - -Open the `Tasks` tab in Nuxt DevTools and click on the `db:seed` task. - -:: - -## CLI - -NuxtHub provides a CLI for managing your database migrations and running SQL queries accessible from the `npx nuxt db` command. - -### `nuxt db generate` - -Generate database migrations from the schema. - -```bash [Terminal] -USAGE db generate [OPTIONS] - -OPTIONS - --cwd The directory to run the command in. - -v, --verbose Show verbose output. -``` - -### `nuxt db migrate` - -Apply database migrations to the database. - -```bash [Terminal] -USAGE db migrate [OPTIONS] - -OPTIONS - - --cwd The directory to run the command in. - --dotenv Point to another .env file to load. - -v, --verbose Show verbose output. -``` - -### `nuxt db mark-as-migrated` - -Mark local database migration(s) as applied to the database. - -```bash [Terminal] -USAGE db mark-as-migrated [OPTIONS] [NAME] - -ARGUMENTS - NAME The name of the migration to mark as applied. - -OPTIONS - --cwd The directory to run the command in. - --dotenv Point to another .env file to load. - -v, --verbose Show verbose output. -``` - -### `nuxt db drop` - -Drop a table from the database. - -```bash [Terminal] -USAGE db drop [OPTIONS] - -ARGUMENTS - TABLE The name of the table to drop. - -OPTIONS - --cwd The directory to run the command in. - --dotenv Point to another .env file to load. - -v, --verbose Show verbose output. -``` - -### `nuxt db sql` - -Execute a SQL query against the database. - -```bash [Terminal] -USAGE db sql [OPTIONS] [QUERY] - -ARGUMENTS - QUERY The SQL query to execute. If not provided, reads from stdin. - -OPTIONS - --cwd The directory to run the command in. - --dotenv Point to another .env file to load, relative to the root directory. - -v, --verbose Show verbose output. -``` - -Example usage: - -```bash [Terminal] -npx nuxt db sql "SELECT * FROM users" -# or -npx nuxt db sql < dump.sql -``` - -## AI Agents - -If you work with an IDE that supports AI agents, you can add the following text in your `Agents.md` or `.cursor/rules` file: - -```md -# Agent Instructions - -/** ... your agent instructions ... */ - -## Database - -- **Database Dialect**: The database dialect is set in the `nuxt.config.ts` file, within the `hub.db` option or `hub.db.dialect` property. -- **Drizzle Config**: Don't generate the `drizzle.config.ts` file manually, it is generated automatically by NuxtHub. -- **Generate Migrations**: Use `npx nuxt db generate` to automatically generate database migrations from schema changes -- **Never Write Manual Migrations**: Do not manually create SQL migration files in the `server/db/migrations/` directory -- **Workflow**: - 1. Create or modify the database schema in `server/db/schema.ts` or any other schema file in the `server/db/schema/` directory - 2. Run `npx nuxt db generate` to generate the migration - 3. Run `npx nuxt db migrate` to apply the migration to the database, or run `npx nuxt dev` to apply the migration during development -- **Access the database**: Use the `db` instance from `hub:db` to query the database, it is a Drizzle ORM instance. -``` - -## Local development - -During local development, view and edit your database from [Nuxt DevTools](https://devtools.nuxt.com) using the [Drizzle Studio](https://orm.drizzle.team/drizzle-studio/overview): - -:img{src="/images/landing/nuxt-devtools-database.png" alt="Nuxt DevTools Database" width="915" height="515"} - -::warning -At the moment, Drizzle Studio does not support SQLite. -:: - - -## Build-time Hooks - -::field-group - ::field{name="'hub:db:migrations:dirs'" type="(dirs: string[]) => void | Promise"} - Add additional directories to scan for database migration files (.sql). - - ```ts [nuxt.config.ts] - export default defineNuxtConfig({ - hooks: { - 'hub:db:migrations:dirs': (dirs) => { - dirs.push('my-module/db/migrations') - } - } - }) - ``` - :: - ::field{name="'hub:db:queries:paths'" type="(queries: string[], dialect: string) => void | Promise"} - Add queries that are not tracked in the `_hub_migrations` table which are applied after the database migrations complete. - - ```ts [nuxt.config.ts] - export default defineNuxtConfig({ - hooks: { - 'hub:db:queries:paths': (queries, dialect) => { - queries.push('my-module/db/queries') - } - } - }) - ``` - :: - ::field{name="'hub:db:schema:extend'" type="({ paths: string[], dialect: string }) => void | Promise"} - Extend the database schema with additional files. - - ```ts [modules/my-module/index.ts] - import { createResolver, defineNuxtModule } from '@nuxt/kit' - - export default defineNuxtModule({ - setup(options, nuxt) { - const { resolve } = createResolver(import.meta.url) - - nuxt.hook('hub:db:schema:extend', ({ paths, dialect }) => { - paths.push(resolve(`./db/schema/pages.${dialect}.ts`)) - }) - } - }) - ``` - :: -:: - -::note{to="https://nuxt.com/docs/4.x/guide/going-further/hooks#nuxt-hooks-build-time"} -Learn more about Nuxt server hooks on the **Nuxt documentation**. -:: - -## Advanced configuration - -For advanced use cases, you can explicitly configure the database connection: - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - hub: { - db: { - dialect: 'postgresql', - driver: 'postgres-js', // Optional: explicitly choose driver - connection: { - connectionString: process.env.DATABASE_URL - } - } - } -}) -``` - -### Cloudflare D1 over HTTP - -Use the `d1-http` driver to access a Cloudflare D1 database over HTTP. This is useful when you want to query your D1 database when hosting on other platforms. - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - hub: { - db: { - dialect: 'sqlite', - driver: 'd1-http' - } - } -}) -``` - -This driver requires the following environment variables: - -| Variable | Description | -| --- | --- | -| `NUXT_HUB_CLOUDFLARE_ACCOUNT_ID` | Your Cloudflare account ID | -| `NUXT_HUB_CLOUDFLARE_API_TOKEN` | A Cloudflare API token with D1 permissions | -| `NUXT_HUB_CLOUDFLARE_DATABASE_ID` | The ID of your D1 database | - -::callout{icon="i-lucide-info"} -You can find your Cloudflare account ID and create API tokens in the [Cloudflare dashboard](https://dash.cloudflare.com). The API token needs `D1:Edit` permissions. -:: - -## Migration guide - -::important -**Breaking changes in NuxtHub v1:** If you're upgrading from a previous version that used `hubDatabase()`, follow this migration guide. -:: - -### Configuration changes - -The `database` option has been renamed to `db` and now accepts a SQL dialect instead of a boolean. - -```diff [nuxt.config.ts] -export default defineNuxtConfig({ - hub: { -- database: true -+ db: 'sqlite' - } -}) -``` - -Valid dialects are `sqlite`, `postgresql` and `mysql`. - -### Directory changes - -The database directory has been renamed from `server/database/` to `server/db/`: - -```diff -- server/database/schema.ts -+ server/db/schema.ts - -- server/database/migrations/ -+ server/db/migrations/ -``` - -Make sure to move your schema and migration files to the new location. - -### API changes - -The old `hubDatabase()` function has been removed. You must now use Drizzle ORM. - -**Before:** - -```ts -const db = hubDatabase() -const result = await db.prepare('SELECT * FROM users').all() -``` - -**After:** -```ts -const result = await db.select().from(tables.users) -``` - -### Migration files - -Your existing SQL migration files continue to work! Just move them to `server/db/migrations/`. diff --git a/docs/content/docs/2.features/1.blob/.navigation.yml b/docs/content/docs/2.features/1.blob/.navigation.yml new file mode 100644 index 00000000..215864db --- /dev/null +++ b/docs/content/docs/2.features/1.blob/.navigation.yml @@ -0,0 +1,4 @@ +title: Blob Storage +icon: i-lucide-file +navigation.title: Blob +description: Upload, store and serve images, videos, music, documents and other unstructured data in your Nuxt application. diff --git a/docs/content/docs/2.features/1.blob/setup.md b/docs/content/docs/2.features/1.blob/setup.md new file mode 100644 index 00000000..25d20583 --- /dev/null +++ b/docs/content/docs/2.features/1.blob/setup.md @@ -0,0 +1,350 @@ +--- +title: Setup +description: Configure Blob Storage in your Nuxt application, including installation, environment variables, and initial setup for uploading and serving files. +--- + +## Getting Started + +Enable blob storage in your project by setting `blob: true` in the NuxtHub config. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + hub: { + blob: true + } +}) +``` + +### Automatic Configuration + +NuxtHub automatically configures the blob storage driver based on your hosting provider when `blob: true` is set in the NuxtHub config. + +::tabs{sync="provider"} + :::tabs-item{label="Vercel Blob" icon="i-simple-icons-vercel"} + + When deploying to Vercel, it automatically configures [Vercel Blob Storage](https://vercel.com/docs/storage/vercel-blob). + + 1. Install the `@vercel/blob` package + + :pm-install{name="@vercel/blob"} + + 2. Assign a Vercel Blob Store to your project from the [Vercel dashboard](https://vercel.com/) -> Project -> Storage + + ::important + Files stored in Vercel Blob are always public. Manually configure a different storage driver if storing sensitive files. + :: + + 3. When running locally, you can set the `BLOB_READ_WRITE_TOKEN` environment variable to enable the Vercel Blob driver: + + ```bash [.env] + BLOB_READ_WRITE_TOKEN=your-token + ``` + ::: + + :::tabs-item{label="Cloudflare R2" icon="i-simple-icons-cloudflare"} + + When deploying to Cloudflare, it automatically configures [Cloudflare R2](https://developers.cloudflare.com/r2/). + + Add a `BLOB` binding to a [Cloudflare R2](https://developers.cloudflare.com/r2/) bucket in your `wrangler.jsonc` config. + + ```json [wrangler.jsonc] + { + "$schema": "node_modules/wrangler/config-schema.json", + // ... + "r2_buckets": [ + { + "binding": "BLOB", + "bucket_name": "" + } + ] + } + ``` + + Learn more about adding bindings on [Cloudflare's documentation](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/). + + ::tip + To use Cloudflare R2 without hosting on Cloudflare Workers, use the [Cloudflare R2 via S3 API](https://developers.cloudflare.com/r2/api/s3/api/). + :: + + ::: + + :::tabs-item{label="S3" icon="i-simple-icons-amazons3"} + + To configure [Amazon S3](https://aws.amazon.com/s3/) as a blob storage driver. + + 1. Install the `aws4fetch` package + + :pm-install{name="aws4fetch"} + + 2. Set the following environment variables: + + ```bash [.env] + S3_ACCESS_KEY_ID=your-access-key-id + S3_SECRET_ACCESS_KEY=your-secret-access-key + S3_BUCKET=your-bucket-name + S3_REGION=your-region + S3_ENDPOINT=your-endpoint # (optional) + ``` + ::: + + :::tabs-item{label="Filesystem" icon="i-simple-icons-nodedotjs"} + To customize the directory, you can set the `dir` option. + + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + hub: { + blob: { + driver: 'fs', + dir: '.data/my-blob-directory' // Defaults to `.data/blob` + } + } + }) + ``` + + ::important + The local filesystem driver is not suitable for production environments. + :: + ::: +:: + +::callout +By default, the local filesystem driver is used if no automatic configuration is found. +:: + +### Custom Driver + +You can set a custom driver by providing a configuration object to `blob`. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + hub: { + blob: { + driver: 'fs', + dir: '.data/blob' + } + }, + // or overwrite only in production + $production: { + hub: { + blob: { + driver: 'cloudflare-r2', + binding: 'BLOB' + } + } + } +}) +``` + +::callout{to="https://unstorage.unjs.io/drivers"} +You can find the driver list on [unstorage documentation](https://unstorage.unjs.io/drivers) with their configuration. +:: + +## `ensureBlob()` + +`ensureBlob()` is a handy util to validate a `Blob` by checking its size and type: + +```ts +import { ensureBlob } from 'hub:blob' + +// Will throw an error if the file is not an image or is larger than 1MB +ensureBlob(file, { maxSize: '1MB', types: ['image']}) +``` + +#### Params + +::field-group + ::field{name="file" type="Blob" required} + The file to validate. + :: + ::field{name="options" type="Object" required} + Note that at least `maxSize` or `types` should be provided. + ::collapsible + ::field{name="maxSize" type="BlobSize"} + The maximum size of the file, should be: :br + (`1` | `2` | `4` | `8` | `16` | `32` | `64` | `128` | `256` | `512` | `1024`) + (`B` | `KB` | `MB` | `GB`) :br + e.g. `'512KB'`, `'1MB'`, `'2GB'`, etc. + :: + ::field{name="types" type="BlobType[]"} + Allowed types of the file, e.g. `['image/jpeg']`. + :: + :: + :: +:: + +#### Return + +Returns nothing. + +Throws an error if `file` doesn't meet the requirements. + +## Vue Composables + +::note +The following composables are meant to be used in the Vue side of your application (not the `server/` directory). +:: + +### `useUpload()` + +`useUpload` is to handle file uploads in your Nuxt application. + +```vue + + + +``` + +#### Params + +::field-group + ::field{name="apiBase" type="string" required} + The base URL of the upload API. + :: + ::field{name="options" type="Object" required} + Optionally, you can pass Fetch options to the request. Read more about Fetch API [here](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options). + ::collapsible + ::field{name="formKey" type="string"} + The key to add the file/files to the request form. Defaults to `'files'`. + :: + ::field{name="multiple" type="boolean"} + Whether to allow multiple files to be uploaded. Defaults to `true`. + :: + :: + :: +:: + +#### Return + +Return a `MultipartUpload` function that can be used to upload a file in parts. + +```ts +const { completed, progress, abort } = upload(file) +const data = await completed +``` + +### `useMultipartUpload()` + +Application composable that creates a multipart upload helper. + +::important +When using the Vercel Blob driver, this utility will automatically use the [Vercel Blob Client SDK](https://vercel.com/docs/vercel-blob/client-upload) to upload the file. +:: + +```ts [utils/multipart-upload.ts] +export const mpu = useMultipartUpload('/api/files/multipart') +``` + +#### Params + +::field-group + ::field{name="baseURL" type="string"} + The base URL of the multipart upload API handled by [`handleMultipartUpload()`](/docs/features/blob/usage#handlemultipartupload). + :: + ::field{name="options"} + The options for the multipart upload helper. + ::collapsible + ::field{name="partSize" type="number"} + The size of each part of the file to be uploaded. Defaults to `10MB`. + :: + ::field{name="concurrent" type="number"} + The maximum number of concurrent uploads. Defaults to `1`. + :: + ::field{name="maxRetry" type="number"} + The maximum number of retry attempts for the whole upload. Defaults to `3`. + :: + ::field{name="prefix" type="string"} + The prefix to use for the blob pathname. + :: + ::field{name="fetchOptions" type="Omit"} + Override the ofetch options. + The `query` and `headers` will be merged with the options provided by the uploader. + :: + :: + :: +:: + +#### Return + +Return a `MultipartUpload` function that can be used to upload a file in parts. + +```ts +const { completed, progress, abort } = mpu(file) +const data = await completed +``` + +## Types + +### `BlobObject` + +```ts +interface BlobObject { + pathname: string + contentType: string | undefined + size: number + httpEtag: string + uploadedAt: Date + httpMetadata: Record + customMetadata: Record + url: string | undefined +} +``` + +### `BlobMultipartUpload` + +```ts +export interface BlobMultipartUpload { + pathname: string + uploadId: string + uploadPart( + partNumber: number, + value: string | ReadableStream | ArrayBuffer | ArrayBufferView | Blob + ): Promise + abort(): Promise + complete(uploadedParts: BlobUploadedPart[]): Promise +} +``` + +### `BlobUploadedPart` + +```ts +export interface BlobUploadedPart { + partNumber: number; + etag: string; +} +``` + +### `MultipartUploader` + +```ts +export type MultipartUploader = (file: File) => { + completed: Promise | undefined> + progress: Readonly> + abort: () => Promise +} +``` + +### `BlobListResult` + +```ts +interface BlobListResult { + blobs: BlobObject[] + hasMore: boolean + cursor?: string + folders?: string[] +} +``` diff --git a/docs/content/docs/2.features/0.blob.md b/docs/content/docs/2.features/1.blob/usage.md similarity index 57% rename from docs/content/docs/2.features/0.blob.md rename to docs/content/docs/2.features/1.blob/usage.md index 537a61b3..db6eb958 100644 --- a/docs/content/docs/2.features/0.blob.md +++ b/docs/content/docs/2.features/1.blob/usage.md @@ -1,147 +1,8 @@ --- -title: Blob Storage -navigation.title: Blob -description: Upload, store and serve images, videos, music, documents and other unstructured data in your Nuxt application. +title: Usage +description: Learn how to upload, read, delete, and manage files using Blob Storage in your Nuxt application, with common patterns and examples. --- -## Getting Started - -Enable blob storage in your project by setting `blob: true` in the NuxtHub config. - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - hub: { - blob: true - } -}) -``` - -### Automatic Configuration - -NuxtHub automatically configures the blob storage driver based on your hosting provider when `blob: true` is set in the NuxtHub config. - -::tabs{sync="provider"} - :::tabs-item{label="Vercel Blob" icon="i-simple-icons-vercel"} - - When deploying to Vercel, it automatically configures [Vercel Blob Storage](https://vercel.com/docs/storage/vercel-blob). - - 1. Install the `@vercel/blob` package - - :pm-install{name="@vercel/blob"} - - 2. Assign a Vercel Blob Store to your project from the [Vercel dashboard](https://vercel.com/) -> Project -> Storage - - ::important - Files stored in Vercel Blob are always public. Manually configure a different storage driver if storing sensitive files. - :: - - 3. When running locally, you can set the `BLOB_READ_WRITE_TOKEN` environment variable to enable the Vercel Blob driver: - - ```bash [.env] - BLOB_READ_WRITE_TOKEN=your-token - ``` - ::: - - :::tabs-item{label="Cloudflare R2" icon="i-simple-icons-cloudflare"} - - When deploying to Cloudflare, it automatically configures [Cloudflare R2](https://developers.cloudflare.com/r2/). - - Add a `BLOB` binding to a [Cloudflare R2](https://developers.cloudflare.com/r2/) bucket in your `wrangler.jsonc` config. - - ```json [wrangler.jsonc] - { - "$schema": "node_modules/wrangler/config-schema.json", - // ... - "r2_buckets": [ - { - "binding": "BLOB", - "bucket_name": "" - } - ] - } - ``` - - Learn more about adding bindings on [Cloudflare's documentation](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/). - - ::tip - To use Cloudflare R2 without hosting on Cloudflare Workers, use the [Cloudflare R2 via S3 API](https://developers.cloudflare.com/r2/api/s3/api/). - :: - - ::: - - :::tabs-item{label="S3" icon="i-simple-icons-amazons3"} - - To configure [Amazon S3](https://aws.amazon.com/s3/) as a blob storage driver. - - 1. Install the `aws4fetch` package - - :pm-install{name="aws4fetch"} - - 2. Set the following environment variables: - - ```bash [.env] - S3_ACCESS_KEY_ID=your-access-key-id - S3_SECRET_ACCESS_KEY=your-secret-access-key - S3_BUCKET=your-bucket-name - S3_REGION=your-region - S3_ENDPOINT=your-endpoint # (optional) - ``` - ::: - - :::tabs-item{label="Filesystem" icon="i-simple-icons-nodedotjs"} - To customize the directory, you can set the `dir` option. - - ```ts [nuxt.config.ts] - export default defineNuxtConfig({ - hub: { - blob: { - driver: 'fs', - dir: '.data/my-blob-directory' // Defaults to `.data/blob` - } - } - }) - ``` - - ::important - The local filesystem driver is not suitable for production environments. - :: - ::: -:: - -::callout -By default, the local filesystem driver is used if no automatic configuration is found. -:: - -### Custom Driver - -You can set a custom driver by providing a configuration object to `blob`. - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - hub: { - blob: { - driver: 'fs', - dir: '.data/blob' - } - }, - // or overwrite only in production - $production: { - hub: { - blob: { - driver: 'cloudflare-r2', - binding: 'BLOB' - } - } - } -}) -``` - -::callout{to="https://unstorage.unjs.io/drivers"} -You can find the driver list on [unstorage documentation](https://unstorage.unjs.io/drivers) with their configuration. -:: - -## Usage - The `hub:blob` module provides access to the Blob storage through an [unstorage](https://unstorage.unjs.io) instance. ```ts @@ -152,7 +13,7 @@ import { blob } from 'hub:blob' `blob` is auto-imported on server-side, you can directly use it without importing it from `hub:blob`. :: -### `list()` +## `list()` Returns a paginated list of blobs (metadata only). @@ -194,7 +55,7 @@ When using the local filesystem driver, the `limit` option is ignored and all bl #### Return -Returns [`BlobListResult`](#bloblistresult). +Returns [`BlobListResult`](/docs/features/blob/setup#bloblistresult). #### Return all blobs @@ -213,7 +74,7 @@ do { } while (cursor) ``` -### `serve()` +## `serve()` Returns a blob's data and sets `Content-Type`, `Content-Length` and `ETag` headers. @@ -266,7 +127,7 @@ export default eventHandler(async (event) => { Returns the blob's raw data and sets `Content-Type` and `Content-Length` headers. -### `head()` +## `head()` Returns a blob's metadata. @@ -284,10 +145,10 @@ const metadata = await blob.head(pathname) #### Return -Returns a [`BlobObject`](#blobobject). +Returns a [`BlobObject`](/docs/features/blob/setup#blobobject). -### `get()` +## `get()` Returns a blob body. @@ -307,7 +168,7 @@ const file = await blob.get(pathname) Returns a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or `null` if not found. -### `put()` +## `put()` Uploads a blob to the storage. @@ -396,9 +257,9 @@ async function uploadImage (e: Event) { #### Return -Returns a [`BlobObject`](#blobobject). +Returns a [`BlobObject`](/docs/features/blob/setup#blobobject). -### `del()` +## `del()` Delete a blob with its pathname. @@ -436,12 +297,12 @@ You can also use the `delete()` method as alias of `del()`. Returns nothing. -### `handleUpload()` +## `handleUpload()` This is an "all in one" function to validate a `Blob` by checking its size and type and upload it to the storage. ::note -This server util is made to be used with the [`useUpload()`](#useupload) Vue composable. +This server util is made to be used with the [`useUpload()`](/docs/features/blob/setup#useupload) Vue composable. :: It can be used to handle file uploads in API routes. @@ -489,7 +350,7 @@ async function onFileSelect(event: Event) { When `true`, the `formKey` field will be an array of `Blob` objects. :: ::field{name="ensure" type="BlobEnsureOptions"} - See [`ensureBlob()`](#ensureblob) options for more details. + See [`ensureBlob()`](/docs/features/blob/setup#ensureblob) options for more details. :: ::field{name="put" type="BlobPutOptions"} See [`put()`](#put) options for more details. @@ -498,11 +359,11 @@ async function onFileSelect(event: Event) { #### Return -Returns a [`BlobObject`](#blobobject) or an array of [`BlobObject`](#blobobject) if `multiple` is `true`. +Returns a [`BlobObject`](/docs/features/blob/setup#blobobject) or an array of [`BlobObject`](/docs/features/blob/setup#blobobject) if `multiple` is `true`. Throws an error if `file` doesn't meet the requirements. -### `handleMultipartUpload()` +## `handleMultipartUpload()` Handle the request to support multipart upload. @@ -530,8 +391,8 @@ async function uploadFile(file: File) { ``` -::note{to="#usemultipartupload"} -See [`useMultipartUpload()`](#usemultipartupload) on usage details. +::note{to="/docs/features/blob/setup#usemultipartupload"} +See [`useMultipartUpload()`](/docs/features/blob/setup#usemultipartupload) on usage details. :: @@ -553,7 +414,7 @@ Multipart uploads are only supported on Vercel Blob, Cloudflare R2, S3 and files :: :: -### `createMultipartUpload()` +## `createMultipartUpload()` ::note We recommend using [`handleMultipartUpload()`](#handlemultipartupload) to handle the multipart upload requests. @@ -605,7 +466,7 @@ export default eventHandler(async (event) => { Returns a `BlobMultipartUpload` -### `resumeMultipartUpload()` +## `resumeMultipartUpload()` ::note We recommend using [`handleMultipartUpload()`](#handlemultipartupload) to handle the multipart upload requests. @@ -722,213 +583,3 @@ Returns a `BlobMultipartUpload` The event to handle. :: :: - -## `ensureBlob()` - -`ensureBlob()` is a handy util to validate a `Blob` by checking its size and type: - -```ts -import { ensureBlob } from 'hub:blob' - -// Will throw an error if the file is not an image or is larger than 1MB -ensureBlob(file, { maxSize: '1MB', types: ['image']}) -``` - -#### Params - -::field-group - ::field{name="file" type="Blob" required} - The file to validate. - :: - ::field{name="options" type="Object" required} - Note that at least `maxSize` or `types` should be provided. - ::collapsible - ::field{name="maxSize" type="BlobSize"} - The maximum size of the file, should be: :br - (`1` | `2` | `4` | `8` | `16` | `32` | `64` | `128` | `256` | `512` | `1024`) + (`B` | `KB` | `MB` | `GB`) :br - e.g. `'512KB'`, `'1MB'`, `'2GB'`, etc. - :: - ::field{name="types" type="BlobType[]"} - Allowed types of the file, e.g. `['image/jpeg']`. - :: - :: - :: -:: - -#### Return - -Returns nothing. - -Throws an error if `file` doesn't meet the requirements. - -## Vue Composables - -::note -The following composables are meant to be used in the Vue side of your application (not the `server/` directory). -:: - -### `useUpload()` - -`useUpload` is to handle file uploads in your Nuxt application. - -```vue - - - -``` - -#### Params - -::field-group - ::field{name="apiBase" type="string" required} - The base URL of the upload API. - :: - ::field{name="options" type="Object" required} - Optionally, you can pass Fetch options to the request. Read more about Fetch API [here](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options). - ::collapsible - ::field{name="formKey" type="string"} - The key to add the file/files to the request form. Defaults to `'files'`. - :: - ::field{name="multiple" type="boolean"} - Whether to allow multiple files to be uploaded. Defaults to `true`. - :: - :: - :: -:: - -#### Return - -Return a `MultipartUpload` function that can be used to upload a file in parts. - -```ts -const { completed, progress, abort } = upload(file) -const data = await completed -``` - -### `useMultipartUpload()` - -Application composable that creates a multipart upload helper. - -::important -When using the Vercel Blob driver, this utility will automatically use the [Vercel Blob Client SDK](https://vercel.com/docs/vercel-blob/client-upload) to upload the file. -:: - -```ts [utils/multipart-upload.ts] -export const mpu = useMultipartUpload('/api/files/multipart') -``` - -#### Params - -::field-group - ::field{name="baseURL" type="string"} - The base URL of the multipart upload API handled by [`handleMultipartUpload()`](#handlemultipartupload). - :: - ::field{name="options"} - The options for the multipart upload helper. - ::collapsible - ::field{name="partSize" type="number"} - The size of each part of the file to be uploaded. Defaults to `10MB`. - :: - ::field{name="concurrent" type="number"} - The maximum number of concurrent uploads. Defaults to `1`. - :: - ::field{name="maxRetry" type="number"} - The maximum number of retry attempts for the whole upload. Defaults to `3`. - :: - ::field{name="prefix" type="string"} - The prefix to use for the blob pathname. - :: - ::field{name="fetchOptions" type="Omit"} - Override the ofetch options. - The `query` and `headers` will be merged with the options provided by the uploader. - :: - :: - :: -:: - -#### Return - -Return a `MultipartUpload` function that can be used to upload a file in parts. - -```ts -const { completed, progress, abort } = mpu(file) -const data = await completed -``` - -## Types - -### `BlobObject` - -```ts -interface BlobObject { - pathname: string - contentType: string | undefined - size: number - httpEtag: string - uploadedAt: Date - httpMetadata: Record - customMetadata: Record - url: string | undefined -} -``` - -### `BlobMultipartUpload` - -```ts -export interface BlobMultipartUpload { - pathname: string - uploadId: string - uploadPart( - partNumber: number, - value: string | ReadableStream | ArrayBuffer | ArrayBufferView | Blob - ): Promise - abort(): Promise - complete(uploadedParts: BlobUploadedPart[]): Promise -} -``` - -### `BlobUploadedPart` - -```ts -export interface BlobUploadedPart { - partNumber: number; - etag: string; -} -``` - -### `MultipartUploader` - -```ts -export type MultipartUploader = (file: File) => { - completed: Promise | undefined> - progress: Readonly> - abort: () => Promise -} -``` - -### `BlobListResult` - -```ts -interface BlobListResult { - blobs: BlobObject[] - hasMore: boolean - cursor?: string - folders?: string[] -} -``` diff --git a/docs/content/docs/2.features/2.cache/.navigation.yml b/docs/content/docs/2.features/2.cache/.navigation.yml new file mode 100644 index 00000000..f2c1f0e1 --- /dev/null +++ b/docs/content/docs/2.features/2.cache/.navigation.yml @@ -0,0 +1,4 @@ +title: Cache Pages, API & Functions +icon: i-lucide-zap +navigation.title: Cache +description: Speed up Nuxt by caching pages, API routes and functions. diff --git a/docs/content/docs/2.features/0.cache.md b/docs/content/docs/2.features/2.cache/setup.md similarity index 97% rename from docs/content/docs/2.features/0.cache.md rename to docs/content/docs/2.features/2.cache/setup.md index 2c94b723..bac8ab4b 100644 --- a/docs/content/docs/2.features/0.cache.md +++ b/docs/content/docs/2.features/2.cache/setup.md @@ -1,7 +1,6 @@ --- -title: Cache Pages, API & Functions -navigation.title: Cache -description: Speed up Nuxt by caching pages, API routes and functions. +title: Setup +description: Configure caching in your Nuxt application, including setup for pages, API routes, and serverless functions to improve performance and reduce load. --- NuxtHub Cache automatically configures [Nitro's cache storage](https://nitro.build/guide/cache#customize-cache-storage). It allows you to cache API routes, server functions, and pages in your application. diff --git a/docs/content/docs/2.features/3.database/.navigation.yml b/docs/content/docs/2.features/3.database/.navigation.yml new file mode 100644 index 00000000..572bc2c6 --- /dev/null +++ b/docs/content/docs/2.features/3.database/.navigation.yml @@ -0,0 +1,5 @@ +title: SQL Database +icon: i-lucide-database +navigation.title: Database +seo.title: Nuxt SQL Database +description: Access a SQL database with Drizzle ORM in Nuxt to store and retrieve relational data with full type-safety. diff --git a/docs/content/docs/2.features/3.database/1.setup.md b/docs/content/docs/2.features/3.database/1.setup.md new file mode 100644 index 00000000..f3a9d100 --- /dev/null +++ b/docs/content/docs/2.features/3.database/1.setup.md @@ -0,0 +1,334 @@ +--- +title: Setup +description: Configure your SQL database in Nuxt with Drizzle ORM, including installation, environment setup, and database connection initialization. +--- + +NuxtHub Database provides a type-safe SQL database powered by [Drizzle ORM](https://orm.drizzle.team), supporting PostgreSQL, MySQL, and SQLite with smart detection and automatic migrations at build time. + +## Getting started + +::steps{level="3"} +### Install dependencies + +Install Drizzle ORM, Drizzle Kit, and the appropriate driver(s) for the database you are using: + +::tabs{sync="database-dialect"} + :::tabs-item{label="PostgreSQL" icon="i-simple-icons-postgresql"} + :pm-install{name="drizzle-orm drizzle-kit postgres @electric-sql/pglite"} + ::callout + NuxtHub automatically detects your database connection using environment variables: + - Uses `PGlite` (embedded PostgreSQL) if no environment variables are set. + - Uses `postgres-js` driver if you set `DATABASE_URL`, `POSTGRES_URL`, or `POSTGRESQL_URL` environment variable. + :: + ::: + :::tabs-item{label="MySQL" icon="i-simple-icons-mysql"} + :pm-install{name="drizzle-orm drizzle-kit mysql2"} + ::callout + NuxtHub automatically detects your database connection using environment variables: + - Uses `mysql2` driver if you set `DATABASE_URL` or `MYSQL_URL` environment variable. + - Requires environment variable (no local fallback) + :: + ::: + :::tabs-item{label="SQLite" icon="i-simple-icons-sqlite"} + :pm-install{name="drizzle-orm drizzle-kit @libsql/client"} + ::callout + NuxtHub automatically detects your database connection using environment variables: + - Uses `libsql` driver for [Turso](https://turso.tech) if you set `TURSO_DATABASE_URL` and `TURSO_AUTH_TOKEN` environment variables. + - Uses `libsql` locally with file at `.data/db/sqlite.db` if no environment variables are set. + :: + ::: +:: + +### Set SQL dialect + +Enable the database in your `nuxt.config.ts` by setting the `db` property to your desired SQL dialect: + +::tabs{sync="database-dialect"} + :::tabs-item{label="PostgreSQL" icon="i-simple-icons-postgresql"} + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + hub: { + db: 'postgresql' + } + }) + ``` + ::: + :::tabs-item{label="MySQL" icon="i-simple-icons-mysql"} + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + hub: { + db: 'mysql' + } + }) + ``` + ::: + :::tabs-item{label="SQLite" icon="i-simple-icons-sqlite"} + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + hub: { + db: 'sqlite' + } + }) + ``` + ::: +:: + +### Database schema + +Create your database schema with full TypeScript support using Drizzle ORM: + +::tabs{sync="database-dialect"} + :::tabs-item{label="PostgreSQL" icon="i-simple-icons-postgresql"} + ```ts [server/db/schema.ts] + import { pgTable, text, serial, timestamp } from 'drizzle-orm/pg-core' + + export const users = pgTable('users', { + id: serial().primaryKey(), + name: text().notNull(), + email: text().notNull().unique(), + password: text().notNull(), + avatar: text().notNull(), + createdAt: timestamp().notNull().defaultNow(), + }) + ``` + ::: + + :::tabs-item{label="MySQL" icon="i-simple-icons-mysql"} + ```ts [server/db/schema.ts] + import { mysqlTable, text, serial, timestamp } from 'drizzle-orm/mysql-core' + + export const users = mysqlTable('users', { + id: serial().primaryKey(), + name: text().notNull(), + email: text().notNull().unique(), + password: text().notNull(), + avatar: text().notNull(), + createdAt: timestamp().notNull().defaultNow(), + }) + ``` + ::: + + :::tabs-item{label="SQLite" icon="i-simple-icons-sqlite"} + ```ts [server/db/schema.ts] + import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core' + + export const users = sqliteTable('users', { + id: integer().primaryKey({ autoIncrement: true }), + name: text().notNull(), + email: text().notNull().unique(), + password: text().notNull(), + avatar: text().notNull(), + createdAt: integer({ mode: 'timestamp' }).notNull(), + }) + ``` + ::: +:: + +::callout{to="/docs/features/database/schema#schema-definition"} +Learn more about defining the **database schema files**. +:: + +### Generate migrations + +Generate the database migrations from your schema: + +```bash [Terminal] +npx nuxt db generate +``` + +This creates SQL migration files in `server/db/migrations/{dialect}/` which are automatically applied during deployment and development. + +::tip{icon="i-lucide-rocket"} +That's it! You can now start your development server and query your database using the `db` instance from `hub:db`. +:: + +::important +Make sure to run `npx nuxt db generate` to generate the database migrations each time you change your database schema and restart the development server. +:: + +:: + +## AI Agents + +If you work with an IDE that supports AI agents, you can add the following text in your `Agents.md` or `.cursor/rules` file: + +```md +# Agent Instructions + +/** ... your agent instructions ... */ + +## Database + +- **Database Dialect**: The database dialect is set in the `nuxt.config.ts` file, within the `hub.db` option or `hub.db.dialect` property. +- **Drizzle Config**: Don't generate the `drizzle.config.ts` file manually, it is generated automatically by NuxtHub. +- **Generate Migrations**: Use `npx nuxt db generate` to automatically generate database migrations from schema changes +- **Never Write Manual Migrations**: Do not manually create SQL migration files in the `server/db/migrations/` directory +- **Workflow**: + 1. Create or modify the database schema in `server/db/schema.ts` or any other schema file in the `server/db/schema/` directory + 2. Run `npx nuxt db generate` to generate the migration + 3. Run `npx nuxt db migrate` to apply the migration to the database, or run `npx nuxt dev` to apply the migration during development +- **Access the database**: Use the `db` instance from `hub:db` to query the database, it is a Drizzle ORM instance. +``` + +## Local development + +During local development, view and edit your database from [Nuxt DevTools](https://devtools.nuxt.com) using the [Drizzle Studio](https://orm.drizzle.team/drizzle-studio/overview): + +:img{src="/images/landing/nuxt-devtools-database.png" alt="Nuxt DevTools Database" width="915" height="515"} + +::warning +At the moment, Drizzle Studio does not support SQLite. +:: + + +## Build-time Hooks + +::field-group + ::field{name="'hub:db:migrations:dirs'" type="(dirs: string[]) => void | Promise"} + Add additional directories to scan for database migration files (.sql). + + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + hooks: { + 'hub:db:migrations:dirs': (dirs) => { + dirs.push('my-module/db/migrations') + } + } + }) + ``` + :: + ::field{name="'hub:db:queries:paths'" type="(queries: string[], dialect: string) => void | Promise"} + Add queries that are not tracked in the `_hub_migrations` table which are applied after the database migrations complete. + + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + hooks: { + 'hub:db:queries:paths': (queries, dialect) => { + queries.push('my-module/db/queries') + } + } + }) + ``` + :: + ::field{name="'hub:db:schema:extend'" type="({ paths: string[], dialect: string }) => void | Promise"} + Extend the database schema with additional files. + + ```ts [modules/my-module/index.ts] + import { createResolver, defineNuxtModule } from '@nuxt/kit' + + export default defineNuxtModule({ + setup(options, nuxt) { + const { resolve } = createResolver(import.meta.url) + + nuxt.hook('hub:db:schema:extend', ({ paths, dialect }) => { + paths.push(resolve(`./db/schema/pages.${dialect}.ts`)) + }) + } + }) + ``` + :: +:: + +::note{to="https://nuxt.com/docs/4.x/guide/going-further/hooks#nuxt-hooks-build-time"} +Learn more about Nuxt server hooks on the **Nuxt documentation**. +:: + +## Advanced configuration + +For advanced use cases, you can explicitly configure the database connection: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + hub: { + db: { + dialect: 'postgresql', + driver: 'postgres-js', // Optional: explicitly choose driver + connection: { + connectionString: process.env.DATABASE_URL + } + } + } +}) +``` + +### Cloudflare D1 over HTTP + +Use the `d1-http` driver to access a Cloudflare D1 database over HTTP. This is useful when you want to query your D1 database when hosting on other platforms. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + hub: { + db: { + dialect: 'sqlite', + driver: 'd1-http' + } + } +}) +``` + +This driver requires the following environment variables: + +| Variable | Description | +| --- | --- | +| `NUXT_HUB_CLOUDFLARE_ACCOUNT_ID` | Your Cloudflare account ID | +| `NUXT_HUB_CLOUDFLARE_API_TOKEN` | A Cloudflare API token with D1 permissions | +| `NUXT_HUB_CLOUDFLARE_DATABASE_ID` | The ID of your D1 database | + +::callout{icon="i-lucide-info"} +You can find your Cloudflare account ID and create API tokens in the [Cloudflare dashboard](https://dash.cloudflare.com). The API token needs `D1:Edit` permissions. +:: + +## Migration guide + +::important +**Breaking changes in NuxtHub v1:** If you're upgrading from a previous version that used `hubDatabase()`, follow this migration guide. +:: + +### Configuration changes + +The `database` option has been renamed to `db` and now accepts a SQL dialect instead of a boolean. + +```diff [nuxt.config.ts] +export default defineNuxtConfig({ + hub: { +- database: true ++ db: 'sqlite' + } +}) +``` + +Valid dialects are `sqlite`, `postgresql` and `mysql`. + +### Directory changes + +The database directory has been renamed from `server/database/` to `server/db/`: + +```diff +- server/database/schema.ts ++ server/db/schema.ts + +- server/database/migrations/ ++ server/db/migrations/ +``` + +Make sure to move your schema and migration files to the new location. + +### API changes + +The old `hubDatabase()` function has been removed. You must now use Drizzle ORM. + +**Before:** + +```ts +const db = hubDatabase() +const result = await db.prepare('SELECT * FROM users').all() +``` + +**After:** +```ts +const result = await db.select().from(tables.users) +``` + +### Migration files + +Your existing SQL migration files continue to work! Just move them to `server/db/migrations/`. diff --git a/docs/content/docs/2.features/3.database/2.schema.md b/docs/content/docs/2.features/3.database/2.schema.md new file mode 100644 index 00000000..5eea5a68 --- /dev/null +++ b/docs/content/docs/2.features/3.database/2.schema.md @@ -0,0 +1,172 @@ +--- +title: Schema +description: Define and manage your database schema with Drizzle ORM in Nuxt, including tables, columns, relations, and type-safe models. +--- + +NuxtHub supports defining the database schema in multiple files and directories, allowing you to organize your schema files in a way that makes sense for your project, but also open the possibility to Nuxt modules to extend the database schema. + +### Schema files + +Database schema can be defined in a single file or in multiple files, these files are scanned and automatically imported following this glob pattern: +- `server/db/schema.ts` +- `server/db/schema.{dialect}.ts` +- `server/db/schema/*.ts` +- `server/db/schema/*.{dialect}.ts` + +The merged schema is exported in `hub:db:schema` or via the `schema` object in the `hub:db` namespace: + +```ts +import * as schema from 'hub:db:schema' +// or +import { schema } from 'hub:db' +``` + +::callout{icon="i-lucide-lightbulb"} +You can locate the generated schema file at `.nuxt/hub/db/schema.mjs`. +:: + +::note{to="https://orm.drizzle.team/docs/sql-schema-declaration" external} +Learn more about [Drizzle ORM schema](https://orm.drizzle.team/docs/sql-schema-declaration) on the Drizzle documentation. +:: + +### Nuxt layers + +Database schema is scanned and automatically imported for each [Nuxt layer](https://nuxt.com/docs/getting-started/layers). + +This meands that you can also define schema in the `layers` directory: + +```bash [Directory structure] +layers/cms/server/db/schema.ts +layers/products/server/db/schema/products.ts +``` + +### Nuxt modules + +If you are a Nuxt module developer, you can also extend the database schema by using the `hub:db:schema:extend` hook: + +```ts [modules/cms/index.ts] +import { defineNuxtModule, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup(options, nuxt) { + const { resolvePath } = createResolver(import.meta.url) + nuxt.hook('hub:db:schema:extend', async ({ dialect, paths }) => { + // Add your module drizzle schema files for the given dialect + // e.g. ./schema/pages.postgresql.ts if hub.db is 'postgresql' + paths.push(await resolvePath(`./schema/pages.${dialect}`)) + }) + } +}) +``` + +### Sharing types with Vue + +Types inferred from your database schema are only available on the server-side by default. To share these types with your Vue application, you can use the [`shared/`](https://nuxt.com/docs/guide/directory-structure/shared) directory which is auto-imported across both server and client. + +Create a types file in the `shared/types/` directory: + +```ts [shared/types/db.ts] +import { users, posts } from 'hub:db:schema' + +// Select types (for reading data) +export type User = typeof users.$inferSelect +export type Post = typeof posts.$inferSelect + +// Insert types (for creating data) +export type NewUser = typeof users.$inferInsert +export type NewPost = typeof posts.$inferInsert +``` + +These types are now auto-imported and available in your Vue components, composables, and API routes: + +::code-group +```vue [pages/users.vue] + +``` + +```ts [server/api/users.post.ts] +import { db, schema } from 'hub:db' + +export default eventHandler(async (event) => { + const body = await readBody(event) + + return await db.insert(schema.users).values(body).returning() +}) +``` +:: + +::tip +You can also create more specific types by using `Pick` and `Omit` TypeScript's built-in utility types. + +```ts [shared/types/db.ts] +// User without password for public API responses +export type PublicUser = Omit + +// Only the fields needed for user creation form +export type UserForm = Pick +``` +:: + + +## Database seed + +You can populate your database with initial data using [Nitro Tasks](https://nitro.build/guide/tasks): + +::steps{level="3"} + +### Enable Nitro tasks + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + nitro: { + experimental: { + tasks: true + } + } +}) +``` + +### Create a seed task + +```ts [server/tasks/seed.ts] +import { db, schema } from 'hub:db' + +export default defineTask({ + meta: { + name: 'db:seed', + description: 'Seed database with initial data' + }, + async run() { + console.log('Seeding database...') + + const users = [ + { + name: 'John Doe', + email: 'john@example.com', + password: 'hashed_password', + avatar: 'https://i.pravatar.cc/150?img=1', + createdAt: new Date() + }, + { + name: 'Jane Doe', + email: 'jane@example.com', + password: 'hashed_password', + avatar: 'https://i.pravatar.cc/150?img=2', + createdAt: new Date() + } + ] + + await db.insert(schema.users).values(users) + + return { result: 'Database seeded successfully' } + } +}) +``` + +### Execute the task + +Open the `Tasks` tab in Nuxt DevTools and click on the `db:seed` task. + +:: diff --git a/docs/content/docs/2.features/3.database/3.query.md b/docs/content/docs/2.features/3.database/3.query.md new file mode 100644 index 00000000..23c97965 --- /dev/null +++ b/docs/content/docs/2.features/3.database/3.query.md @@ -0,0 +1,104 @@ +--- +title: Query +description: Learn how to read and write data using Drizzle ORM in Nuxt, including filtering, joining, and aggregating relational data safely and efficiently. +--- + +Now that you have your database schema and migrations set up, you can start querying your database. + +The `hub:db` module provides access to the database through a [Drizzle ORM](https://orm.drizzle.team) instance. + +```ts +import { db } from 'hub:db' +``` + +::tip +`db` is auto-imported on server-side, you can directly use it without importing it from `hub:db`. +:: + +## SQL Select + +```ts [server/api/users.get.ts] +import { db, schema } from 'hub:db' + +export default eventHandler(async (event) => { + return await db.query.users.findMany() + // or + return await db.select().from(schema.users) +}) +``` + +::callout{to="https://orm.drizzle.team/docs/select" external} +Learn more about [Drizzle ORM select](https://orm.drizzle.team/docs/select) on the Drizzle documentation. +:: + +## SQL Insert + +```ts [server/api/users.post.ts] +import { db, schema } from 'hub:db' + +export default eventHandler(async (event) => { + const { name, email } = await readBody(event) + + return await db + .insert(schema.users) + .values({ + name, + email, + createdAt: new Date() + }) + .returning() +}) +``` + +::callout{to="https://orm.drizzle.team/docs/insert" external} +Learn more about [Drizzle ORM insert](https://orm.drizzle.team/docs/insert) on the Drizzle documentation. +:: + +## SQL Update + +```ts [server/api/users/[id\\].patch.ts] +import { db, schema } from 'hub:db' + +export default eventHandler(async (event) => { + const { id } = getRouterParams(event) + const { name } = await readBody(event) + + return await db + .update(schema.users) + .set({ name }) + .where(eq(tables.users.id, Number(id))) + .returning() +}) +``` + +::callout{to="https://orm.drizzle.team/docs/update" external} +Learn more about [Drizzle ORM update](https://orm.drizzle.team/docs/update) on the Drizzle documentation. +:: + +## SQL Delete + +```ts [server/api/users/[id\\].delete.ts] +import { db, schema } from 'hub:db' + +export default eventHandler(async (event) => { + const { id } = getRouterParams(event) + + const deletedUser = await db + .delete(schema.users) + .where(eq(schema.users.id, Number(id))) + .returning() + + if (!deletedUser) { + throw createError({ + statusCode: 404, + message: 'User not found' + }) + } + + return { deleted: true } +}) +``` + +::callout{to="https://orm.drizzle.team/docs/delete" external} +Learn more about [Drizzle ORM delete](https://orm.drizzle.team/docs/delete) on the Drizzle documentation. +:: diff --git a/docs/content/docs/2.features/3.database/4.migrations.md b/docs/content/docs/2.features/3.database/4.migrations.md new file mode 100644 index 00000000..0639ddbe --- /dev/null +++ b/docs/content/docs/2.features/3.database/4.migrations.md @@ -0,0 +1,145 @@ +--- +title: Migrations +description: Manage database schema changes in Nuxt with Drizzle ORM migrations, including creating, applying, and tracking migration files safely. +--- + +Database migrations provide version control for your database schema. NuxtHub supports SQL migration files (`.sql`) and automatically applies them during development and deployment. Making them fully compatible with Drizzle Kit generated migrations. + +::note +Create dialect-specific migrations with `..sql` suffix (e.g., `0001_create-todos.postgresql.sql`). +:: + +### Migrations Directories + +NuxtHub scans `server/db/migrations` for migrations in each [Nuxt layer](https://nuxt.com/docs/getting-started/layers). + +To scan additional directories, specify them in your config: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + hub: { + db: { + dialect: 'postgresql', + migrationsDirs: [ + 'server/db/custom-migrations/' + ] + } + } +}) +``` + +For more control (e.g., in Nuxt modules), use the `hub:db:migrations:dirs` hook: + +::code-group +```ts [modules/auth/index.ts] +import { createResolver, defineNuxtModule } from '@nuxt/kit' + +export default defineNuxtModule({ + meta: { + name: 'my-auth-module' + }, + setup(options, nuxt) { + const { resolve } = createResolver(import.meta.url) + + nuxt.hook('hub:db:migrations:dirs', (dirs) => { + dirs.push(resolve('./db-migrations')) + }) + } +}) +``` +```sql [modules/auth/db-migrations/0001_create-users.sql] +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL +); +``` +:: + +::tip +All migration files are copied to `.data/db/migrations` when you run Nuxt, giving you a consolidated view. +:: + +## Automatic migrations + +Migrations are automatically applied when you: +- Start the development server (`npx nuxi dev`) +- Build the application (`npx nuxi build`) + +Applied migrations are tracked in the `_hub_migrations` database table. + +## Generating migrations + +Once you have updates your database schema, you can generate new migrations using the following command: + +```bash [Terminal] +npx nuxt db generate +``` + +This will generate new migrations files in `server/db/migrations/{dialect}/` which are automatically applied during development and deployment. + +## Applying migrations + +Once you have generated new migrations, you can apply them using the following command: + +```bash [Terminal] +npx nuxt db migrate +``` + +This will apply the new migrations to your database. + +::tip +When running the development server, NuxtHub will automatically apply the migrations for you. +:: + +## Post-migration queries + +::important +Advanced use case: These queries run after migrations but aren't tracked in `_hub_migrations`. Ensure they're idempotent. +:: + +Use the `hub:db:queries:paths` hook to run additional queries after migrations: + +::code-group +```ts [modules/admin/index.ts] +import { createResolver, defineNuxtModule } from '@nuxt/kit' + +export default defineNuxtModule({ + meta: { + name: 'my-auth-module' + }, + setup(options, nuxt) { + const { resolve } = createResolver(import.meta.url) + + nuxt.hook('hub:db:queries:paths', (paths, dialect) => { + paths.push(resolve(`./db-queries/seed-admin.${dialect}.sql`)) + }) + } +}) +``` +```sql [modules/admin/db-queries/seed-admin.sql] +INSERT OR IGNORE INTO admin_users (id, email, password) VALUES (1, 'admin@nuxt.com', 'admin'); +``` +:: + +::tip +All migrations queries are copied to `.data/db/queries` when you run Nuxt, giving you a consolidated view. +:: + +## Foreign-key constraints + +For Cloudflare D1 with Drizzle ORM migrations, replace: + +```diff [Example] +-PRAGMA foreign_keys = OFF; ++PRAGMA defer_foreign_keys = on; + +ALTER TABLE ... + +-PRAGMA foreign_keys = ON; ++PRAGMA defer_foreign_keys = off; +``` + +::callout{to="https://developers.cloudflare.com/d1/sql-api/foreign-keys/#defer-foreign-key-constraints" external} +Learn more about [defer foreign key constraints](https://developers.cloudflare.com/d1/sql-api/foreign-keys/#defer-foreign-key-constraints) in Cloudflare D1. +:: diff --git a/docs/content/docs/2.features/3.database/cli.md b/docs/content/docs/2.features/3.database/cli.md new file mode 100644 index 00000000..01044f57 --- /dev/null +++ b/docs/content/docs/2.features/3.database/cli.md @@ -0,0 +1,88 @@ +--- +title: CLI +description: Manage your Nuxt SQL database with the `npx nuxt db` CLI, including generating migrations, applying them, running SQL queries, and marking migrations as applied. +--- + +NuxtHub provides a CLI for managing your database migrations and running SQL queries accessible from the `npx nuxt db` command. + +## `nuxt db generate` + +Generate database migrations from the schema. + +```bash [Terminal] +USAGE db generate [OPTIONS] + +OPTIONS + --cwd The directory to run the command in. + -v, --verbose Show verbose output. +``` + +## `nuxt db migrate` + +Apply database migrations to the database. + +```bash [Terminal] +USAGE db migrate [OPTIONS] + +OPTIONS + + --cwd The directory to run the command in. + --dotenv Point to another .env file to load. + -v, --verbose Show verbose output. +``` + +## `nuxt db mark-as-migrated` + +Mark local database migration(s) as applied to the database. + +```bash [Terminal] +USAGE db mark-as-migrated [OPTIONS] [NAME] + +ARGUMENTS + NAME The name of the migration to mark as applied. + +OPTIONS + --cwd The directory to run the command in. + --dotenv Point to another .env file to load. + -v, --verbose Show verbose output. +``` + +## `nuxt db drop` + +Drop a table from the database. + +```bash [Terminal] +USAGE db drop [OPTIONS]
+ +ARGUMENTS + TABLE The name of the table to drop. + +OPTIONS + --cwd The directory to run the command in. + --dotenv Point to another .env file to load. + -v, --verbose Show verbose output. +``` + +## `nuxt db sql` + +Execute a SQL query against the database. + +```bash [Terminal] +USAGE db sql [OPTIONS] [QUERY] + +ARGUMENTS + QUERY The SQL query to execute. If not provided, reads from stdin. + +OPTIONS + --cwd The directory to run the command in. + --dotenv Point to another .env file to load, relative to the root directory. + -v, --verbose Show verbose output. +``` + +Example usage: + +```bash [Terminal] +npx nuxt db sql "SELECT * FROM users" +# or +npx nuxt db sql < dump.sql +``` diff --git a/docs/content/docs/2.features/4.kv/.navigation.yml b/docs/content/docs/2.features/4.kv/.navigation.yml new file mode 100644 index 00000000..fd8c81bc --- /dev/null +++ b/docs/content/docs/2.features/4.kv/.navigation.yml @@ -0,0 +1,4 @@ +title: Key Value Storage +icon: i-lucide-braces +navigation.title: Key Value +description: Use a key-value data storage in Nuxt. diff --git a/docs/content/docs/2.features/0.kv.md b/docs/content/docs/2.features/4.kv/setup.md similarity index 57% rename from docs/content/docs/2.features/0.kv.md rename to docs/content/docs/2.features/4.kv/setup.md index d83ace69..e0b8fe0a 100644 --- a/docs/content/docs/2.features/0.kv.md +++ b/docs/content/docs/2.features/4.kv/setup.md @@ -1,7 +1,6 @@ --- -title: Key Value Storage -navigation.title: Key Value -description: Use a key-value data storage in Nuxt. +title: Setup +description: Set up Key-Value Storage in your Nuxt application, including installation, environment configuration, and connecting to your KV database. --- NuxtHub Key Value Storage automatically configures [Nitro Storage](https://nitro.build/guide/storage), which is built on [unstorage](https://unstorage.unjs.io/). @@ -128,147 +127,3 @@ export default defineNuxtConfig({ ::callout{to="https://unstorage.unjs.io/drivers"} You can find the driver list on [unstorage documentation](https://unstorage.unjs.io/drivers) with their configuration. :: - -## Usage - -The `hub:kv` module provides access to the Key-Value storage through an [unstorage](https://unstorage.unjs.io) instance. - -```ts -import { kv } from 'hub:kv' -``` - -::tip -`kv` is auto-imported on server-side, you can directly use it without importing it from `hub:kv`. -:: - -### Set an item - -Puts an item in the storage. - -```ts -import { kv } from 'hub:kv' - -await kv.set('vue', { year: 2014 }) - -// using prefixes to organize your KV namespace, useful for the `keys` operation -await kv.set('vue:nuxt', { year: 2016 }) -``` - -::note -The maximum size of a value is 25 MiB and the maximum length of a key is 512 bytes. -:: - -#### Expiration - -By default, items in your KV namespace will never expire. You can delete them manually using the [`del()`](#delete-an-item) method or set a TTL (time to live) in seconds. - -The item will be deleted after the TTL has expired. The `ttl` option maps to Cloudflare's [`expirationTtl`](https://developers.cloudflare.com/kv/api/write-key-value-pairs/#reference) option. Values that have recently been read will continue to return the cached value for up to 60 seconds and may not be immediately deleted for all regions. - -```ts -import { kv } from 'hub:kv' - -await kv.set('vue:nuxt', { year: 2016 }, { ttl: 60 }) -``` - - - - -### Get an item - -Retrieves an item from the Key-Value storage. - -```ts -import { kv } from 'hub:kv' - -const vue = await kv.get('vue') -/* -{ - year: 2014 -} -*/ -``` - - -### Has an item - -Checks if an item exists in the storage. - -```ts -import { kv } from 'hub:kv' - -const hasAngular = await kv.has('angular') // false -const hasVue = await kv.has('vue') // true -``` - -### Delete an item - -Delete an item with the given key from the storage. - -```ts -import { kv } from 'hub:kv' - -await kv.del('react') -``` - -### Clear the KV namespace - -Deletes all items from the KV namespace.. - -```ts -import { kv } from 'hub:kv' - -await kv.clear() -``` - -To delete all items for a specific prefix, you can pass the prefix as an argument. We recommend using prefixes for better organization in your KV namespace. - -```ts -import { kv } from 'hub:kv' - -await kv.clear('react') -``` - -### List all keys - -Retrieves all keys from the KV storage. - -```ts -import { kv } from 'hub:kv' - -const keys = await kv.keys() -/* -[ - 'react', - 'react:gatsby', - 'react:next', - 'vue', - 'vue:nuxt', - 'vue:quasar' -] -``` - -To get the keys starting with a specific prefix, you can pass the prefix as an argument. We recommend using prefixes for better organization in your KV namespace. - -```ts -import { kv } from 'hub:kv' - -const vueKeys = await kv.keys('vue') -/* -[ - 'vue:nuxt', - 'vue:quasar' -] -*/ -``` diff --git a/docs/content/docs/2.features/4.kv/usage.md b/docs/content/docs/2.features/4.kv/usage.md new file mode 100644 index 00000000..5d3492f3 --- /dev/null +++ b/docs/content/docs/2.features/4.kv/usage.md @@ -0,0 +1,146 @@ +--- +title: Usage +description: Learn how to store, retrieve, update, and delete key-value pairs in your Nuxt application, with practical examples and best practices. +--- + +The `hub:kv` module provides access to the Key-Value storage through an [unstorage](https://unstorage.unjs.io) instance. + +```ts +import { kv } from 'hub:kv' +``` + +::tip +`kv` is auto-imported on server-side, you can directly use it without importing it from `hub:kv`. +:: + +## Set an item + +Puts an item in the storage. + +```ts +import { kv } from 'hub:kv' + +await kv.set('vue', { year: 2014 }) + +// using prefixes to organize your KV namespace, useful for the `keys` operation +await kv.set('vue:nuxt', { year: 2016 }) +``` + +::note +The maximum size of a value is 25 MiB and the maximum length of a key is 512 bytes. +:: + +#### Expiration + +By default, items in your KV namespace will never expire. You can delete them manually using the [`del()`](#delete-an-item) method or set a TTL (time to live) in seconds. + +The item will be deleted after the TTL has expired. The `ttl` option maps to Cloudflare's [`expirationTtl`](https://developers.cloudflare.com/kv/api/write-key-value-pairs/#reference) option. Values that have recently been read will continue to return the cached value for up to 60 seconds and may not be immediately deleted for all regions. + +```ts +import { kv } from 'hub:kv' + +await kv.set('vue:nuxt', { year: 2016 }, { ttl: 60 }) +``` + + + + +## Get an item + +Retrieves an item from the Key-Value storage. + +```ts +import { kv } from 'hub:kv' + +const vue = await kv.get('vue') +/* +{ + year: 2014 +} +*/ +``` + + +## Has an item + +Checks if an item exists in the storage. + +```ts +import { kv } from 'hub:kv' + +const hasAngular = await kv.has('angular') // false +const hasVue = await kv.has('vue') // true +``` + +## Delete an item + +Delete an item with the given key from the storage. + +```ts +import { kv } from 'hub:kv' + +await kv.del('react') +``` + +## Clear the KV namespace + +Deletes all items from the KV namespace.. + +```ts +import { kv } from 'hub:kv' + +await kv.clear() +``` + +To delete all items for a specific prefix, you can pass the prefix as an argument. We recommend using prefixes for better organization in your KV namespace. + +```ts +import { kv } from 'hub:kv' + +await kv.clear('react') +``` + +## List all keys + +Retrieves all keys from the KV storage. + +```ts +import { kv } from 'hub:kv' + +const keys = await kv.keys() +/* +[ + 'react', + 'react:gatsby', + 'react:next', + 'vue', + 'vue:nuxt', + 'vue:quasar' +] +``` + +To get the keys starting with a specific prefix, you can pass the prefix as an argument. We recommend using prefixes for better organization in your KV namespace. + +```ts +import { kv } from 'hub:kv' + +const vueKeys = await kv.keys('vue') +/* +[ + 'vue:nuxt', + 'vue:quasar' +] +*/ +``` diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index f76ee30f..31088ba2 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -56,8 +56,13 @@ export default defineNuxtConfig({ '/changelog/rss.xml': { prerender: true }, // Redirects '/docs/getting-started/remote-storage': { redirect: { statusCode: 301, to: '/' } }, - '/docs/features': { redirect: { statusCode: 301, to: '/docs/features/blob' } }, + '/docs/features': { redirect: { statusCode: 301, to: '/docs/features/blob/setup' } }, + '/docs/features/blob': { redirect: { statusCode: 301, to: '/docs/features/blob/setup' } }, + '/docs/features/cache': { redirect: { statusCode: 301, to: '/docs/features/cache/setup' } }, + '/docs/features/database': { redirect: { statusCode: 301, to: '/docs/features/database/setup' } }, + '/docs/features/kv': { redirect: { statusCode: 301, to: '/docs/features/kv/setup' } }, '/docs/features/realtime': { redirect: { statusCode: 301, to: '/docs/guides/realtime' } }, + '/docs/guides': { redirect: { statusCode: 301, to: '/docs/guides/pre-rendering' } }, '/docs/recipes': { redirect: { statusCode: 301, to: '/docs/guides/pre-rendering' } }, '/docs/storage/blob': { redirect: { statusCode: 301, to: '/docs/features/blob' } }, '/docs/storage/database': { redirect: { statusCode: 301, to: '/docs/features/database' } }, From 82c7cc271e4d24a80fbd4dc42f25d65d915cea4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 19 Dec 2025 01:00:32 +0100 Subject: [PATCH 2/4] docs: revamp --- README.md | 8 +- docs/app/app.vue | 27 +- docs/app/components/AppHeader.vue | 17 +- docs/app/components/PageHeaderLinks.vue | 2 +- docs/app/layouts/docs.vue | 49 +- docs/app/pages/templates.vue | 6 +- .../content/changelog/nuxthub-multi-vendor.md | 4 +- .../content/docs/1.getting-started/1.index.md | 14 +- .../3.database => 2.database}/.navigation.yml | 2 +- .../1.setup.md => 2.database/1.index.md} | 7 +- .../3.database => 2.database}/2.schema.md | 3 +- .../3.database => 2.database}/3.query.md | 3 +- .../3.database => 2.database}/4.migrations.md | 3 +- .../3.database => 2.database}/cli.md | 3 +- docs/content/docs/2.features/.navigation.yml | 1 - docs/content/docs/2.features/1.blob/setup.md | 350 ---------- docs/content/docs/2.features/1.blob/usage.md | 585 ---------------- .../1.blob => 3.blob}/.navigation.yml | 2 +- docs/content/docs/3.blob/1.index.md | 215 ++++++ docs/content/docs/3.blob/2.upload.md | 628 ++++++++++++++++++ docs/content/docs/3.blob/3.usage.md | 475 +++++++++++++ .../{2.features => }/4.kv/.navigation.yml | 2 +- .../4.kv/setup.md => 4.kv/1.index.md} | 0 .../4.kv/usage.md => 4.kv/2.usage.md} | 5 +- .../2.cache => 5.cache}/.navigation.yml | 0 docs/content/docs/5.cache/1.index.md | 81 +++ .../2.cache/setup.md => 5.cache/2.usage.md} | 109 +-- .../{3.guides => 6.guides}/.navigation.yml | 0 .../{3.guides => 6.guides}/1.pre-rendering.md | 0 .../docs/{3.guides => 6.guides}/2.realtime.md | 0 docs/content/index.md | 92 +-- docs/nuxt.config.ts | 24 +- docs/public/_redirects | 6 - playground/server/api/todos/index.get.ts | 6 +- src/blob/lib/storage.ts | 2 +- src/blob/runtime/app/composables/useUpload.ts | 2 +- src/blob/runtime/blob.d.ts | 4 +- src/blob/types/index.ts | 12 +- src/kv/runtime/kv.d.ts | 14 +- src/types/config.ts | 8 +- 40 files changed, 1533 insertions(+), 1238 deletions(-) rename docs/content/docs/{2.features/3.database => 2.database}/.navigation.yml (90%) rename docs/content/docs/{2.features/3.database/1.setup.md => 2.database/1.index.md} (97%) rename docs/content/docs/{2.features/3.database => 2.database}/2.schema.md (99%) rename docs/content/docs/{2.features/3.database => 2.database}/3.query.md (98%) rename docs/content/docs/{2.features/3.database => 2.database}/4.migrations.md (98%) rename docs/content/docs/{2.features/3.database => 2.database}/cli.md (98%) delete mode 100644 docs/content/docs/2.features/.navigation.yml delete mode 100644 docs/content/docs/2.features/1.blob/setup.md delete mode 100644 docs/content/docs/2.features/1.blob/usage.md rename docs/content/docs/{2.features/1.blob => 3.blob}/.navigation.yml (88%) create mode 100644 docs/content/docs/3.blob/1.index.md create mode 100644 docs/content/docs/3.blob/2.upload.md create mode 100644 docs/content/docs/3.blob/3.usage.md rename docs/content/docs/{2.features => }/4.kv/.navigation.yml (82%) rename docs/content/docs/{2.features/4.kv/setup.md => 4.kv/1.index.md} (100%) rename docs/content/docs/{2.features/4.kv/usage.md => 4.kv/2.usage.md} (92%) rename docs/content/docs/{2.features/2.cache => 5.cache}/.navigation.yml (100%) create mode 100644 docs/content/docs/5.cache/1.index.md rename docs/content/docs/{2.features/2.cache/setup.md => 5.cache/2.usage.md} (56%) rename docs/content/docs/{3.guides => 6.guides}/.navigation.yml (100%) rename docs/content/docs/{3.guides => 6.guides}/1.pre-rendering.md (100%) rename docs/content/docs/{3.guides => 6.guides}/2.realtime.md (100%) delete mode 100644 docs/public/_redirects diff --git a/README.md b/README.md index 1c215cf2..f8c9f6a3 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ NuxtHub supercharges your Nuxt development workflow so you can focus on shipping ## ✨ Key Features NuxtHub provides optional features to help you build full-stack applications: -- [**SQL database**](https://hub.nuxt.com/docs/features/database) to store your application's data with [automatic migrations](https://hub.nuxt.com/docs/features/database#database-migrations) -- [**Files storage**](https://hub.nuxt.com/docs/features/blob) to store static assets, such as images, videos and more -- [**Caching system**](https://hub.nuxt.com/docs/features/cache) for your Nuxt pages, API routes or server functions -- [**Key-Value**](https://hub.nuxt.com/docs/features/kv) to store JSON data accessible globally with low-latency +- [**SQL database**](https://hub.nuxt.com/docs/database) to store your application's data with [automatic migrations](https://hub.nuxt.com/docs/databas/migrations) +- [**Files storage**](https://hub.nuxt.com/docs/blob) to store static assets, such as images, videos and more +- [**Caching system**](https://hub.nuxt.com/docs/cache) for your Nuxt pages, API routes or server functions +- [**Key-Value**](https://hub.nuxt.com/docs/kv) to store JSON data accessible globally with low-latency Read more on https://hub.nuxt.com diff --git a/docs/app/app.vue b/docs/app/app.vue index 0662f381..a3440a4f 100644 --- a/docs/app/app.vue +++ b/docs/app/app.vue @@ -1,6 +1,5 @@ diff --git a/docs/app/components/PageHeaderLinks.vue b/docs/app/components/PageHeaderLinks.vue index fed65a65..d6f67617 100644 --- a/docs/app/components/PageHeaderLinks.vue +++ b/docs/app/components/PageHeaderLinks.vue @@ -43,7 +43,7 @@ const items = [ ], [ { - label: 'Copy MCP Server URL', + label: 'Copy MCP URL', icon: 'i-lucide-link', onSelect() { copy(`https://hub.nuxt.com/mcp`) diff --git a/docs/app/layouts/docs.vue b/docs/app/layouts/docs.vue index afbd2839..5fecc594 100644 --- a/docs/app/layouts/docs.vue +++ b/docs/app/layouts/docs.vue @@ -3,54 +3,7 @@ import type { ContentNavigationItem } from '@nuxt/content' const navigation = inject>('navigation') -const route = useRoute() - -const overviewMap: Record = { - '/docs/getting-started': [ - '/docs/getting-started', - '/docs/getting-started/installation', - '/docs/getting-started/deploy', - '/docs/getting-started/migration' - ], - '/docs/guides': [ - '/docs/guides', - '/docs/guides/pre-rendering', - '/docs/guides/realtime' - ] -} - -const asideNavigation = computed(() => { - const section = (navigation.value.find(l => l.path === '/docs')?.children ?? []) - .find(l => route.path.startsWith(l.path)) - - if (!section?.children) { - return [] - } - - const links = section.children - const overviewPaths = overviewMap[section.path] - - // Section has no overview grouping → return normal links - if (!overviewPaths) { - return links - } - - const overviewChildren = links - .filter(item => overviewPaths.includes(item.path)) - - const otherLinks = links - .filter(item => !overviewPaths.includes(item.path)) - - return [ - { - title: 'Overview', - path: section.path, - children: overviewChildren, - defaultOpen: true - }, - ...otherLinks - ] -}) +const asideNavigation = computed(() => navigation.value.find(item => item.path === '/docs')?.children || [])