### Start Development Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Launch your app and view it at http://localhost:3000. ```bash pnpm dev ``` ```bash bun dev ``` ```bash npm run dev ``` -------------------------------- ### Install dependencies Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Install the Convex client and the Nuxt module. ```bash pnpm add convex better-convex-nuxt ``` ```bash bun add convex better-convex-nuxt ``` ```bash npm add convex better-convex-nuxt ``` -------------------------------- ### Prepare Sample Data Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Create a file to define some initial data for your database. ```json {"text": "Buy groceries", "isCompleted": true} {"text": "Go for a swim", "isCompleted": true} {"text": "Integrate Convex", "isCompleted": false} ``` -------------------------------- ### Start the development server Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/installation2.md Command to start the Convex Nuxt development server. ```bash npm run dev ``` -------------------------------- ### Environment Variables Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Local environment file for Convex configuration. ```env CONVEX_DEPLOYMENT=dev:***-***-*** CONVEX_URL=https://***-***-***.convex.cloud ``` -------------------------------- ### Import Sample Data Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Import this data into a table named tasks. ```bash npx convex import --table tasks sampleData.jsonl ``` -------------------------------- ### Create a Database Query Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Define a function to fetch the tasks from the database. ```typescript import { query } from './_generated/server' export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query('tasks').collect() }, }) ``` -------------------------------- ### Create a new Nuxt project Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Initialize a fresh Nuxt project using the minimal template. ```bash pnpm create nuxt@latest my-convex-app ``` ```bash bun create nuxt@latest my-convex-app ``` ```bash npm create nuxt@latest my-convex-app ``` -------------------------------- ### Configure Nuxt Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Add the module to your nuxt.config.ts and provide the Convex deployment URL. ```typescript export default defineNuxtConfig({ compatibilityDate: '2025-07-15', modules: ['better-convex-nuxt'], convex: { url: process.env.CONVEX_URL, }, }) ``` -------------------------------- ### Display Data in your App Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Use the useConvexQuery composable to subscribe to your data. It fetches on the server (SSR) and updates in real-time via WebSocket. ```vue ``` -------------------------------- ### Install Packages Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/3.auth.md Install the Better Auth core and the Convex integration using your preferred package manager. ```bash pnpm add better-auth @convex-dev/better-auth ``` ```bash bun add better-auth @convex-dev/better-auth ``` ```bash npm install better-auth @convex-dev/better-auth ``` -------------------------------- ### Initialize Convex Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Run the Convex dev command. This will prompt you to log in and create a new project. It will also generate a .env.local file containing your CONVEX_URL. ```bash npx convex dev ``` -------------------------------- ### Code Group Examples Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/mdc-components.md Provides examples of installing Nuxt UI using different package managers (pnpm, yarn, npm, bun) within a code group. ```bash pnpm add @nuxt/ui ``` ```bash yarn add @nuxt/ui ``` ```bash npm install @nuxt/ui ``` ```bash bun add @nuxt/ui ``` -------------------------------- ### Define the Schema Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Create a schema file to describe your data structure. This ensures type safety across your full stack. ```typescript import { defineSchema, defineTable } from 'convex/server' import { v } from 'convex/values' export default defineSchema({ tasks: defineTable({ text: v.string(), isCompleted: v.boolean(), }), }) ``` -------------------------------- ### Nuxt App Component Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md Vue component to display tasks fetched from Convex. ```vue ``` -------------------------------- ### Complete Auth Page Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/1.authentication.md A full example of an authentication page (`pages/auth/signin.vue`) demonstrating email and Google sign-in using `useConvexAuth`. ```vue ``` -------------------------------- ### Install the module Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/README.md Install the better-convex-nuxt module using pnpm. ```bash pnpm add better-convex-nuxt ``` -------------------------------- ### Basic Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/3.mutations/3.optimistic-updates.md This example demonstrates how to use `optimisticUpdate` to immediately update the local UI with an expected result before the server confirms the mutation. It shows how to update a query cache by adding a new todo item optimistically. ```typescript const { execute } = useConvexMutation(api.todos.create, { optimisticUpdate: (localStore, args) => { // Update local query cache immediately updateQuery({ query: api.todos.list, args: {}, store: localStore, updater: (current) => { const optimisticTodo = { _id: crypto.randomUUID() as Id<'todos'>, _creationTime: Date.now(), text: args.text, completed: false, } return current ? [optimisticTodo, ...current] : [optimisticTodo] }, }) }, }) ``` -------------------------------- ### Status Display Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/3.mutations/2.actions.md An example demonstrating how to display the status of a Convex action (idle, pending, success, error) and provide user feedback. ```vue ``` -------------------------------- ### Backend Permission Helpers Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Server-side utilities for authorization. Use `authorize()` in every mutation! ```typescript /** * Backend Permission Helpers * * Server-side utilities for authorization. * Use authorize() in every mutation! */ import type { QueryCtx, MutationCtx } from '../_generated/server' import type { Id } from '../_generated/dataModel' import { checkPermission, type Permission, type PermissionContext, type Role, } from '../permissions.config' // ============================================ // TYPES // ============================================ export interface AuthUser { _id: Id<'users'> authId: string role: Role organizationId: Id<'organizations'> displayName?: string email?: string } // ============================================ // GET USER // ============================================ // Returns current user or null if not authenticated. export async function getUser(ctx: QueryCtx | MutationCtx): Promise { const identity = await ctx.auth.getUserIdentity() if (!identity) return null const user = await ctx.db .query('users') .withIndex('by_auth_id', (q) => q.eq('authId', identity.subject)) .first() if (!user || !user.organizationId) return null return user as AuthUser } // ============================================ // REQUIRE USER // ============================================ // Returns user or throws if not authenticated. export async function requireUser(ctx: QueryCtx | MutationCtx): Promise { const user = await getUser(ctx) if (!user) throw new Error('Unauthorized') return user } // ============================================ // AUTHORIZE // ============================================ // The main security gate. Use in EVERY mutation. // // Does three things: // 1. Verifies user is authenticated // 2. Verifies resource is in user's org (if provided) // 3. Verifies user has the permission // // Usage: // await authorize(ctx, 'org.invite') // Global // await authorize(ctx, 'post.update', post) // Resource export async function authorize( ctx: QueryCtx | MutationCtx, permission: Permission, resource?: { ownerId?: string; organizationId?: Id<'organizations'> }, ): Promise { const user = await requireUser(ctx) // Check org isolation if (resource?.organizationId && resource.organizationId !== user.organizationId) { throw new Error(`Forbidden: ${permission}`) } // Check permission const permCtx: PermissionContext = { role: user.role, userId: user.authId, } if (!checkPermission(permCtx, permission, resource)) { throw new Error(`Forbidden: ${permission}`) } return user } // ============================================ // REQUIRE SAME ORG // ============================================ // Type guard for org isolation in queries. export function requireSameOrg }> (user: AuthUser | null, resource: T | null, ): resource is T { if (!resource || !user) return false return resource.organizationId === user.organizationId } ``` -------------------------------- ### Server: Install Better Auth Plugin Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/1.authentication.md Configuration for installing the Better Auth plugin, specifically the 'admin' plugin, in the `convex/auth.ts` file. ```typescript import { convex } from '@convex-dev/better-auth/plugins' import { admin } from 'better-auth/plugins' plugins: [convex({ authConfig }), admin()] ``` -------------------------------- ### Payment Processing Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/3.mutations/2.actions.md An example of using `useConvexAction` to create a Stripe checkout session. It redirects the user to Stripe's checkout page. ```vue ``` -------------------------------- ### serverConvexAction Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/5.server-side/1.server-routes.md Execute an action from server-side code. ```typescript import { api } from '~~/convex/_generated/api' export default defineEventHandler(async (event) => { const body = await readBody(event) const report = await serverConvexAction(event, api.reports.generate, { month: body.month, year: body.year, }) return report }) ``` -------------------------------- ### Debug Options Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/6.advanced/7.module-config.md Example of configuring logging and debug options within the nuxt.config.ts file. ```typescript convex: { logging: 'info', debug: { authFlow: false, // Detailed auth flow logs (both client and server) clientAuthFlow: false, // Client-only auth flow trace serverAuthFlow: false, // Server-only auth flow trace }, } ``` -------------------------------- ### serverConvexMutation Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/5.server-side/1.server-routes.md Execute a mutation from server-side code. ```typescript import { api } from '~~/convex/_generated/api' export default defineEventHandler(async (event) => { const body = await readBody(event) await serverConvexMutation(event, api.tasks.complete, { taskId: body.taskId }) return { success: true } }) ``` -------------------------------- ### Custom Subscription Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/6.advanced/2.client-access.md Shows how to manually subscribe to updates and handle them with custom logic. ```vue ``` -------------------------------- ### serverConvexQuery Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/5.server-side/1.server-routes.md Execute a one-off query from server-side code. ```typescript import { api } from '~~/convex/_generated/api' export default defineEventHandler(async (event) => { const posts = await serverConvexQuery(event, api.posts.list, { status: 'published' }) return posts }) ``` -------------------------------- ### Header with Auth States Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/1.authentication.md An example of a header component that dynamically displays navigation links based on the user's authentication state using Convex auth components. ```vue ``` -------------------------------- ### Create Frontend Composable Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Use `createPermissions` to create your app's permission composable. ```typescript import { createPermissions } from '#imports' import { api } from '~~/convex/_generated/api' import { checkPermission, type Permission, type Resource } from '~~/convex/permissions.config' // Create permission composables for your app export const { usePermissions, usePermissionGuard } = createPermissions({ query: api.auth.getPermissionContext, checkPermission, }) ``` -------------------------------- ### File Processing Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/3.mutations/2.actions.md An example of using `useConvexAction` to process a file uploaded by the user. It converts the file to base64 and sends it to a Convex action. ```vue ``` -------------------------------- ### Backend Protection Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/3.auth.md Example of securing Convex queries and mutations by checking the user identity using `ctx.auth.getUserIdentity()` in `convex/tasks.ts`. ```typescript import { query, mutation } from './_generated/server' import { v } from 'convex/values' export const get = query({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity() if (!identity) { throw new Error('Not authenticated') } return await ctx.db.query('tasks').collect() }, }) export const create = mutation({ args: { text: v.string(), }, handler: async (ctx, args) => { const identity = await ctx.auth.getUserIdentity() if (!identity) { throw new Error('Not authenticated') } return await ctx.db.insert('tasks', { text: args.text, isCompleted: false, }) }, }) ``` -------------------------------- ### Default Loading Strategy Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/5.concepts.md Example of the default loading strategy using await useConvexQuery. ```typescript const { data } = await useConvexQuery(api.posts.get, { id }) ``` -------------------------------- ### Full Module Configuration Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/6.advanced/7.module-config.md A comprehensive example of the Better Convex Nuxt module configuration in nuxt.config.ts, including URL, authentication, skip routes, defaults, uploads, auth cache, permissions, and logging. ```typescript export default defineNuxtConfig({ modules: ['better-convex-nuxt'], convex: { url: process.env.CONVEX_URL, // siteUrl: 'https://custom-domain.example.com', // Only if using custom domain auth: { enabled: true }, skipAuthRoutes: ['/', '/pricing', '/docs/**'], defaults: { server: true, subscribe: true, }, upload: { maxConcurrent: 3, }, authCache: { enabled: true, ttl: 60, }, permissions: true, logging: process.env.NODE_ENV === 'development' ? 'debug' : false, }, }) ``` -------------------------------- ### Passing Arguments Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/2.data-fetching/1.queries.md Example of passing arguments to a Convex query. ```typescript // Convex query definition export const getById = query({ args: { id: v.id('posts') }, handler: async (ctx, args) => { return await ctx.db.get(args.id) }, }) // In your Vue component const { data: post } = await useConvexQuery( api.posts.getById, { id: 'abc123' }, // Pass the required args ) ``` ```typescript const { data: posts } = await useConvexQuery(api.posts.list, {}) ``` -------------------------------- ### Enable in Nuxt Config Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Enable the permission composables in your module config. ```typescript export default defineNuxtConfig({ modules: ['better-convex-nuxt'], convex: { url: process.env.CONVEX_URL, permissions: true, // Enable createPermissions }, }) ``` -------------------------------- ### Nuxt Configuration Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/installation2.md Adding 'better-convex-nuxt' to modules and configuring Convex URL in nuxt.config.ts. ```typescript // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ compatibilityDate: '2025-07-15', devtools: { enabled: true }, - modules: ['better-convex-nuxt'], - convex: { url: process.env.CONVEX_URL }, }) ``` -------------------------------- ### Basic Usage Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/6.advanced/2.client-access.md Demonstrates how to get the Convex client instance and use it to make a query. ```vue ``` -------------------------------- ### Application Architecture Overview Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Diagram illustrating the interaction between frontend, backend, and shared permission logic. ```text ┌─────────────────────────────────────────────────────────────────┐ │ Your Application │ ├─────────────────────────────────────────────────────────────────┤ │ Frontend │ Backend │ │ ─────────────────────────────────│──────────────────────────────│ │ │ │ │ usePermissions() │ authorize() │ │ ├─ can('post.create') │ ├─ Verify authenticated │ │ ├─ can('post.update', post) │ ├─ Verify same org │ │ └─ role, orgId, isAuthenticated │ └─ Verify permission │ │ │ │ │ ▲ │ ▲ │ │ │ uses │ │ uses │ │ │ │ │ │ │ ┌──────┴──────────────────────────────────┴──────────┐ │ │ │ ~/convex/permissions.config.ts │ │ │ │ (shared permission logic) │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` -------------------------------- ### usePermissions composable Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/2.permissions.md Example of how to use the `usePermissions` composable to get permission-related state and functions. ```typescript const { can, role, orgId, user, isAuthenticated, pending } = usePermissions() ``` -------------------------------- ### Enable logging in nuxt.config.ts Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/6.advanced/6.logging.md Quick start example to enable logging in the Nuxt configuration file. ```typescript export default defineNuxtConfig({ modules: ['better-convex-nuxt'], convex: { url: process.env.CONVEX_URL, logging: 'info', }, }) ``` -------------------------------- ### Auth-Loop Bootstrap (Strict Fail-Fast) Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/test/TESTING.md Steps to set up the local Convex environment for E2E testing, including environment variables and running the test suite. ```bash cd /Users/matthias/Git/libs/better-convex-nuxt/playground npx convex dev --local --once npx convex env set SITE_URL http://localhost:3000 --env-file .env.local npx convex env set BETTER_AUTH_SECRET --env-file .env.local cd /Users/matthias/Git/libs/better-convex-nuxt pnpm test:e2e ``` -------------------------------- ### Convex Query Function Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/installation2.md Defining a query function 'get' in convex/tasks.ts to retrieve tasks from the database. ```typescript import { query } from "./_generated/server"; export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); }, }); ``` -------------------------------- ### Backend Mutations with Permission Checks Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Illustrates how to integrate permission checks directly into Convex backend mutations for secure data operations. ```typescript import { mutation, query } from './_generated/server' import { v } from 'convex/values' import { authorize, getUser, requireSameOrg } from './lib/permissions' // Query with org isolation export const list = query({ handler: async (ctx) => { const user = await getUser(ctx) if (!user) return [] return ctx.db .query('posts') .withIndex('by_organization', (q) => q.eq('organizationId', user.organizationId)) .collect() }, }) // Mutation with permission check export const create = mutation({ args: { title: v.string(), content: v.string() }, handler: async (ctx, args) => { // Verify user can create posts const user = await authorize(ctx, 'post.create') return ctx.db.insert('posts', { title: args.title, content: args.content, status: 'draft', ownerId: user.authId, organizationId: user.organizationId, createdAt: Date.now(), updatedAt: Date.now(), }) }, }) // Mutation with ownership check export const update = mutation({ args: { id: v.id('posts'), title: v.string() }, handler: async (ctx, args) => { const post = await ctx.db.get(args.id) if (!post) throw new Error('Not found') // Checks org isolation + ownership rules await authorize(ctx, 'post.update', post) await ctx.db.patch(args.id, { title: args.title, updatedAt: Date.now(), }) }, }) export const remove = mutation({ args: { id: v.id('posts') }, handler: async (ctx, args) => { const post = await ctx.db.get(args.id) if (!post) throw new Error('Not found') await authorize(ctx, 'post.delete', post) await ctx.db.delete(args.id) }, }) ``` -------------------------------- ### Displaying Data in Nuxt App Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/installation2.md Using useQuery to fetch and display tasks from the Convex API in app.vue. ```vue ``` -------------------------------- ### Update Environment Handling Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/1.get-started.md By default, Convex saves variables to .env.local, but Nuxt looks for .env. Update your dev script in package.json to include the correct flag. ```json { "scripts": { "dev": "nuxt dev --dotenv .env.local" } } ``` -------------------------------- ### Page Protection with Permission Guard Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Shows how to protect entire pages using the `usePermissionGuard` composable, redirecting users if they lack the required permission. ```vue ``` -------------------------------- ### Mutation Function Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/demo/convex/README.md An example of a Convex mutation function with argument validation and database insertion. ```typescript // convex/myFunctions.ts import { mutation } from './_generated/server' import { v } from 'convex/values' export const myMutationFunction = mutation({ // Validators for arguments. args: { first: v.string(), second: v.string(), }, // Function implementation. handler: async (ctx, args) => { // Insert or modify documents in the database here. // Mutations can also read from the database like queries. // See https://docs.convex.dev/database/writing-data. const message = { body: args.first, author: args.second } const id = await ctx.db.insert('messages', message) // Optionally, return a value from your mutation. return await ctx.db.get('messages', id) }, }) ``` -------------------------------- ### Query Function Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/demo/convex/README.md An example of a Convex query function with argument validation and database interaction. ```typescript // convex/myFunctions.ts import { query } from './_generated/server' import { v } from 'convex/values' export const myQueryFunction = query({ // Validators for arguments. args: { first: v.number(), second: v.string(), }, // Function implementation. handler: async (ctx, args) => { // Read the database as many times as you need here. // See https://docs.convex.dev/database/reading-data. const documents = await ctx.db.query('tablename').collect() // Arguments passed from the client are properties of the args object. console.log(args.first, args.second) // Write arbitrary JavaScript here: filter, aggregate, build derived data, // remove non-public properties, or create new objects. return documents }, }) ``` -------------------------------- ### Authentication Component Setup Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/4.permissions.md Sets up the authentication component, including user creation, update, and deletion handlers, and defines a context for permissions. ```typescript import { createClient, type GenericCtx, type AuthFunctions } from '@convex-dev/better-auth' import { convex } from '@convex-dev/better-auth/plugins' import { betterAuth } from 'better-auth' import { query } from './_generated/server' import type { DataModel } from './_generated/dataModel' import { components, internal } from './_generated/api' import authConfig from './auth.config' const siteUrl = process.env.SITE_URL! const authFunctions: AuthFunctions = internal.auth export const authComponent = createClient(components.betterAuth, { authFunctions, triggers: { user: { onCreate: async (ctx, doc) => { const now = Date.now() await ctx.db.insert('users', { authId: doc._id, displayName: doc.name, email: doc.email, avatarUrl: doc.image ?? undefined, role: 'member', createdAt: now, updatedAt: now, }) }, onUpdate: async (ctx, newDoc, oldDoc) => { const nameChanged = newDoc.name !== oldDoc.name const emailChanged = newDoc.email !== oldDoc.email const imageChanged = newDoc.image !== oldDoc.image if (nameChanged || emailChanged || imageChanged) { const user = await ctx.db .query('users') .withIndex('by_auth_id', (q) => q.eq('authId', newDoc._id)) .first() if (user) { await ctx.db.patch(user._id, { ...(nameChanged && { displayName: newDoc.name }), ...(emailChanged && { email: newDoc.email }), ...(imageChanged && { avatarUrl: newDoc.image ?? undefined }), updatedAt: Date.now(), }) } } }, onDelete: async (ctx, doc) => { const user = await ctx.db .query('users') .withIndex('by_auth_id', (q) => q.eq('authId', doc._id)) .first() if (user) { await ctx.db.delete(user._id) } }, }, }, }) export const createAuth = (ctx: GenericCtx) => { return betterAuth({ baseURL: siteUrl, database: authComponent.adapter(ctx), emailAndPassword: { enabled: true }, plugins: [convex({ authConfig })], session: { expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24, }, trustedOrigins: [siteUrl], }) } export const getPermissionContext = query({ handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity() if (!identity) return null const user = await ctx.db .query('users') .withIndex('by_auth_id', (q) => q.eq('authId', identity.subject)) .first() if (!user) return null return { role: user.role, userId: user.authId, } }, }) export const { onCreate, onUpdate, onDelete } = authComponent.triggersApi() ``` -------------------------------- ### Card Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/mdc-components.md An example of a card component with a title, icon, color, and a link, suitable for small teams. ```vue ::card{title="Startup" icon="i-lucide-users" color="primary" to="https://nuxt.lemonsqueezy.com" target="_blank"} Best suited for small teams, startups and agencies with up to 5 developers. :: ``` -------------------------------- ### Composable vs Components Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/1.authentication.md Demonstrates the equivalence between using Convex auth components and the `useConvexAuth()` composable. ```vue ``` -------------------------------- ### Permission Context Query Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Add a query to your auth file that returns the permission context for the frontend. ```typescript import { query } from './_generated/server' // ... your existing auth setup ... // ============================================ // PERMISSION CONTEXT QUERY // ============================================ // Called by usePermissions() on the frontend. // Returns role, userId, and orgId for permission checks. export const getPermissionContext = query({ handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity() if (!identity) return null const user = await ctx.db .query('users') .withIndex('by_auth_id', (q) => q.eq('authId', identity.subject)) .first() if (!user) return null return { role: user.role, userId: user.authId, orgId: user.organizationId ?? null, displayName: user.displayName, email: user.email, } }, }) ``` -------------------------------- ### Protected Page Example Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/3.auth.md Example of a protected page in Nuxt.js using `definePageMeta({ convexAuth: true })` to enable zero-boilerplate route protection. ```vue ``` -------------------------------- ### Commands for Running Tests Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/test/TESTING.md Various commands to execute different testing suites, including CI/local reliability gates and specific tier tests. ```bash # CI/local reliability gate (unit + convex + nuxt + browser) pnpm test # Fast dev loop for frontend/runtime pnpm test:watch # Nuxt runtime composables only pnpm test:nuxt # Browser component suite pnpm test:browser # Full-stack E2E (manual/local) pnpm test:e2e # Non-E2E full matrix pnpm test:full ``` -------------------------------- ### Component Usage of Permissions Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/0.permissions-setup.md Demonstrates how to use the `usePermissions` composable in Vue components for conditional rendering based on permissions. ```vue ``` -------------------------------- ### List Page (populate cache) Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/7.recipes/5.instant-list-detail-cache-reuse.md This code snippet demonstrates how to populate the cache on the list page using `useConvexQuery`. ```vue ``` -------------------------------- ### App Structure Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/1.guide/2.basics.md Demonstrates the basic structure of a Nuxt application with multiple pages and navigation, including a page that uses useConvexQuery. ```vue ``` ```vue ``` ```vue ``` -------------------------------- ### Example: Use Plugin APIs with Reactive Logout Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/4.auth-security/1.authentication.md Demonstrates using plugin APIs (like `authClient.admin.listUsers()`) alongside the reactive logout provided by `useConvexAuth().signOut()` to ensure local auth state stays in sync. ```vue ``` -------------------------------- ### Basic Usage Source: https://github.com/lupinum-dev/better-convex-nuxt/blob/main/docs/content/docs/3.mutations/2.actions.md Example of using `useConvexAction` to send an email. ```vue ```