These utilities are auto-imported in your server/ directory.
server/api/*, server/middleware/*, webhooks, jobs). It does not cover Nuxt app route middleware (defineNuxtRouteMiddleware).If you need Convex data inside defineNuxtRouteMiddleware() (for auth/permissions during navigation), use one-shot APIs:
useConvexCallserverConvexQueryuseConvexQuery and useConvexPaginatedQuery are setup-scope composables and should not be used inside route middleware/plugins.
| Function | Purpose |
|---|---|
serverConvexQuery | Execute a query |
serverConvexMutation | Execute a mutation |
serverConvexAction | Execute an action |
Execute a one-off query from server-side code.
import { api } from '~~/convex/_generated/api'
export default defineEventHandler(async (event) => {
const posts = await serverConvexQuery(event, api.posts.list, { status: 'published' })
return posts
})
function serverConvexQuery<Query>(
event: H3Event,
query: Query,
args?: FunctionArgs<Query>,
options?: ServerConvexOptions,
): Promise<FunctionReturnType<Query>>
| Parameter | Type | Description |
|---|---|---|
event | H3Event | Current Nitro event (used for config + auth) |
query | FunctionReference | Query function reference |
args | object | Query arguments |
options | ServerConvexOptions | Optional auth policy/token override |
Execute a mutation from server-side code.
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 }
})
function serverConvexMutation<Mutation>(
event: H3Event,
mutation: Mutation,
args?: FunctionArgs<Mutation>,
options?: ServerConvexOptions,
): Promise<FunctionReturnType<Mutation>>
Execute an action from server-side code.
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
})
function serverConvexAction<Action>(
event: H3Event,
action: Action,
args?: FunctionArgs<Action>,
options?: ServerConvexOptions,
): Promise<FunctionReturnType<Action>>
Options available for all fetch functions:
interface ServerConvexOptions {
auth?: 'auto' | 'required' | 'none'
authToken?: string
}
import { api } from '~~/convex/_generated/api'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export default defineEventHandler(async (event) => {
const body = await readRawBody(event)
const signature = getHeader(event, 'stripe-signature')!
// Verify webhook
const stripeEvent = stripe.webhooks.constructEvent(
body!,
signature,
process.env.STRIPE_WEBHOOK_SECRET!,
)
// Handle the event
switch (stripeEvent.type) {
case 'checkout.session.completed': {
const session = stripeEvent.data.object
await serverConvexMutation(event, api.subscriptions.activate, {
userId: session.metadata!.userId,
stripeSubscriptionId: session.subscription as string,
})
break
}
case 'customer.subscription.deleted': {
const subscription = stripeEvent.data.object
await serverConvexMutation(event, api.subscriptions.cancel, {
stripeSubscriptionId: subscription.id,
})
break
}
}
return { received: true }
})
import { api } from '~~/convex/_generated/api'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
// Get auth token from session cookie
const sessionCookie = getCookie(event, 'better-auth.session_token')
if (!sessionCookie) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
// Exchange session for JWT token
const tokenResponse = await $fetch<{ token?: string }>(
`${config.public.convex.siteUrl}/api/auth/convex/token`,
{ headers: { Cookie: `better-auth.session_token=${sessionCookie}` } },
)
if (!tokenResponse?.token) {
throw createError({ statusCode: 401, message: 'Invalid session' })
}
// Fetch with auth
const profile = await serverConvexQuery(
event,
api.users.getProfile,
{},
{ authToken: tokenResponse.token },
)
return profile
})
import { api } from '~~/convex/_generated/api'
export default defineEventHandler(async (event) => {
// Verify cron secret
const cronSecret = getHeader(event, 'x-cron-secret')
if (cronSecret !== process.env.CRON_SECRET) {
throw createError({ statusCode: 401 })
}
// Trigger digest generation
const result = await serverConvexAction(event, api.notifications.sendDailyDigests, {})
return { sent: result.count }
})
import { api } from '~~/convex/_generated/api'
export default defineEventHandler(async (event) => {
// Only track page views
if (!event.path.startsWith('/api/')) {
// Fire and forget - don't await
serverConvexMutation(event, api.analytics.trackPageView, {
path: event.path,
userAgent: getHeader(event, 'user-agent') ?? '',
timestamp: Date.now(),
}).catch(() => {
// Ignore errors - analytics shouldn't break the request
})
}
})
import { api } from '~~/convex/_generated/api'
export default defineEventHandler(async (event) => {
const { repoUrl } = await readBody(event)
// Fetch from external API
const [owner, repo] = repoUrl.replace('https://github.com/', '').split('/')
const issues = await $fetch(`https://api.github.com/repos/${owner}/${repo}/issues`, {
headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` },
})
// Store in Convex
const imported = await serverConvexMutation(event, api.issues.importFromGithub, {
repoUrl,
issues: issues.map((i: any) => ({
title: i.title,
body: i.body,
githubId: i.id,
})),
})
return { imported: imported.count }
})
export default defineEventHandler(async (event) => {
try {
const result = await serverConvexMutation(event, api.tasks.create, { text: 'New task' })
return result
} catch (error) {
// Convex errors have a message property
if (error instanceof Error) {
throw createError({
statusCode: 400,
message: error.message,
})
}
throw error
}
})
Enable logging in your nuxt.config.ts to debug server-side operations:
export default defineNuxtConfig({
convex: {
url: process.env.CONVEX_URL,
logging: {
enabled: true, // Enable canonical log events
},
},
})
Server-side operations emit operation:complete events:
[better-convex-nuxt] ▷ query ✓ api.posts.list 45ms
[better-convex-nuxt] ▷ mutation ✓ api.tasks.create 67ms
See Logging for full configuration options.
Full type inference:
// Return type is inferred from query
const posts = await serverConvexQuery(event, api.posts.list, { status: 'published' })
// posts: Post[]
// Args are typed
await serverConvexMutation(
event,
api.posts.create,
{ title: 123 }, // Type error: title should be string
)