Nuxt Server-API bauen
REST-API-Endpunkte direkt in deiner Nuxt-App erstellen
Server-Routen in Nuxt
Nuxt bietet eine integrierte Server-Engine (Nitro), mit der du API-Endpunkte direkt im server/-Verzeichnis definierst. Keine separate Backend-Anwendung nötig — alles in einem Projekt.
Dateien in server/api/ werden automatisch als API-Routen registriert. Die HTTP-Methode wird über den Dateinamen gesteuert:
server/
├── api/
│ ├── kontakte/
│ │ ├── index.get.ts → GET /api/kontakte
│ │ ├── index.post.ts → POST /api/kontakte
│ │ ├── [id].get.ts → GET /api/kontakte/:id
│ │ ├── [id].put.ts → PUT /api/kontakte/:id
│ │ └── [id].delete.ts → DELETE /api/kontakte/:id
│ └── health.get.ts → GET /api/health
├── middleware/
│ └── log.ts → Server-Middleware (alle Requests)
└── utils/
└── db.ts → Geteilte HilfsfunktionenRails-Vergleich
In Rails definierst du Routen in config/routes.rb und Controller in app/controllers/. In Nuxt sind Routen und Handler in einer Datei vereint — wie ein minimalistischer Controller pro Endpunkt.
Arbeiten mit JSON-Datendateien
Für Prototypen und kleine Apps reicht eine JSON-Datei als Datenquelle. Wir bauen eine Kontakte-API mit persistenter Dateispeicherung:
[
{
"id": 1,
"name": "Anna Schmidt",
"email": "anna@beispiel.de",
"telefon": "+49 170 1234567",
"firma": "TechStart GmbH",
"rolle": "Entwicklerin"
},
{
"id": 2,
"name": "Max Weber",
"email": "max@beispiel.de",
"telefon": "+49 171 9876543",
"firma": "WebDesign AG",
"rolle": "Designer"
}
]import { readFile, writeFile } from 'fs/promises'
import { resolve } from 'path'
export interface Kontakt {
id: number
name: string
email: string
telefon?: string
firma?: string
rolle?: string
}
const DB_PFAD = resolve('./server/data/kontakte.json')
export async function leseKontakte(): Promise<Kontakt[]> {
try {
const daten = await readFile(DB_PFAD, 'utf-8')
return JSON.parse(daten)
} catch {
return []
}
}
export async function schreibeKontakte(kontakte: Kontakt[]): Promise<void> {
await writeFile(DB_PFAD, JSON.stringify(kontakte, null, 2), 'utf-8')
}
export function naechsteId(kontakte: Kontakt[]): number {
if (kontakte.length === 0) return 1
return Math.max(...kontakte.map(k => k.id)) + 1
}CRUD-Endpunkte implementieren
export default defineEventHandler(async (event) => {
const query = getQuery(event)
let kontakte = await leseKontakte()
// Suchfilter
if (query.suche) {
const suchbegriff = (query.suche as string).toLowerCase()
kontakte = kontakte.filter(k =>
k.name.toLowerCase().includes(suchbegriff) ||
k.email.toLowerCase().includes(suchbegriff) ||
k.firma?.toLowerCase().includes(suchbegriff)
)
}
return kontakte
})export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Validierung
if (!body.name || !body.email) {
throw createError({
statusCode: 400,
message: 'Name und E-Mail sind Pflichtfelder'
})
}
// E-Mail-Format prüfen
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/
if (!emailRegex.test(body.email)) {
throw createError({
statusCode: 400,
message: 'Ungültiges E-Mail-Format'
})
}
const kontakte = await leseKontakte()
// Duplikat prüfen
if (kontakte.some(k => k.email === body.email)) {
throw createError({
statusCode: 409,
message: 'E-Mail-Adresse existiert bereits'
})
}
const neuerKontakt: Kontakt = {
id: naechsteId(kontakte),
name: body.name,
email: body.email,
telefon: body.telefon || undefined,
firma: body.firma || undefined,
rolle: body.rolle || undefined
}
kontakte.push(neuerKontakt)
await schreibeKontakte(kontakte)
setResponseStatus(event, 201)
return neuerKontakt
})export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
const body = await readBody(event)
if (isNaN(id)) {
throw createError({ statusCode: 400, message: 'Ungültige ID' })
}
const kontakte = await leseKontakte()
const index = kontakte.findIndex(k => k.id === id)
if (index === -1) {
throw createError({ statusCode: 404, message: 'Kontakt nicht gefunden' })
}
// Felder aktualisieren (nur übergebene Felder)
kontakte[index] = {
...kontakte[index],
...body,
id // ID darf nicht überschrieben werden
}
await schreibeKontakte(kontakte)
return kontakte[index]
})export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
if (isNaN(id)) {
throw createError({ statusCode: 400, message: 'Ungültige ID' })
}
const kontakte = await leseKontakte()
const index = kontakte.findIndex(k => k.id === id)
if (index === -1) {
throw createError({ statusCode: 404, message: 'Kontakt nicht gefunden' })
}
kontakte.splice(index, 1)
await schreibeKontakte(kontakte)
setResponseStatus(event, 204)
return null
})Fehlerbehandlung
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
if (isNaN(id)) {
throw createError({
statusCode: 400,
statusMessage: 'Bad Request',
message: 'ID muss eine Zahl sein'
})
}
const kontakte = await leseKontakte()
const kontakt = kontakte.find(k => k.id === id)
if (!kontakt) {
throw createError({
statusCode: 404,
statusMessage: 'Not Found',
message: \\\`Kontakt mit ID \${id} nicht gefunden\\\`
})
}
return kontakt
})Fehler-Konventionen
- 400 — Ungültige Eingabe (fehlende Felder, falsches Format)
- 404 — Ressource nicht gefunden
- 409 — Konflikt (z.B. Duplikat)
- 500 — Interner Serverfehler (unbehandelte Ausnahme)
createError() erzeugt eine H3-konforme Fehlerantwort. Der Client erhält ein JSON-Objekt mit statusCode und message.
Endpunkte testen
Teste deine API mit curl, den Nuxt DevTools oder einem HTTP-Client wie Insomnia:
# Alle Kontakte abrufen
curl http://localhost:3000/api/kontakte
# Kontakt suchen
curl 'http://localhost:3000/api/kontakte?suche=anna'
# Neuen Kontakt erstellen
curl -X POST http://localhost:3000/api/kontakte \\
-H 'Content-Type: application/json' \\
-d '{"name": "Lisa Müller", "email": "lisa@beispiel.de"}'
# Kontakt aktualisieren
curl -X PUT http://localhost:3000/api/kontakte/1 \\
-H 'Content-Type: application/json' \\
-d '{"telefon": "+49 172 5555555"}'
# Kontakt löschen
curl -X DELETE http://localhost:3000/api/kontakte/1Zusammenfassung
Server-API Konzepte
- server/api/ — Verzeichnis für API-Routen
- Dateiname → Methode —
.get.ts,.post.ts, etc. - [id] — Dynamische Parameter in Routen
- readBody() — Request-Body lesen
- getQuery() — Query-Parameter lesen
- createError() — Strukturierte Fehlerantworten
- server/utils/ — Geteilte Funktionen (Auto-Import)