Vercel Logo

Server Routes

In Next.js, an API route is a file called route.ts that exports named functions: GET, POST, PUT, DELETE. The HTTP method is the function name. The file lives inside app/api/, right next to your pages.

Nuxt encodes the HTTP method in the filename. A file called index.get.ts handles GET requests. A file called index.post.ts handles POST. The server code lives in server/api/, completely separate from your Vue app. Under the hood, Nuxt uses a server engine called Nitro that compiles your routes into a standalone server, which is how it deploys to platforms like Vercel without any extra configuration.

We have 17 hot springs sitting in a JSON file. Let's give them an API.

Outcome

Create a server route that returns all hot springs from the JSON data file.

Fast Track

  1. Create server/api/springs/index.get.ts with a basic event handler
  2. Import and return the springs JSON data
  3. Test the endpoint at /api/springs in the browser

Hands-on exercise 2.1

Build the first server route for the app: a GET endpoint that returns all hot springs.

Requirements:

  1. Create server/api/springs/index.get.ts
  2. Import the springs data from server/data/springs.json
  3. Return the full array of springs
  4. Verify the endpoint returns JSON at /api/springs

Implementation hints:

  • defineEventHandler is the Nuxt equivalent of exporting a GET function in Next.js. It's auto-imported in the server/ directory
  • You can import JSON files directly with import springs from "~/server/data/springs.json"
  • The ~/ alias resolves to the project root in server code, just like in app code
  • Return a value and Nitro automatically serializes it as JSON with the correct content type

In Next.js, a basic API route looks like this:

// app/api/springs/route.ts — Next.js version
import { NextResponse } from "next/server";
import springs from "@/data/springs.json";
 
export async function GET() {
  return NextResponse.json(springs);
}

Here's the Nuxt version:

server/api/springs/index.get.ts
import type { Spring } from "~/types/spring";
import springs from "~/server/data/springs.json";
 
export default defineEventHandler((event) => {
  return springs as Spring[];
});

That's it. No NextResponse.json() wrapper. No explicit status codes. Return a value and Nitro handles serialization. The event parameter gives you access to the request, query parameters, headers, and body, but we don't need any of that yet.

The filename does real work here. index.get.ts tells Nitro three things: this handles requests to the root of the /api/springs path (index), it only responds to GET requests (.get), and it's a TypeScript file (.ts). If someone sends a POST to this endpoint, Nitro returns a 405 automatically.

Compare that to Next.js, where a single route.ts file can export multiple methods. Nuxt's one-file-per-method approach means more files but less ambiguity. You never have to scan a file to find out which methods it handles.

Server auto-imports

The server/ directory has its own set of auto-imports, separate from the app/ directory. defineEventHandler, getQuery, getRouterParam, createError, and other Nitro utilities are all available without imports. Vue utilities like ref and computed are NOT available here. The server doesn't know about Vue.

Don't mix up the aliases

In server/ files, ~/ resolves to the project root. In app/ files, ~/ resolves to the app/ directory. This means ~/types/spring works in both places, but for different reasons. If you try to import a Vue component from a server route, it will fail.

Try It

Start the dev server and visit http://localhost:3000/api/springs in your browser. You should see a JSON array of 17 hot springs:

[
  {
    "id": "breitenbush-hot-springs",
    "name": "Breitenbush Hot Springs",
    "description": "Tucked into the Willamette National Forest...",
    "location": {
      "region": "Pacific Northwest",
      "country": "US",
      "lat": 44.7896,
      "lng": -121.9815
    },
    "temperature": { "min": 98, "max": 112 },
    "type": "developed",
    "features": ["clothing-optional", "forest-setting", ...],
    "elevation": 2200,
    "imageUrl": "/images/springs/breitenbush-hot-springs.jpg"
  },
  ...
]

You can also test with curl:

curl http://localhost:3000/api/springs | head -c 200

If you see the JSON data, the route is working. If you get a 404, make sure the file is at server/api/springs/index.get.ts, not app/api/springs/index.get.ts. The server directory is a common source of misplaced files when you're coming from Next.js.

Commit

git add -A && git commit -m "feat(api): add GET /api/springs server route"

Done-When

  • server/api/springs/index.get.ts exists and exports a default event handler
  • Visiting /api/springs returns a JSON array of 17 hot springs
  • You can explain how filename conventions (.get.ts, .post.ts) replace exported function names in Next.js

Solution

server/api/springs/index.get.ts
import type { Spring } from "~/types/spring";
import springs from "~/server/data/springs.json";
 
export default defineEventHandler((event) => {
  return springs as Spring[];
});