---
title: "Server Routes"
description: "Call Convex from Nitro server routes, middleware and plugins as the request's user, with the helpers nuxt-convex adds to the server."
canonical_url: "https://nuxt-convex.onmax.me/nuxt-module/server-routes"
---
# Server Routes

> Call Convex from Nitro server routes, middleware and plugins as the request's user, with the helpers nuxt-convex adds to the server.

`nuxt-convex` adds server helpers that call Convex over HTTP as the user who made the request. They use the same token as SSR, resolved once per request by the [token provider](https://nuxt-convex.onmax.me/nuxt-module/authenticated-ssr), and turn Convex failures into H3 errors.

## Example

The routes below call these Convex functions: the `add` mutation from [Design your functions](https://nuxt-convex.onmax.me/convex-patterns/functions), and a `stats` query:

```ts [convex/tasks.ts]
export const stats = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity()
    if (!identity)
      return { total: 0, completed: 0 }
    const tasks = await ctx.db.query('tasks').withIndex('by_user', q => q.eq('userId', identity.subject)).collect()
    return { total: tasks.length, completed: tasks.filter(task => task.isCompleted).length }
  },
})
```

```ts [server/api/tasks/stats.get.ts]
import { api } from '#convex/api'

export default defineEventHandler(async (event) => {
  // 401 for anonymous requests
  await requireConvexAuth(event)
  return await serverConvexQuery(event, api.tasks.stats)
})
```

```ts [server/api/tasks.post.ts]
import { api } from '#convex/api'

export default defineEventHandler(async (event) => {
  const { title } = await readBody<{ title?: string }>(event)
  if (!title)
    throw createError({ statusCode: 400, message: 'A title is required' })

  // Throws a 400 with the error's data if the mutation throws a ConvexError
  return await serverConvexMutation(event, api.tasks.add, { title })
})
```

## Imports

On Nuxt 4 (Nitro 2), the helpers are auto-imported in `server/`. They're also exported from `#convex/server`, which only resolves in server code.

On Nuxt 5 (Nitro 3) there are no server auto-imports. Import the helpers from `#convex/server`, and `defineEventHandler` and the other H3 utilities from `nuxt/server`:

```ts [server/api/tasks/stats.get.ts]
import { api } from '#convex/api'
import { requireConvexAuth, serverConvexQuery } from '#convex/server'
import { defineEventHandler } from 'nuxt/server'

export default defineEventHandler(async (event) => {
  await requireConvexAuth(event)
  return await serverConvexQuery(event, api.tasks.stats)
})
```

Explicit imports work on Nuxt 4 too, so writing them now saves a migration later.

## `serverConvexQuery`, `serverConvexMutation`, `serverConvexAction`

```ts
function serverConvexQuery<Query extends FunctionReference<'query'>>(
  event: ConvexServerEvent,
  query: Query,
  ...args: OptionalRestArgs<Query>
): Promise<FunctionReturnType<Query>>

function serverConvexMutation<Mutation extends FunctionReference<'mutation'>>(
  event: ConvexServerEvent,
  mutation: Mutation,
  ...args: OptionalRestArgs<Mutation>
): Promise<FunctionReturnType<Mutation>>

function serverConvexAction<Action extends FunctionReference<'action'>>(
  event: ConvexServerEvent,
  action: Action,
  ...args: OptionalRestArgs<Action>
): Promise<FunctionReturnType<Action>>
```

Run one Convex function as the request's user, or anonymously when there's no token. Args are optional when the function takes none. Failures are thrown as H3 errors, see [`toConvexH3Error`](#toconvexh3error).

## `useConvexServer`

```ts
function useConvexServer(event: ConvexServerEvent): ConvexServer

interface ConvexServer {
  query: (query, ...args) => Promise<FunctionReturnType<Query>>
  mutation: (mutation, ...args) => Promise<FunctionReturnType<Mutation>>
  action: (action, ...args) => Promise<FunctionReturnType<Action>>
  /** The request's ConvexHttpClient, authenticated with its token. Its errors are not mapped to H3 errors */
  getClient: () => Promise<ConvexHttpClient>
  getToken: () => Promise<string | null>
}
```

Returns the request's Convex server: one per request, created on first use, with one `ConvexHttpClient` that carries the request's token. The three `serverConvex*` helpers are shortcuts for it.

```ts [server/api/dashboard.get.ts]
import { api } from '#convex/api'

export default defineEventHandler(async (event) => {
  const convex = useConvexServer(event)
  const [stats, recent] = await Promise.all([
    convex.query(api.tasks.stats),
    convex.query(api.tasks.list),
  ])
  return { stats, recent: recent.slice(0, 5) }
})
```

Use `getClient()` when you need the raw `ConvexHttpClient`, for example to handle errors yourself.

Without a configured Convex URL, every call throws a 500.

## `getConvexToken`

```ts
function getConvexToken(event: ConvexServerEvent): Promise<string | null>
```

The request's Convex JWT, or `null`. Resolved once per request: the `convex:token` hook first, then the built-in provider. When it finds a token, the response is marked `Cache-Control: private, no-cache` and `Vary: Cookie`.

## `requireConvexAuth`

```ts
function requireConvexAuth(event: ConvexServerEvent): Promise<string>
```

Like `getConvexToken`, but throws a 401 for anonymous requests. Returns the token.

It only checks that the request has a token. Your Convex functions still decide what that user may do: check `ctx.auth.getUserIdentity()` in them.

## `toConvexH3Error`

```ts
function toConvexH3Error(error: unknown): NuxtError
```

Maps a failed Convex call to an H3 error. The helpers above apply it for you; use it with `getClient()` or your own `ConvexHttpClient`.

<table>
<thead>
  <tr>
    <th>
      Thrown
    </th>
    
    <th>
      Becomes
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ConvexError
      </code>
    </td>
    
    <td>
      Status <code>
        data.status
      </code>
      
       when it's a number from 400 to 599, otherwise 400. The message is <code>
        data
      </code>
      
       when it's a string, else <code>
        data.message
      </code>
      
      , else <code>
        Convex function failed
      </code>
      
      . <code>
        data
      </code>
      
       is kept on the error
    </td>
  </tr>
  
  <tr>
    <td>
      An H3 or Nuxt error
    </td>
    
    <td>
      Returned unchanged
    </td>
  </tr>
  
  <tr>
    <td>
      Anything else
    </td>
    
    <td>
      500 with the message <code>
        Convex request failed
      </code>
      
      . The original error is the <code>
        cause
      </code>
      
      , and its message isn't sent to the client
    </td>
  </tr>
</tbody>
</table>

```ts
// convex/tasks.ts
throw new ConvexError({ status: 404, message: 'Task not found', id })
// The server route responds with 404, message 'Task not found', and data { status, message, id }
```

`data` reaches the client in the error response. Only put in a `ConvexError` what the user may see.

## The `convex:token` hook

```ts
type ConvexTokenHook = (event: ConvexServerEvent, ctx: ConvexTokenContext) => void | Promise<void>

interface ConvexTokenContext {
  token?: string | null
  headers: Headers
}
```

Register it in a server plugin with `useServerHooks().hook('convex:token', ...)` (from `nuxt/server`) or `nitroApp.hooks.hook('convex:token', ...)`. Set `ctx.token` to a JWT, or to `null` to force anonymous; leave it `undefined` to fall through to the built-in provider. See [Authenticated SSR](https://nuxt-convex.onmax.me/nuxt-module/authenticated-ssr#custom-providers).

## Types

`ConvexServer`, `ConvexServerEvent` and `ConvexTokenContext` are exported from `#convex/server` and from `nuxt-convex`. `ConvexServerEvent` accepts both an H3 event and a `nuxt/server` event.

## Next steps

- [Error Handling](https://nuxt-convex.onmax.me/nuxt-module/error-handling) for the client side of the same errors
- [Security](https://nuxt-convex.onmax.me/nuxt-module/security) before exposing mutations through server routes


## Sitemap

See the full [sitemap](https://nuxt-convex.onmax.me/sitemap.md) for all pages.
