Server Routes in Nuxt 3

Nuxt 3 not only does it give us amazing tools to make building our frontend radically easier, it also gives us some awesome tools for building our backend.

Michael Thiessen
Nuxt 3

Mastering Nuxt 3 course is here!

Get notified when we release new tutorials, lessons, and other expert Nuxt content.

Click here to view the Nuxt 3 course

One of the great things about Nuxt 3 is that it’s a full stack framework.

Not only does it give us amazing tools to make building our frontend radically easier, it also gives us some awesome tools for building our backend.

In this article, we’ll take a look at how we can use server routes in Nuxt 3.

Defining Server Routes

The most basic version of a server route is this:

export default defineEventHandler(() => 'Not so complicated.');

We use defineEventHandler to create an event handler, and then directly export that so that Nuxt can use it.

All of our server files are placed in the /server/routes directory. Routing here works exactly like page routing —it’s based on the filename. So if we put our server route in /server/routes/hello.ts, we can access it by sending a request to /hello.

But Nuxt 3 also gives us a shorthand for API routes since these are the most common type. Any route placed in /server/api will automatically be prefixed with /api. If we place our event handler in /server/api/hello.ts, we can access it be sending a request to /api/hello instead.

Return Values

One of my favourite features is how h3 —the server that Nuxt uses internally —handles return values from defineEventHandler.

Instead of needing to properly set content-type headers and format our Response object correctly, we just return whatever we need to return:

// Return JSON without any extra work!
export default defineEventHandler(() => ({
  "name": "Chocolate",
  "type": "Ice Cream",
  "ingredients": ["Cream", "Sugar", "Cocoa Powder"],
  "serving_suggestion": "In a waffle cone with whipped cream and a cherry on top",
  "calories": 250
}));

But a lot of the time we need some sort of input to our endpoint —we’re not just sending the same JSON blob over and over again.

Getting Inputs

There are a couple main ways we can get inputs or arguments passed into an event handler:

  1. Through the route itself via route parameters or query parameters
  2. From the body of the request object

If we have a server route at /server/api/icecream/[flavor].ts we can use the flavor route parameter to dynamically return the right object. We access it from event.context.params:

import { IceCreamFlavor } from './types';

const flavors: IceCreamFlavor[] = [
  {
    name: 'Chocolate',
    type: 'Ice Cream',
    ingredients: ['Cream', 'Sugar', 'Cocoa Powder'],
    serving_suggestion: 'In a waffle cone with whipped cream and a cherry on top',
    calories: 250
  },
  {
    name: 'Vanilla',
    type: 'Ice Cream',
    ingredients: ['Cream', 'Sugar', 'Vanilla Extract'],
    serving_suggestion: 'In a waffle cone with chocolate syrup and sprinkles',
    calories: 200
  },
  {
    name: 'Strawberry',
    type: 'Ice Cream',
    ingredients: ['Cream', 'Sugar', 'Strawberry Puree'],
    serving_suggestion: 'In a bowl with fresh sliced strawberries',
    calories: 225
  }
];

export default defineEventHandler((): IceCreamFlavor | undefined => {
    // Grab the parameter from the route
  const { flavor } = event.context.params;
  return flavors.find(flavor => flavor.name.toLowerCase() === name.toLowerCase());
});

We can also rewrite this handler to get the flavor from the body of the request using the readBody utility from h3:

export default defineEventHandler(async (event): IceCreamFlavor | undefined => {
  // Pass in the event object so we can parse out the body
  const { flavor } = await readBody(event);
  return flavors.find(flavor => flavor.name.toLowerCase() === name.toLowerCase());
});

Now we rename this route to /server/api/icecream.ts, and send it a request where the body is { flavor: 'chocolate' } and we’ll get the chocolate object back!

Advanced Server Routes

That’s enough to get you started on server routes. They’re pretty straightforward, so you don’t need to know a lot in order to be productive with them.

But as always with Nuxt, there’s a lot more you can do with them if you need to.

Here are a few places you can continue your exploration:

  • Check out the list of h3 utility methods —lots of very helpful things in here like readBody, getCookie, getRequestHeader and many more.
  • Learn about server middleware —functions that run on every request that you can use to check or modify the request object.
  • Play around with creating Nitro plugins (aka server plugins) to extend the behaviour of your backend.
  • Check out the advanced usage section of the docs. It shows how you can use your own custom h3 router, stream responses, and leverage the storage layer for custom caching.
Michael Thiessen
Michael is a passionate full time Vue.js and Nuxt.js educator. His weekly newsletter is sent to over 11,000 Vue developers, and he has written over a hundred articles for his blog and VueSchool.

Follow MasteringNuxt on