Start here

From nothing to a working app

Seven steps. Each one runs right here against a real backend, so you can see what it does before you build anything.

1

Get a backend

perusta has two halves. The client runs in the browser and holds no secrets; the server holds your storage credentials. You need somewhere for the server half to live.

The fastest route is the Deploy button — it creates the storage for you and you paste nothing. Two things to know before you click: the Upstash step makes you choose a plan and none of them are free, and on a Pro account the finished deploy answers 401 until you attach a domain or turn off Deployment Protection. Both are covered in what the button does.

Or run it locally first if you would rather not deploy anything yet.

Locally:

git clone https://github.com/miikakuisma/perusta
cd perusta && pnpm install && pnpm build
pnpm --filter @perusta/example-drive-vanilla start

That runs entirely in memory with nothing provisioned, so it works on a fresh clone.

2

Point the client at it

If your page is served from the same deployment, there is nothing to configure — that is the common case and the default.

<script src="https://unpkg.com/perusta"></script>
<script type="module">
  // `perusta` is now a global. That is the whole setup.
</script>

With a bundler, or to point somewhere else:

import { createClient } from 'perusta';

const cloud = createClient();                              // same origin
const cloud = createClient({ origin: 'https://api.you.dev' }); // elsewhere

3

Know who someone is

Everything is scoped to a user, so this comes first. ensureUser signs somebody in anonymously if they have no session — no signup screen, no friction, and they get a real id straight away.

Run it. You will get a fresh identity.

Editor
Output
Press Run.
Network — real requests to this site’s own backend
No requests yet.

4

Save something

Two places to put things. kv is for small values you look up by name — a setting, a counter, a bit of state. fs is for files, with real folders.

Change the text and run it again. It persists, because it is going to actual storage.

Editor
Output
Press Run.
Network — real requests to this site’s own backend
No requests yet.

5

Read it back

Nothing was configured between those two steps, and no key went anywhere near the browser. Reload this page and run it again — it is still there.

Editor
Output
Press Run.
Network — real requests to this site’s own backend
No requests yet.

6

Add a model

Same shape. The gateway key stays on your server, and the reply streams back token by token.

Editor
Output
Press Run.
Network — real requests to this site’s own backend
No requests yet.

7

Run your own code

Some things do not belong in a browser: an API key, a charge, anything you cannot trust the client to do honestly. Define a function on the server and call it from the page — it receives the caller already signed in, with ctx.fs and ctx.kv already scoped to them.

// server
functions: {
  async publish(ctx, { slug }) {
    const draft = await ctx.fs.readText(`drafts/${slug}.md`);
    await ctx.fs.write(`published/${slug}.md`, draft);
    return { url: `/p/${slug}` };
  },
}

The two below are real functions on this site’s server. Run them.

Editor
Output
Press Run.
Network — real requests to this site’s own backend
No requests yet.

That is the whole API

Now build something small

You have seen every namespace. The rest is detail you can look up when you need it — the recipes are complete little apps you can paste into an HTML file, and the reference pages cover each method properly.