Edit this page

Quickstart

In five minutes: write a small backend, see its worst-case usage, add an example that doubles as a test, and serve it locally.

Install

Aleph is two npm packages: the SDK you write apps with, and the CLI that checks, tests, and serves them. The CLI brings the WebAssembly build of the core with it, so there's nothing else to install.

npm install @aleph-lang/sdk
npm install --save-dev @aleph-lang/cli

Aleph apps are ES modules. New npm projects say "type": "commonjs" in package.json, so switch yours to modules:

npm pkg set type=module

Requirements:

  • Node.js 24 or newer. The CLI loads app.ts with Node's built-in TypeScript support, so there's no separate build step.
  • TypeScript 5.8 or newer for editor support. The SDK's types do much of the checking as you type.

From the repository

git clone https://github.com/gubkin-labs/aleph.git
cd aleph
pnpm install
pnpm build

The aleph command is then available from packages/cli (pnpm --filter @aleph-lang/cli exec aleph --help), and the example apps under examples/ use the packages directly. Building from source also needs a Rust toolchain with the wasm32-unknown-unknown target and wasm-bindgen-cli; see Contributing.

Check your setup

npx aleph --help

1. Describe the app

Create app.ts:

import { app, err, t } from "@aleph-lang/sdk";

const a = app("notes");

const Note = a.entity("Note", {
  key: ["ownerId", "noteId"],
  fields: {
    ownerId: t.string().max(64),
    noteId: t.id().generated(),
    text: t.string().min(1).max(500),
    pinned: t.bool().default(false),
  },
});

a.route("POST /notes")
  .body({ text: t.string().min(1).max(500) })
  .status(201)
  .example({
    name: "creates a note",
    as: { userId: "ada" },
    request: { body: { text: "  hello  " } },
    expect: { status: 201, body: { text: "hello", pinned: false } },
  })
  .handle(($, { body }) => Note.insert({ ownerId: $.caller, text: body.text.trim() }));

a.route("GET /notes")
  .query({ cursor: t.cursor() })
  .handle(($, { query }) => Note.page({ prefix: [$.caller], limit: 20, after: query.cursor }));

a.route("POST /notes/:noteId/pin")
  .params({ noteId: t.id() })
  .errors({ missing: err.notFound("No such note") })
  .handle(($, { params, errors }) =>
    Note.getOrFail({ ownerId: $.caller, noteId: params.noteId }, errors.missing).update({ pinned: true }),
  );

export default a;

Routes default to signed-in callers, and $.caller is the user's id. Every string has a maximum length: that's what lets Aleph bound the size of a row.

2. Check it

npx aleph check app.ts
✔ valid. The most each route and handler can use in one run:

route                     reads   writes         ops    bytes read   effects   calls         memory
limit                   250        50      2,000          2 MB          50       3         8 MB
get_notes                20  8%     0  –       6 <1%   67.6 KB  3%       0 –     0 –   285.6 KB  3%
post_notes                1 <1%     1 2%       7 <1%    3.4 KB <1%       0 –     0 –    13.7 KB <1%
post_notes_noteId_pin     2 <1%     1 2%      16 <1%    6.8 KB <1%       0 –     0 –    21.9 KB <1%

Each row is the most that route can ever use in one request, whatever the input or data. The limit row shows the per-request limits, and the percentage is the share of each one: – means none, and <1% means a little. get_notes reads at most 20 rows because of its limit: 20. Try changing the GET route's limit: 20 to limit: 5000, and the check fails with a diagnostic explaining which bound to lower.

3. Test it

npx aleph test app.ts
✔ post_notes: creates a note

1 passed, 0 failed

Each example runs on a fresh in-memory store with fixed time and ids, and also checks that the request stayed within its static bounds. See Testing with examples.

4. Serve it

npx aleph dev app.ts --watch

Authentication is faked with headers in development:

curl -X POST localhost:3000/notes -H 'x-aleph-user: ada' -H 'content-type: application/json' -d '{"text":"first"}'
curl localhost:3000/notes -H 'x-aleph-user: ada'

Every response carries an x-aleph-usage header with what the request actually used.

5. Generate a typed client

npx aleph client app.ts -o client.ts
import { createClient } from "./client";

const api = createClient({ baseUrl: "http://127.0.0.1:3000", headers: () => ({ "x-aleph-user": "ada" }) });
const note = await api.postNotes({ text: "typed" }); // note.pinned: boolean

Next