Endpoints and API Routes
Astro endpoints are .ts or .js files in src/pages/ that return Response objects instead of rendering HTML. They handle RSS feeds, sitemaps, JSON APIs, text files, and any other non-HTML output.
Basic endpoint
// src/pages/api/health.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => {
return new Response(JSON.stringify({ status: 'ok' }), {
headers: { 'Content-Type': 'application/json' },
});
};
HTTP methods
Export named functions for each method:
// src/pages/api/posts.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ request }) => {
const posts = await fetchPosts();
return new Response(JSON.stringify(posts), {
headers: { 'Content-Type': 'application/json' },
});
};
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
// process body
return new Response(JSON.stringify({ success: true }), { status: 201 });
};
Supported: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, ALL.
Static vs on-demand endpoints
In output: 'static' mode (default), endpoints run at build time and produce static files:
// src/pages/robots.txt.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = () => {
return new Response(`User-agent: *\nAllow: /`, {
headers: { 'Content-Type': 'text/plain' },
});
};
This generates a robots.txt file at build time.
For request-time endpoints, opt out of prerendering:
// src/pages/api/search.ts
export const prerender = false;
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ url }) => {
const query = url.searchParams.get('q');
const results = await search(query);
return new Response(JSON.stringify(results));
};
Dynamic route endpoints
// src/pages/api/posts/[id].ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ params }) => {
const post = await fetchPost(params.id);
if (!post) {
return new Response('Not found', { status: 404 });
}
return new Response(JSON.stringify(post), {
headers: { 'Content-Type': 'application/json' },
});
};
RSS feeds
Astro has an official RSS package:
npm install @astrojs/rss
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';
export async function GET(context: APIContext) {
const posts = await getCollection('blog');
return rss({
title: 'My Blog',
description: 'Blog feed',
site: context.site!,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
link: `/blog/${post.id}/`,
})),
});
}
Sitemaps
Use the official integration:
npx astro add sitemap
// astro.config.mjs
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com',
integrations: [sitemap()],
});
Automatically generates sitemap-index.xml and individual sitemaps at build time.
Text and Markdown endpoints
For endpoints like llms-full.txt:
// src/pages/llms-full.txt.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => {
const content = await generateLLMsContent();
return new Response(content, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
};
Accessing request context
Endpoint handlers receive a context object:
export const GET: APIRoute = async (context) => {
context.params; // route parameters
context.request; // standard Request object
context.url; // parsed URL
context.site; // configured site URL
context.locals; // data from middleware
context.redirect(); // redirect helper
context.cookies; // cookie helpers
};
Redirects from endpoints
export const GET: APIRoute = async ({ redirect }) => {
return redirect('/new-location', 301);
};
Comparison with Next.js route handlers
The pattern is almost identical. Key differences:
- File location:
src/pages/instead ofapp/ - Type:
APIRouteinstead of untyped exports - Static endpoints produce files at build time (Next.js route handlers are always dynamic unless explicitly cached)
- The
contextobject has Astro-specific helpers (Astro.locals,Astro.cookies)