Endpoints and Server Data

By Syncfusion®, Inc. · Last updated

What endpoints are, .js and .ts endpoint files, HTTP method functions, reading JSON and form data, returning JSON and status codes, static and on-demand endpoints, the APIRoute type, RSS feeds, databases in server code, and advanced routing with src/fetch.ts.

Endpoints

  • A page answers with HTML. Sometimes you need to answer with data instead: JSON, an RSS feed, or plain text.
  • An endpoint is a .js or .ts file in src/pages that returns a Response instead of HTML.
  • Scripts, apps and other websites can read it, like any API.
  • In this lesson, Asha builds a small API for her blog.

Simple lesson: a page returns HTML, an endpoint returns data.

A Real-World Example

  • Asha's friend wants to show Asha's latest posts on his own site.
  • He does not want to copy HTML. He wants a list of posts as JSON.
  • Asha adds src/pages/api/posts.json.ts. It returns the list at /api/posts.json.

Press ▶ Run. Then pick /api/posts.json in the address bar.

What happened:

  • The file name posts.json.ts became the address /api/posts.json. The .ts is dropped.
  • The GET function ran during the build and returned a Response.
  • The Terminal shows "static endpoint". The JSON file was built like a page.

What You Will Learn

In this lesson, you will learn:

  1. How to write an endpoint in JavaScript or TypeScript.
  2. What the HTTP method functions are.
  3. How to read JSON and form data.
  4. How to return JSON with the right status code.
  5. Static endpoints vs on-demand endpoints.
  6. How to build an RSS feed.
  7. Where database code belongs.

Endpoint Basics

What Endpoints Are

  • Endpoint = a route file that exports functions named after HTTP methods.
  • HTTP method = the kind of request: GET (read), POST (send), PUT or PATCH (change), DELETE (remove).
  • Each function receives a context and returns a Response.

[JS] Creating an Endpoint as a .js File

// src/pages/api/hello.json.js
export function GET() {
  return Response.json({ message: "Hello" });
}

[TS] Creating an Endpoint as a .ts File

// src/pages/api/hello.json.ts
import type { APIRoute } from "astro";

export const GET: APIRoute = () => {
  return Response.json({ message: "Hello" });
};
  • APIRoute tells your editor what the context holds and what to return.

HTTP Method Functions

  • Export one function per method: GET, POST, PUT, PATCH, DELETE.
  • ALL answers any method that has no function of its own.
  • A request with a method you did not export gets a 405 Method Not Allowed.

Method names are written in capitals: GET, not get. Astro looks for those exact names.

The Context

The function's argument has the same tools as Astro in a page:

  • params = values from [name] in the file name.
  • request = the request: method, headers, body.
  • url = the full address, with searchParams.
  • cookies, locals and redirect() = the same as in pages.

Simple lesson: export a function per HTTP method. It gets a context and returns a Response.

Handling Data

Reading JSON and Form Data

  • await request.json() = the body, when the sender sent JSON.
  • await request.formData() = the fields, when a form sent the request.
  • Both only work in on-demand endpoints. A static endpoint is built before any request has a body.

Press ▶ Run. Fill the form and send it. The endpoint reads the form fields and answers with JSON.

  • The browser sent the fields as form data. formData() read them by their name.

Returning JSON and Status Codes

  • Response.json(data, { status: 201 }) returns JSON with a status, in one line.
  • Common status codes:

    • 200 OK, 201 Created,
    • 400 Bad Request (the input is wrong),
    • 404 Not Found, 405 Method Not Allowed,
    • 500 Server Error.
  • Always check the input, and answer 400 when it is wrong.

Press ▶ Run. Click both buttons. The endpoint counts likes, and refuses a bad request with 400.

In this lab, likes lives in a variable, so it resets on each Run. A real server can restart at any time, and serverless hosts start a fresh copy for many requests. Keep real data in a database.

Simple lesson: read the body with request.json() or formData(), check it, and answer with a fitting status.

Endpoints and Rendering Modes

Static Endpoints

  • By default, an endpoint is static, like a page.
  • Its GET runs once at build time. The result is saved as a file, such as dist/api/posts.json.
  • Great for data that changes only when you rebuild: feeds, search indexes, sitemaps.
  • A static endpoint with [param] needs getStaticPaths(), like a page.

On-Demand Endpoints

  • export const prerender = false; makes it run on every request.
  • Needed for POST, for reading cookies or headers, and for fresh data.
  • Needs an adapter, like on-demand pages.

