server/api/users/[id].get.ts
import { z } from 'zod'

export default defineEndpoint({
  request: { params: z.object({ id: z.coerce.number() }) },
  responses: {
    200: User,
    404: z.object({ message: z.string() }),
  },
  handler: (event) => {
    return findUser(event.validated.params.id) ?? event.respond(404, { message: 'Not found' })
  },
})
app code — nothing to import
const result = await $endpoint('/api/users/:id', {
  method: 'get',
  params: { id: '1' },
})

if (result.status === 200) result.body.name // User

One contract, every status typed.

Define the HTTP contract once, next to the handler. Request values are validated before execution, and every declared response status becomes a branchable client result with the matching body type.

  • params, query, headers, and body are validated before your handler runs
  • Check result.status and TypeScript narrows result.body to the matching response schema
  • Runtime validation, $endpoint, useEndpoint, and OpenAPI derive from the same contract
Define your first endpoint
step 1 — ship it without a schema
export default defineEndpoint({
  request: { params: z.object({ id: z.coerce.number() }) },
  handler: (event) => {
    return findUser(event.validated.params.id)
    // client types are inferred from this return value
  },
})
step 2 — tighten the contract
export default defineEndpoint({
  request: { params: z.object({ id: z.coerce.number() }) },
  responses: { 200: User, 404: NotFound },
  handler: async (event) => {
    const user = await findUser(event.validated.params.id)
    return user ?? event.respond(404, { message: 'Not found' })
  },
})

// now the handler return is checked against the schemas,
// and client types come from the contract instead

Incremental by design.

No big-bang migration. Only routes that export an endpoint join the contract — and inside a route, the contract can start loose and tighten when you are ready.

  • Opt-in per route: everything else stays a plain Nitro route
  • No response schema yet? Client types are inferred from your handler return value
  • Declare responses to lock the handler in — or delete the export to roll back
Read the adoption guide
status-typed result
const result = await $endpoint('/api/users/:id', {
  method: 'get',
  params: { id: '123' },
})

if (result.status === 200) {
  result.body.name // User
}

if (result.status === 404) {
  result.body.message // typed from the 404 schema
}

Errors are typed too.

Declared non-2xx responses stop being unknown. Branch on the status code and the body type follows.

  • responses declares 200 and 404 — TypeScript checks handler returns
  • Awaiting the request narrows the response body by status
  • .raw() returns the native Web Response for streaming or low-level access
Responses
nuxt.config.ts — official SSR setup
export default defineNuxtConfig({
  modules: ['@pinia/nuxt', '@pinia/colada-nuxt', 'nuxt-endpoints'],
})
pages/users/[id].vue
import { useQuery } from '@pinia/colada'
import { queryOptions } from '#endpoints/colada'

const route = useRoute()
const request = $endpoint('/api/users/:id', {
  method: 'get',
  params: { id: String(route.params.id) },
})
const user = useQuery(queryOptions(request))

if (user.data.value?.status === 200) {
  user.data.value.body.name // User
}

Pinia Colada integration

Your contract, now query-ready.

The same $endpoint request object plugs directly into Pinia Colada. Colada owns server-state behavior while Nuxt Endpoints keeps request identity, HTTP idempotency, and status-aware response types aligned with the server contract.

  • GET and HEAD expose query options; unsafe methods expose mutation options
  • Ordinary Pinia Colada options keep invalidation, optimistic updates, and Devtools standard
  • Use the official Nuxt modules for automatic SSR prefetching, serialization, and hydration
  • useEndpoint and query options carry incoming cookies to internal routes during SSR
Use Pinia Colada
GET /_endpoints/schema
{
  "openapi": "3.1.0",
  "paths": {
    "/api/users/{id}": {
      "get": {
        "operationId": "getApiUsersById",
        "parameters": [
          { "name": "id", "in": "path", "required": true }
        ],
        "responses": {
          "200": { "description": "OK" },
          "404": { "description": "Not found" }
        }
      }
    }
  }
}

OpenAPI that can't go stale.

The OpenAPI 3.1 document is generated from the same contracts that run your validation, so there is no spec to keep in sync. Endpoints stay plain HTTP routes.

  • Served at /_endpoints/schema — always matching the code
  • document / extend hatches add auth schemes and extra detail
  • Plain REST: callable from curl, mobile apps, and any other service
Explore the OpenAPI output

Explore by topic.

Getting Started

Install the module, define your first endpoint, and call it with types.

Open guide

Nuxt 5 Progress

Follow the public Nuxt 5 integration branches and see what is implemented today.

Open guide

Define Endpoints

Declare validated request and response contracts with the canonical route-handler API.

Open guide

Generated Client

Call server routes by typed path and method.

Open guide

Responses

Work with status-aware endpoint results or raw Web Responses.

Open guide

Pinia Colada

Use typed endpoint request objects as Pinia Colada queries and mutations with its official Nuxt integration.

Open guide

OpenAPI

Serve OpenAPI 3.1 from endpoint definitions.

Open guide

Schema Libraries

Zod, Valibot, and Effect Schema are supported without locking the API layer to one vendor.

Open guide

Idempotency

Idempotency-Key replay protection for unsafe endpoints, with an application-owned durable storage contract.

Open guide

Low-level HTTP

Handle files, streams, redirects, proxies, raw Responses, and 204 routes.

Open guide

Incremental Adoption

Convert one route at a time. Every other route keeps working unchanged.

Open guide

Mental Model

One route definition powers the server, client, and documentation.

Open guide

Why Nuxt Endpoints?

The drift problem in plain Nuxt apps, and the single-contract idea behind the module.

Open guide

Comparison

How Nuxt Endpoints relates to Nuxt typed fetch, tRPC, and OpenAPI tooling.

Open guide

Limits

Supported platform versions, current constraints, and upstream integration plans.

Open guide