Learn how to create full backend APIs within the Nuxt 3 framework using server routes. This guide covers endpoint creation, request handling, response types, and the underlying technologies like Nitro and h3.
Get notified when we release new tutorials, lessons, and other expert Nuxt content.
With server routes, we can build a full backend API for our app without leaving the Nuxt framework at all.
In this article, you'll learn:
server/routes
and server/api
folders are forSo, let's get started!
Server routes in Nuxt let you create API endpoints directly in your project. You can build a complete backend without switching to a separate framework or technology.
These routes live in either the server/routes
or server/api
folders, with the latter being prefixed with /api
in the URL.
Let's build our server route step by step. We'll start with a simple endpoint and then improve it.
First, we'll create a new file in the server/routes
directory:
// server/routes/ai.ts
export default defineEventHandler(() => {
return {
message: "Hello from the API"
}
})
This creates a basic endpoint that returns a simple JSON object. If we restart our Nuxt server and navigate to /ai
in the browser, we'll see this JSON response.
But we want this endpoint to be prefixed with /api
. To do this, we need to move our file to the server/api
folder:
// server/api/ai.ts
export default defineEventHandler(() => {
return {
message: "Hello from the API"
}
})
Now our endpoint is available at /api/ai
.
Let's improve our endpoint to process incoming data. We'll use the event
parameter to access request details and the readBody
function to extract data from the request:
// server/api/ai.ts
export default defineEventHandler(async (event) => {
// Extract data from the request body
const body = await readBody(event)
const { messages } = body
// Process the extracted data
const id = messages.length.toString()
const lastMessage = messages[messages.length - 1]
// Return a response that mimics an AI assistant
return {
id,
role: 'assistant',
content: `(server) You said: ${lastMessage.content}`,
}
})
This version extracts messages from the request body, identifies the last message, and returns a response that echoes back that message. We're mocking what an AI assistant response would look like, which we'll replace with a real AI integration later.
Server routes can return various types of responses:
// server/api/ai.ts
export default defineEventHandler(async (event) => {
// Different response types examples below
// Get request data if needed
const body = await readBody(event)
const { messages } = body
const id = messages.length.toString()
const lastMessage = messages[messages.length - 1]
return {
id,
role: 'assistant',
content: `(server) You said: ${lastMessage.content}`,
}
})
You'll most often return JSON objects from your API endpoints. This is perfect for structured data:
return {
id: "message-123",
timestamp: Date.now(),
data: {
name: "User",
messages: ["Hello", "How are you?"]
}
}
JSON is the standard format for web APIs and works seamlessly with frontend JavaScript.
For error handling, use the createError
function to return specific status codes:
// Authentication error
return createError({
statusCode: 401,
statusMessage: 'Unauthorized access'
})
// Resource not found
return createError({
statusCode: 404,
statusMessage: 'Resource not found'
})
// Server error
return createError({
statusCode: 500,
statusMessage: 'Internal server error'
})
This creates proper HTTP error responses that your frontend can effectively process and react to.
Sometimes a simple string is all you need:
// Plain text response
return "Success!"
// Or dynamically generated text
return `Hello, ${user.name}!`
Strings are automatically sent with the correct content-type headers for text responses.
When you don't need to return any data:
// Returns a 204 No Content status
return null
This is useful for operations like deleting a resource or other actions where you simply need to indicate success without returning data.
You can also serve binary data like images or files:
// Setting headers and returning binary data
setResponseHeaders(event, {
'content-type': 'image/png'
})
// Get image using unstorage
const storage = useStorage()
return await storage.getItemRaw(path);
The defineEventHandler
function is what creates our API endpoint. It takes a function that receives an event
parameter, which represents the HTTP request and response.
Inside this function, we can:
readBody(event)
(or other methods from h3)The response can be:
Running our code on the server instead of in the browser gives us several advantages:
Under the hood, Nuxt uses a server toolkit called Nitro, which in turn uses an HTTP server called h3. Nuxt, Nitro and h3 are part of the unjs ecosystem, which includes many tools that work great together.