S SvelteKurs
M9 · Laden & Mutieren

API-Endpunkte (+server.ts)

Wenn du eine echte JSON-API brauchst: +server.ts-Dateien beantworten GET/POST/PUT/DELETE und geben Response-Objekte zurück — für Mobile-Clients, externe Consumer und mehr.

Was ist ein Endpoint?

Eine +server.ts ist eine Route ohne UI. Statt HTML exportiert sie Funktionen, die nach den HTTP-Methoden benannt sind — GET, POST, PUT, DELETE, PATCH — und jeweils ein Response-Objekt zurückgeben. Der Helfer json(...) baut daraus bequem eine JSON-Antwort mit korrektem Content-Type.

src/routes/api/notes/+server.ts ts
// src/routes/api/notes/+server.ts
import { json } from '@sveltejs/kit';
import { db, schema } from '$lib/server/db';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ locals }) => {
  const notes = await db.select().from(schema.notes);
  return json(notes); // -> Content-Type: application/json
};

🛤️ Rails-Vergleich: +server ≈ API-Controller / respond_to

Eine +server.ts ist dein API-Controller. Wo Rails render json: notes schreibt, schreibst du return json(notes). Die Methoden-Exports GET/POST/DELETE übernehmen die Rolle von respond_to bzw. den nach HTTP-Verb gerouteten Controller-Actions.

Rails ruby
# Rails: ein API-Controller mit respond_to / render json
class Api::NotesController < ApplicationController
  def index
    render json: Note.all              # ~ return json(notes)
  end

  def create
    note = Note.create!(note_params)
    render json: note, status: :created # ~ json(note, { status: 201 })
  end

  def destroy
    Note.find(params[:id]).destroy
    head :no_content                    # ~ new Response(null, { status: 204 })
  end
end

Request lesen, Response bauen

In einem Endpoint arbeitest du mit den Web-Standards Request und Response. Einen JSON-Body liest du mit await request.json(), für Statuscodes und Fehler nutzt du error(status, message) bzw. den zweiten Parameter von json().

POST /api/notes ts
// src/routes/api/notes/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request, locals }) => {
  if (!locals.user) throw error(401, 'Nicht angemeldet');

  const body = await request.json();          // JSON-Body lesen
  if (!body.title) throw error(400, 'title fehlt');

  const [note] = await db
    .insert(schema.notes)
    .values({ userId: locals.user.id, title: body.title })
    .returning();

  return json(note, { status: 201 });          // 201 Created
};
src/routes/api/notes/[id]/+server.ts ts
// src/routes/api/notes/[id]/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const PUT: RequestHandler = async ({ params, request }) => {
  const body = await request.json();
  const [note] = await db
    .update(schema.notes)
    .set({ title: body.title })
    .where(eq(schema.notes.id, Number(params.id)))
    .returning();
  return json(note);
};

export const DELETE: RequestHandler = async ({ params }) => {
  await db.delete(schema.notes).where(eq(schema.notes.id, Number(params.id)));
  return new Response(null, { status: 204 }); // 204 No Content
};

ℹ️ json() vs. new Response()

json(daten, opts) ist die Abkürzung für JSON-Antworten. Für alles andere — 204 No Content, Redirects, Text, Binärdaten — baust du direkt ein new Response(body, { status, headers }).

Endpoint oder Form Action?

Beide schreiben Daten auf dem Server — aber sie zielen auf verschiedene Aufrufer:

  • Form Action — für deine eigene Seite. Ein <form> postet, du bekommst Progressive Enhancement (use:enhance) und automatisches Neuladen der load-Funktionen geschenkt. Das ist fast immer die richtige Wahl innerhalb der App.
  • +server.ts-Endpoint — wenn der Aufrufer kein Formular deiner Seite ist: eine Mobile-App, ein externer Dienst per Webhook, ein Drittanbieter oder JavaScript, das strukturiertes JSON braucht. Kurz: eine öffentliche/programmatische API.

⚠️ Nicht doppeln

Baue keinen Endpoint nur, um ihn aus dem eigenen Frontend per fetch anzusprechen, wenn eine Form Action reicht — das kostet dich Progressive Enhancement und die automatische Invalidierung. Greif zu +server.ts erst, wenn du wirklich eine API für fremde Clients brauchst.

Zusammenfassung

💡 Das Wichtigste

  • +server.ts exportiert GET/POST/PUT/DELETE und gibt Response-Objekte zurück.
  • json(daten, { status }) für JSON; new Response(null, { status: 204 }) für den Rest.
  • request.json() liest den Body; error(status, msg) wirft HTTP-Fehler.
  • App-intern → Form Action. Externe/mobile/JSON-Clients → Endpoint.

🎯 Probier es selbst

Lege src/routes/api/ping/+server.ts mit einem GET an, das json({ pong: true, time: new Date().toISOString() }) zurückgibt. Rufe die Route im Browser auf und danach per curl. Ergänze anschließend ein POST, das den JSON-Body zurückspiegelt.