Edit this page

Introduction

Aleph is a trusted, declarative language for server-side applications. You write the business logic in TypeScript: routes, access rules, data, queries, background work, and integrations. Aleph compiles it into a manifest, a versioned document that describes the backend instead of being a program, and a platform runs that manifest for you.

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

const a = app("todos");

const Todo = a.entity("Todo", {
  key: ["ownerId", "todoId"],
  fields: {
    ownerId: t.string().max(64),
    todoId: t.id().generated(),
    title: t.string().min(1).max(200),
    done: t.bool().default(false),
  },
});

a.route("POST /todos")
  .body({ title: t.string().min(1).max(200) })
  .status(201)
  .handle(($, { body }) => Todo.insert({ ownerId: $.caller, title: body.title }));

a.route("GET /todos")
  .handle(($) => Todo.query({ prefix: [$.caller], limit: 50 }));

export default a;

It reads like ordinary TypeScript, but the handlers don't run per request. They run once, at build time, and record a plan of built-in operations. aleph check then reports each route's worst case: GET /todos reads at most 50 rows, because limit: 50 bounds it.

Why declarative

A program can do anything at run time, so nobody knows in advance how much a request will read, write, or compute. A description can be read before it runs. Everything else follows from that:

  • Known usage before deploy. Because a route is a fixed plan, not a program, the checker can count the most it can ever read, write, compute, call, and allocate, from your string maximums, query limits, and list sizes. A route that could go over a limit is rejected with a diagnostic that names the line. A runtime meter enforces the same bounds while it runs. See Bounded by construction.
  • Safe to share, so cheaper to run. No request can run away with the CPU, memory, or the database, and no app can name another's keys. So many apps can share the same processes and database, instead of each getting its own isolated runtime (a Lambda function, a container, or a VPS) and database, mostly idle. See Shared infrastructure and isolation.
  • Correct by default. Every request is one serializable transaction. Emails, jobs, and events go out exactly once, after commit. Idempotent routes replay their response for a repeated key.
  • Nothing to operate. Storage layout, retries, the outbox, and scaling are the runtime's job. There are no Dockerfiles, queues to provision, or deploy pipelines.
  • One source for everything. The same declarations produce input validation, response types, a typed client, OpenAPI, MCP tools for agents, and tests from worked examples.
  • Built for agents. The vocabulary is small and closed, and every mistake comes back as a precise diagnostic, so a coding agent can fix its own errors.

What you give up

  • No arbitrary code in requests. You compose built-in operations; the outside world is reached through declared integrations.
  • No unbounded loops or scans. Loops run over lists with a known maximum, or declare their own ($.loop). Work over all your data becomes a schedule that walks it in batches.
  • Per-request limits. A route reads at most 250 rows and writes at most 50; bigger work moves to jobs and workflows. See Limits.

These constraints are what buy the guarantees, and most application backends fit inside them.

How it works

app.ts ──@aleph-lang/sdk──▶ manifest.json ──aleph-core──▶ validated plan ──▶ execute(tenant, request)
                                       (types, scopes,               (one optimistic transaction,
                                        worst-case usage)             metered, tenant-prefixed keys)
  1. The SDK records. Handlers run once at build time with symbolic values. Task.get(...) appends a get operation and returns an accessor; $.when(...) records a branch; list.map(...) records a bounded loop. Control flow uses $ helpers, not native if or for.
  2. The manifest is the program. Versioned JSON listing entities, routes, handlers, workflows, and their plans, built from a closed set of operations. No author code ships. See Manifest format.
  3. The core checks. aleph-core, a Rust library, resolves every reference, infers types (including nullability and string lengths), and computes worst-case usage: sequences add, branches take the maximum, loops multiply.
  4. The core runs. Each request is one optimistic, serializable transaction, metered on every read, write, and operation. Effects go to a transactional outbox, and every key is built inside the tenant's keyspace. The same core compiles to WebAssembly, so aleph dev and aleph test run exactly what production runs.

Static bounds and the runtime meter are two independent accounts of the same usage, and every full check run (pnpm verify) confirms they agree on every example and on 100,000 randomly generated plans.

Next