git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/astro-server-endpoints" ~/.claude/skills/intense-visions-harness-engineering-astro-server-endpoints-16c89c && rm -rf "$T"
agents/skills/codex/astro-server-endpoints/SKILL.mdAstro Server Endpoints
Build REST API routes, webhooks, and form handlers inside your Astro project using
endpoint files and the middleware API..ts
When to Use
- You need a GET or POST handler in the same project as your Astro pages (form submissions, data APIs, webhooks)
- You want to keep backend logic co-located with your frontend without spinning up a separate server
- You are building an authenticated route that reads cookies or headers on each request
- You need middleware to run before every request (auth checks, logging, locale detection)
- You are in SSR or hybrid mode and need dynamic data endpoints
Instructions
-
Create endpoint files in
with asrc/pages/
or.ts
extension. The file path maps to the URL, same as page routing:.js
→src/pages/api/hello.tsGET /api/hello
→src/pages/api/posts/[id].tsGET/POST /api/posts/:id
-
Export named functions for each HTTP method you want to handle. Function names must be uppercase:
// src/pages/api/posts.ts import type { APIRoute } from 'astro'; export const GET: APIRoute = async ({ request, url, cookies, locals }) => { const tag = url.searchParams.get('tag'); const posts = await fetchPosts(tag); return new Response(JSON.stringify(posts), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }; export const POST: APIRoute = async ({ request }) => { const body = await request.json(); if (!body.title) { return new Response(JSON.stringify({ error: 'title is required' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } const post = await createPost(body); return new Response(JSON.stringify(post), { status: 201 }); };
- Use
to read the rawcontext.request
object. Parse the body based on content type:Request
// JSON body const data = await request.json(); // Form data const formData = await request.formData(); const email = formData.get('email') as string; // Raw text const text = await request.text(); // Request headers const auth = request.headers.get('authorization');
- Read and set cookies via
:context.cookies
export const GET: APIRoute = async ({ cookies }) => { const token = cookies.get('session')?.value; if (!token) return new Response(null, { status: 401 }); cookies.set('last-visit', new Date().toISOString(), { httpOnly: true, secure: true, maxAge: 60 * 60 * 24 * 7, // 1 week path: '/', }); return new Response('OK'); };
- Create middleware at
to run logic before every request. Usesrc/middleware.ts
and calldefineMiddleware
to continue the chain:next()
// src/middleware.ts import { defineMiddleware } from 'astro:middleware'; export const onRequest = defineMiddleware(async (context, next) => { const token = context.cookies.get('session')?.value; context.locals.user = token ? await validateToken(token) : null; // Block unauthenticated access to /dashboard if (context.url.pathname.startsWith('/dashboard') && !context.locals.user) { return context.redirect('/login'); } return next(); // proceed to page/endpoint handler });
- Chain multiple middleware functions with
fromsequence()
:astro:middleware
import { defineMiddleware, sequence } from 'astro:middleware'; const auth = defineMiddleware(async (ctx, next) => { /* ... */ return next(); }); const logging = defineMiddleware(async (ctx, next) => { console.log(`${ctx.request.method} ${ctx.url.pathname}`); return next(); }); export const onRequest = sequence(logging, auth);
- Add types for
to get IntelliSense in middleware and pages. Augment thelocals
interface inApp.Locals
:env.d.ts
// src/env.d.ts /// <reference types="astro/client" /> interface Locals { user: { id: string; email: string } | null; }
- In SSG mode, endpoints with
handlers can generate static files (JSON, XML, RSS). ExportGET
from a dynamic endpoint file to generate multiple static output files.getStaticPaths()
Details
Astro endpoints use the standard Web
Request/Response API — the same API available in Cloudflare Workers, Deno, and modern Node.js. This means your endpoint logic is portable across runtimes without an adapter-specific API.
SSG vs. SSR endpoints:
In SSG mode, only
GET handlers are useful. The build calls each GET handler and writes the response body to a static file. A src/pages/feed.xml.ts with export const GET produces a static dist/feed.xml.
In SSR mode, all HTTP methods work and endpoints are invoked on every request. This is where POST, PUT, DELETE, and PATCH handlers are useful.
per endpoint:prerender
In
output: 'hybrid' mode, endpoints are server-rendered by default. Add export const prerender = true to opt a specific endpoint into static generation. In output: 'server' mode, endpoints are server-rendered by default; add export const prerender = true to force static output.
Error handling:
Always return a
Response — never throw from an endpoint. If you need to return an error, construct a Response with the appropriate status code. Unhandled throws will produce a 500 with an Astro error page.
— the middleware/page contract:locals
context.locals is a mutable plain object scoped to the current request. Set values in middleware, read them in pages and endpoints. This is the correct way to pass auth state, tenant context, or feature flags from middleware to handlers.
Rate limiting and edge cases:
Astro has no built-in rate limiting. Implement rate limiting in middleware using a memory store (for single-instance) or an external store (Redis, KV for edge). Check
context.clientAddress for the caller's IP — this is populated correctly when behind a reverse proxy if the adapter is configured for it.
Source
https://docs.astro.build/en/guides/endpoints
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.