07 - API Routes

What are API Routes?

API routes are server-side endpoints defined in +server.ts files. They handle HTTP requests and return JSON or other responses — like building a REST API inside your SvelteKit app.

Basic API Route

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

export const GET: RequestHandler = async () => {
  const posts = [
    { id: 1, title: 'First Post' },
    { id: 2, title: 'Second Post' }
  ];
  
  return json(posts);  // json() sets Content-Type and serializes automatically
};

Access at: GET http://localhost:5173/api/posts

HTTP Methods

Export named functions matching HTTP methods — GET, POST, PUT, PATCH, DELETE:

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

export const GET: RequestHandler = async ({ url }) => {
  const id = url.searchParams.get('id');  // query params: /api/posts?id=1
  return json({ id });
};

export const POST: RequestHandler = async ({ request }) => {
  const data = await request.json();  // parse JSON body
  return json({ created: data }, { status: 201 });
};

export const PUT: RequestHandler = async ({ request }) => {
  const data = await request.json();
  return json({ updated: data });
};

export const DELETE: RequestHandler = async ({ url }) => {
  const id = url.searchParams.get('id');
  return json({ deleted: id });
};

Dynamic API Routes

Use [param] folders just like page routes:

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

export const GET: RequestHandler = async ({ params }) => {
  const post = await db.posts.find(params.id);  // params.id from URL
  
  if (!post) {
    error(404, 'Post not found');  // throws an error response
  }
  
  return json(post);
};

Access at: GET http://localhost:5173/api/posts/42

Request Headers

Read incoming headers from the request:

import { error, json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ request }) => {
  const auth = request.headers.get('authorization');
  
  if (!auth) {
    error(401, 'Unauthorized');
  }
  
  return json({ authenticated: true });
};

Response Headers

Set custom headers on the response:

import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async () => {
  return json(
    { data: 'value' },
    {
      headers: {
        'Cache-Control': 'max-age=3600'
      }
    }
  );
};

Non-JSON Responses

Return plain text, HTML, or any other format using the standard Response object:

import type { RequestHandler } from './$types';

// Plain text
export const GET: RequestHandler = async () => {
  return new Response('Plain text');
};
// HTML
export const GET: RequestHandler = async () => {
  return new Response('<h1>HTML</h1>', {
    headers: { 'Content-Type': 'text/html' }
  });
};

Error Handling

Use try/catch and error() for proper HTTP error responses:

import { error, json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ params }) => {
  try {
    const data = await fetchData(params.id);
    return json(data);
  } catch (e) {
    error(500, 'Internal server error');
  }
};

CORS Headers

For cross-origin access, set CORS headers manually. Handle the OPTIONS preflight request too:

import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async () => {
  return json(
    { data: 'value' },
    {
      headers: {
        'Access-Control-Allow-Origin': '*'
      }
    }
  );
};

// Preflight request — browsers send OPTIONS before cross-origin POST/PUT/DELETE
export const OPTIONS: RequestHandler = async () => {
  return new Response(null, {
    headers: {
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
      'Access-Control-Allow-Headers': 'Content-Type'
    }
  });
};

Key Points

  • +server.ts files define API endpoints — no +page.svelte needed
  • Export GET, POST, PUT, DELETE etc. as named functions
  • Use json() helper for JSON responses, new Response() for anything else
  • error() throws HTTP error responses
  • Same dynamic routing ([param]) as pages
  • These run server-side only — safe for DB access and secrets