---
title: Functions and "use server"
description: Endpoints in a folder, and functions written in a page that run on the server.
section: Server
order: 1
---

# Functions and "use server"

<p class="lead">Server code in Sluurp is JavaScript or TypeScript that runs in a sandbox as the person who called it, so every rule still applies.</p>

## "use server"

Write a function in a page and mark it with `"use server"`; the browser calls it like any other function. In the copy of the page sent to the browser, the function's body is replaced by a call to `/api/rpc`.

```ts title="islands/add-note.tsx"
export async function addNote(title: string, at: Date) {
  "use server";

  // who may call it; without it, anyone
  "allow: @request.auth.staff = true";
  const made = await server.collection("notes").create({ title });
  return { made, next: new Date(at.getTime() + 86_400_000), tags: new Set(["new"]) };
}

// in the browser
// a Date and a Set, as themselves
const { next, tags } = await addNote("Milk", new Date());
```

- Arguments and return values travel as [devalue](https://github.com/sveltejs/devalue), so Dates, Maps, Sets, BigInts, `undefined` and repeated objects arrive intact.
- Without `"allow: …"`, anyone may call it. It acts as the caller, so each collection's rules still decide what it can read and write.
- When a server component calls it during rendering, it runs in place, with no request at all.
- The body runs on its own, without the rest of its file, so names the page imports at the top aren't available inside it.

## Endpoints

A file in `functions/` becomes an endpoint at `/api/fn/<name>`. It is versioned with the app, and rolls back with it.

```ts title="functions/register-summary.ts"
// the app's own modules
import { average } from "../lib/marks.ts";
import terms from "../data/terms.json" with { type: "json" };

export const rule = "@request.auth.id != null";
export const method = "GET";

export default async function (ctx) {
  const page = await ctx.collection("grades").list({ filter: `student = "${ctx.auth.id}"` });
  return { average: average(page.items.map((g) => g.value)), term: terms.autumn };
}
```

`export const runAs = "system"` lets a function read past the caller's rules, for totals anybody may know that nobody may read row by row. The function is then the policy, so it should return counts, never rows.

## Hooks and jobs

A file in `hooks/` runs before every write to its collection, however the write was made. Its reads bypass the rules, because it is policy rather than user code. That lets it enforce what a rule can't, such as a count or a comparison across rows.

```ts title="hooks/classes.ts"
export async function beforeCreate({ record, db, reject }) {
  const held = await db.count("classes", `responsible = "${record.responsible}"`);
  if (held >= 2) reject(`already responsible for ${held} classes`);

  // the record to write
  return { ...record, name: record.name.trim() };
}
```

```ts title="jobs/nightly.ts"
export const schedule = "0 2 * * *";
export default async function (ctx) { /* … */ }
```

## The sandbox

Each call gets a fresh QuickJS context with a memory ceiling, a deadline, the web platform's globals (`URL`, `TextEncoder`, `crypto.randomUUID`…) and `ctx`. There is no file system, no network and no `process`. Imports of the app's own files are bundled in before the call starts.
