# Endpoints and Server Data

Course: [Astro](https://www.devtutorials.ai/learn/astro) · Section: Server Development
Source: https://www.devtutorials.ai/learn/astro/server/endpoints

> 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.

<p>My posts are also at <a href="/api/posts.json">/api/posts.json</a>.</p>
`,
  }}
/>

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

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

### [TS] Creating an Endpoint as a .ts File

```ts
// 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.

<form method="post" action="/api/contact">
    <input name="name" value="Ravi" />
    <select name="topic">
      <option>Astro</option>
      <option>CSS</option>
    </select>
    <button>Send</button>
</form>
`,
  }}
/>

- 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`.

<button id="like">♥ Like</button>
<button id="bad">Send a bad request</button>
<p id="out">Likes: …</p>

<script>
    const out = document.getElementById("out")!;

    async function show(res: Response) {
      const data = await res.json();
      out.textContent = res.status + ": " + JSON.stringify(data);
    }

    fetch("/api/likes").then(show);

    document.getElementById("like")!.addEventListener("click", async () => {
      show(await fetch("/api/likes", { method: "POST", body: JSON.stringify({ amount: 1 }) }));
    });
    document.getElementById("bad")!.addEventListener("click", async () => {
      show(await fetch("/api/likes", { method: "POST", body: JSON.stringify({ amount: "lots" }) }));
    });
</script>
`,
  }}
/>

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

```bash
npm install @astrojs/rss
```

```ts
// 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.

```ts
// 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)");
```

```ts
// 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

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Reading `request.json()` in a static endpoint | Static endpoints get requests | Use `prerender = false` for bodies | The body is empty at build time |
| Lower-case `get` | Any name works | Export `GET` in capitals | `405 Method Not Allowed` |
| Returning an object | Astro turns it into JSON | Return `Response.json(data)` | Error: not a Response |
| No status for bad input | 200 is fine for errors | Return `400` with a message | Callers think it worked |
| User input in SQL text | Strings are safe | Use `?` placeholders or an ORM | SQL injection |
| Feed with no `site` | Relative links work in feeds | Set `site` in the config | Broken links in feed readers |

</div>

## 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.
