So far, we have stayed on the client side of a Nuxt application. Pages (/app/pages) built from components (/app/components) and driven by logic in /app/composables and /app/utils are the building blocks of what the user ultimately sees - the frontend. For many apps, especially smaller ones, this is more than enough.
But Nuxt also provides powerful features for backend operations. It lets you expose API endpoints, receive and process requests, and acts as a fully fledged server - all together in the same code-base.
Nitro
Nuxt is built on top of the internal web server engine Nitro. Nitro provides its own runtime independent of everything else and powers the application environment, giving us a range of great features. Developers will appreciate hot module reloading (HMR) during development - just save the file and changes are applied instantly to your locally running app. If you’re just starting out today, this may feel standard, but it wasn’t always this easy.
Another Nitro feature is the API layer, which makes it straightforward to build and expose endpoints that can be called via HTTP(S).
/server
A file that implements an API endpoint looks like this:
export default defineEventHandler((event) => {
// handler code
})
Place it in the special /server/api or /server/routes directory and Nuxt will automatically detect and serve it. The difference between these two folders is that a handler in /server/api/foo.ts is exposed at http://localhost:3000/api/foo, while a handler in /server/routes/foo.ts responds at http://localhost:3000/foo. The /server/api subfolder is effectively the same as /server/routes/api. Since the /api prefix is common, it exists as a convenience, but if you prefer more flexibility, you can use /server/routes and structure your server-side routes however you like.
You can refine which HTTP method a handler responds to by using filename suffixes. For example, /server/api/foo.get.ts enables GET at http://localhost:3000/api/foo, and /server/api/foo.post.ts enables POST at the same path. The logic for each can differ significantly. All HTTP methods are supported. If no method suffix is specified, the handler will process all incomming methods the same way.
The defineEventHandler callback can receive an event parameter of type H3Event from the h3 framework. This minimalist HTTP framework, integrated by Nuxt/Nitro, provides many useful helpers. For example, to read the body of an incoming POST request:
export default defineEventHandler(async (event) => {
const postData = await readBody(event)
// further input processing
})
In event.node.req you’ll find the incoming HTTP request in the Node.js HTTP format, giving you easy access to headers via the headers field, among other things.
An important aspect to call out now - and we’ll return to it in later tutorials - is that server code runs strictly on the server, this means where your app will be deployed. Only what you explicitly return from your handlers is sent out to the public internet. Anything involving sensitive data - passwords or API keys - should be isolated from the user’s browser; otherwise, you risk security issues and data leaks. There are ways to call databases directly from client functions and you may be tempted to use them for simplicity. Don’t. Always put sensitive operations behind a safe server-side layer.
Case Study
On my blog home page, you can find several lists titled “What’s new in XY?”. The data shown there comes from GitHub lists of articles that I maintain. For example, here is a summary of my news from the Nuxt/Vue world. Nuxt-powered web services reference these source files and fetch the latest five articles from them. The service for Nuxt news runs on my personal website: https://alois-seckar.cz/nuxt-news.
Its implementation is very simple; the heavy lifting is done by a utility function that reads the provided markdown URL and extracts the required data using the node-html-parser library.
Demo Project
You can find the reference implementation here:
nuxt-api @ GitHub
The server part of the project exposes two API endpoints:
-
sample.get.ts- returns the text “Hello from Nuxt Server!” plus the current date and time (so you can see the request is processed fresh each time) -
error.get.ts- intentionally returns a random HTTP error in the 400–410 range
The simple frontend calls sample.get.ts when you click a button and displays the result. You can also visit both endpoints directly. If you try the “error” service, you’ll see how the latest Nuxt (v4.2) catches and displays errors in dev mode.
Further Reading
- Web scraper in Nuxt 3 (Alois Sečkár) - my earlier four‑part step‑by‑step tutorial on working with the Nuxt backend
- Server Routes in Nuxt 3 (Michael Thiessen)
- Docs - Nuxt
/server - Docs - Nitro
- Docs - h3
Summary
The server side of the Nuxt framework is designed for backend operations. It lets you build and expose your own APIs - either publicly to the internet or as an internal layer that separates concerns: the server handles external API calls and data manipulation, and the client-side frontend focuses on rendering. Operations that require sensitive inputs - passwords or API keys - should always run on the server, where they are protected from exposure.
Just as the /app/pages folder enables client-side routing, the /server/api and /server/routes folders abstract away manual route definitions for server code. You can focus solely on your application logic.
The next part of the tutorial will introduce middleware - handlers that can run automatically before rendering the frontend or before processing server-side data.
Top comments (0)