export const endpoint = defineEndpoint({
params: z.object({ id: z.coerce.number() }), // '123' → 123
})
export const endpoint = defineEndpoint({
params: z.object({ id: z.coerce.number() }), // '123' → 123
})
Supports Nuxt 4.5+ — see compatibility
Choose Zod, Valibot, or Effect Schema for request params, then reuse that schema for types, clients, and OpenAPI.
export const endpoint = defineEndpoint({
params: z.object({ id: z.coerce.number() }), // '123' → 123
})
export const endpoint = defineEndpoint({
params: z.object({ id: z.coerce.number() }), // '123' → 123
})
import { z } from 'zod'
export const endpoint = defineEndpoint({
params: z.object({ id: z.coerce.number() }),
responses: {
200: User,
404: z.object({ message: z.string() }),
},
})const user = await $endpoint('/api/users/:id', {
method: 'get',
params: { id: '1' },
})
user.name // User — inferred from the contractDefine the HTTP contract once, next to the handler, in the schema library you already use. Runtime validation, the generated client, and OpenAPI all derive from that single definition — no codegen step to run.
export default defineEndpoint({
params: z.object({ id: z.coerce.number() }),
handler: ({ params }) => {
return findUser(params.id)
// client types are inferred from this return value
},
})export const endpoint = defineEndpoint({
params: z.object({ id: z.coerce.number() }),
responses: { 200: User, 404: NotFound },
})
// now the handler return is checked against the schemas,
// and client types come from the contract insteadNo 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.
const result = await $endpoint('/api/users/:id', {
method: 'get',
params: { id: '123' },
}).result()
if (result.status === 200) {
result.body.name // User
}
if (result.status === 404) {
result.body.message // typed from the 404 schema
}Declared non-2xx responses stop being unknown. Branch on the status code and the body type follows.
export default defineNuxtConfig({
modules: ['nuxt-endpoints'],
endpoints: {
client: {
query: { setup: 'auto' },
},
},
})import { useQuery } from '@tanstack/vue-query'
import { endpointQueryOptions } from '#endpoints/query'
const route = useRoute()
const user = useQuery(
endpointQueryOptions.getUser(() => ({
params: { id: String(route.params.id) },
})),
)
user.data.value?.name // UserOptional Vue Query adapter
After installing @tanstack/vue-query, generated option factories plug named endpoints directly into Vue Query. There is no second request model to maintain: Vue Query owns server-state behavior while Nuxt Endpoints keeps every request and response aligned with the server contract.
{
"openapi": "3.1.0",
"paths": {
"/api/users/{id}": {
"get": {
"operationId": "getUser",
"parameters": [
{ "name": "id", "in": "path", "required": true }
],
"responses": {
"200": { "description": "OK" },
"404": { "description": "Not found" }
}
}
}
}
}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.
Install the module, define your first endpoint, and call it with types.
Write request and response schemas next to the handler.
Call server routes by typed path, method, or operation name.
Choose between success bodies, status-aware typed results, and raw Web Responses.
Generate typed query, mutation, and infinite-query options for Vue Query from named endpoints, with optional Nuxt SSR setup.
Serve OpenAPI 3.1 from endpoint definitions.
Zod, Valibot, and Effect Schema are supported without locking the API layer to one vendor.
Optional Idempotency-Key replay protection for unsafe endpoints, with an application-owned durable storage contract.
Handle files, streams, redirects, proxies, raw Responses, and 204 routes.
Convert one route at a time. Every other route keeps working unchanged.
One route definition powers the server, client, and documentation.
The drift problem in plain Nuxt apps, and the single-contract idea behind the module.
How Nuxt Endpoints relates to Nuxt typed fetch, tRPC, and OpenAPI tooling.
Early alpha, with the important constraints documented.