[TS] The APIRoute Type

  • APIRoute types the context: params, request, url, cookies, locals.
  • Use APIRoute<{ id: string }> style types only when you need them. The plain type is enough for most endpoints.

Press ▶ Run. One endpoint file makes one JSON file per post, at build time.

Simple lesson: endpoints are static by default. Make them on-demand when they need the request.

RSS Feeds

What an RSS Feed Is

  • RSS feed = an XML file that lists your newest posts.
  • Feed readers, like Feedly, check it and tell readers when you post something new.

Creating an RSS Feed with @astrojs/rss

npm install @astrojs/rss
// src/pages/rss.xml.ts
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";

export async function GET(context) {
  const posts = await getCollection("blog");
  return rss({
    title: "Asha's Dev Journal",
    description: "Notes from my coding journey",
    site: context.site,
    items: posts.map((post) => ({
      title: post.data.title,
      pubDate: post.data.pubDate,
      link: "/blog/" + post.id + "/",
    })),
  });
}
  • context.site comes from site in astro.config.mjs. Feeds need full links, so set it.

Generating the Feed from a Collection

  • The feed is a static endpoint, so it is rebuilt with the site.
  • Add a link in your layout's <head> so browsers can find it: <link rel="alternate" type="application/rss+xml" href="/rss.xml" />.

The lab cannot install @astrojs/rss, so this lab writes the same XML by hand. Press ▶ Run and open /rss.xml.

Simple lesson: an RSS feed is a static endpoint. Use @astrojs/rss on your computer, and set site.

Databases and Advanced Routing

Where Database Code Belongs

  • Database code needs secrets and a connection, so it must run on the server.
  • Put it in endpoints, actions, middleware or the script of on-demand pages.
  • Keep the connection in one file, such as src/lib/db.ts, and import it where needed.
// src/lib/db.ts (Node.js 22.5+)
import { DatabaseSync } from "node:sqlite";

export const db = new DatabaseSync("journal.db");
db.exec("CREATE TABLE IF NOT EXISTS likes (slug TEXT PRIMARY KEY, count INTEGER)");
// src/pages/api/likes/[slug].ts
import { db } from "../../../lib/db";

export const prerender = false;

export function GET({ params }) {
  const row = db.prepare("SELECT count FROM likes WHERE slug = ?").get(params.slug);
  return Response.json({ likes: row?.count ?? 0 });
}
  • Always pass values with ? placeholders. Never join user input into the SQL text. That stops SQL injection attacks.
  • For bigger projects, Drizzle ORM gives you typed queries with the same safety.

The Reserved src/fetch.ts File (Advanced)

  • Astro 7 lets you replace the whole request pipeline with one src/fetch.ts file. This is called advanced routing.
  • It is for special cases, such as custom routers or framework bridges. Most sites never need it.
  • fetchFile in the config changes the file name.

Start with normal pages and endpoints. Reach for advanced routing only when you have a clear need the file-based routes cannot meet.

Simple lesson: database code runs on the server, in endpoints and actions. Use placeholders for user input.

TL;DR

  1. Endpoint = a .js or .ts file in src/pages that returns a Response.
  2. Address = file path without .ts: api/posts.json.ts → /api/posts.json.
  3. Methods = export GET, POST, PUT, PATCH, DELETE, or ALL.
  4. Context = params, request, url, cookies, locals, redirect().
  5. Body = await request.json() or await request.formData() (on-demand only).
  6. Answer = Response.json(data, { status }). 400 for bad input.
  7. Static endpoint = built once (default). On-demand = prerender = false.
  8. RSS = a static endpoint with @astrojs/rss. Set site.
  9. Databases = server code only. Use ? placeholders.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Reading request.json() in a static endpointStatic endpoints get requestsUse prerender = false for bodiesThe body is empty at build time
Lower-case getAny name worksExport GET in capitals405 Method Not Allowed
Returning an objectAstro turns it into JSONReturn Response.json(data)Error: not a Response
No status for bad input200 is fine for errorsReturn 400 with a messageCallers think it worked
User input in SQL textStrings are safeUse ? placeholders or an ORMSQL injection
Feed with no siteRelative links work in feedsSet site in the configBroken links in feed readers

Check Yourself

Exercises

What to Remember

  • An endpoint returns a Response. Its address is its file path without .ts.
  • Export GET, POST and the other methods. Use Response.json() and fitting status codes.
  • Endpoints are static by default. Bodies, cookies and fresh data need prerender = false.
  • RSS feeds are static endpoints. Database code runs on the server, with placeholders.

Next: Middleware and Cookies, where code runs before every request.