Alepha is a full-stack TypeScript ecosystem. One small surface of typed primitives covers the server, the database, auth, background work and React, so a weekend project and a distributed system are the same code with different infrastructure underneath.

>_npx alepha@latest init my-app

One definition. Both sides.

The database column, the HTTP response and the React prop are the same type. No codegen step, no generated client, no shared types package to keep in sync.

Backend
src/Api.ts
 1import { z } from "alepha"; 2import { $entity, $repository, db } from "alepha/orm"; 3import { $action } from "alepha/server"; 4  5const taskEntity = $entity({ 6  name: "tasks", 7  schema: z.object({ 8    id: db.primaryKey(), 9    title: z.text(),10    done: z.boolean({ default: false }),11  }),12});13 14export class Api {15  tasks = $repository(taskEntity);16 17  list = $action({18    schema: { response: z.array(taskEntity.schema) },19    handler: () => this.tasks.findMany({ limit: 20 }),20  });21}
Frontend
src/AppRouter.tsx
 1import { $page } from "alepha/react/router"; 2import { $client } from "alepha/server/links"; 3import type { Api } from "./Api.ts"; 4  5export class AppRouter { 6  api = $client<Api>(); 7  8  home = $page({ 9    path: "/",10    loader: async () => ({11      tasks: await this.api.list(),12    }),13    component: (props) => (14      <ul>15        {props.tasks.map((task) => (16          <li key={task.id}>{task.title}</li>17        ))}18      </ul>19    ),20  });21}

Rename title in the entity and the React component stops compiling. That is the whole contract.

Same code. Anywhere you run it.

Not just the HTTP layer. Your database, cache, queues, cron, storage and WebSockets all resolve to whatever the platform provides, chosen at build time rather than written into your app.

Free to start, nothing to manage.

Create a Cloudflare account and your full-stack app is live in seconds. No configuration, no server to keep running, and the database, cache, queues and cron are all Cloudflare's problem rather than yours. It is the easiest way to run an Alepha app, and the one you can stop thinking about.

You write thisIt runs on this
$entityD1 / Hyperdrive
$job({ cron })Cron Triggers
$job.push()Cloudflare Queues
$topicDurable Objects
$emailCloudflare Email

The left column is your source code. It is byte for byte identical on all three. Nothing is ported, nothing is conditionally imported.

>_alepha build --target cloudflare

One dependency

Twenty packages means twenty changelogs, twenty release cadences and every breaking change landing on a different Tuesday. The glue between them is yours to keep working.

package.json20+ dependencies
{
"dependencies": {
"@aws-sdk/client-s3": "^3.744.0",
"@trpc/client": "^11.0.0",
"@trpc/server": "^11.0.0",
"better-auth": "^1.2.7",
"bullmq": "^5.34.10",
"drizzle-kit": "^0.30.4",
"drizzle-orm": "^0.39.3",
"eslint": "^9.20.1",
"express": "^4.21.2",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
"multer": "^1.4.5-lts.1",
"node-cron": "^3.0.3",
"nodemailer": "^6.10.0",
"pino": "^9.6.0",
"prettier": "^3.5.1",
"socket.io": "^4.8.1",
"vite": "^6.1.0",
"vitest": "^3.0.5",
"zod": "^3.24.2"
}
}
package.json1 dependency
{
"dependencies": {
"alepha": "^1.0.0"
}
}
One version to bump. One changelog to read.

Built on what already works

Alepha does not reinvent the load-bearing parts. It rewrites everything between them, so the pieces you already trust stop needing glue.

Your app$entity · $action · $page · $job
Alephaserver · orm · auth · queues · cron · storage · SSR
The runtimenode:http · Bun.serve · Workers fetch

There is no Express or Fastify in the stack. The HTTP server is whatever the runtime already provides, and Alepha picks the one that matches where you deployed. Hover a brick to see what it does here.

Built for the thing writing your code

An agent opens this repository and reads the current API instead of recalling it: one prefix to write with, one command to check itself, and a failure that names the line.

It reads, it does not remember

AGENTS.md
## Documentation

- Framework source: `node_modules/alepha/src/`
- Docs: https://alepha.dev/llms.txt

alepha init writes the file. The index is rebuilt on every deploy and the framework's own source ships inside the package, so the model reads today's API instead of the one baked into its weights.

It turns itself up

$ alepha verify
[23:41:02] DEBUG CLI <alepha.core.Alepha>: ready OK [0.0ms]
[23:41:03] INFO  CLI <alepha.cli.Lint>: biome OK [1.2s]
[23:41:06] INFO  CLI <alepha.cli.Types>: tsc OK [3.4s]

No flag was passed. CLAUDECODE in the environment switches the logs to full trace and streams every sub-process live.

One prefix, 82 primitives

$
and 75 more, every one of them $

Every capability is a $ export with the same shape. There is no second convention waiting to be discovered.

Nothing merges on a guess

src/Api.ts:14:3 - error TS2322
Type 'string' is not assignable to
type 'boolean'.

  14 |   done: "yes",
     |   ~~~~

Lint, types, tests and build behind one command, there since the first commit.

Swap anything. Even time.

Nothing in the framework is sealed. Every provider is a class in the container, so a test replaces the one it does not want and leaves the rest running for real.

tasks.spec.ts
 1const alepha = Alepha.create() 2  .with({ provide: EmailProvider, use: MemoryEmailProvider }); 3  4const email = alepha.inject(MemoryEmailProvider); 5const time = alepha.inject(DateTimeProvider); 6await alepha.start(); 7  8await time.travel([1, "day"]); 9 10expect(email.records).toHaveLength(1);

One line replaced the mail server. There is no module registry to patch, no hoisting order to get right, and nothing to reset between tests: the container is new each time. No vi.mock.

  1. 09:00
    alepha.start()

    The container resolves. Every provider is the real one, except the ones you swapped.

  2. 09:00
    travel([1, 'day'])

    The clock moves a day forward. The test does not wait, and nothing sleeps.

  3. next day
    $job fires

    Cron is anchored to the same clock, so the scheduled job runs during the jump.

  4. next day
    records: 1

    The mail never left the process. It is a row in an array you can assert on.

Fourteen in-memory providers ship with the framework, covering the filesystem, the shell, queues, topics, locks, mail, SMS, payments, captchas, file storage and the clock. Anything they do not cover is still a class, so it is still substitutable.

An admin panel you did not build

One flag on init and this is already there. Turn on a module and its screens appear on their own: users, sessions, API keys, jobs, notifications, audits, files, parameters, payments and workflows.

Not a template you fork and then maintain, but modules that keep getting updates with the framework.

Every account with its roles and status, searchable, filterable and paginated.

Deploy with one command

The database, the bucket and the queue do not exist yet. You are not going to create them, and you are not going to write the pipeline that does.

alepha.config.ts
 1export default defineConfig({ 2  plugins: [ 3    platform({ 4      environments: { 5        production: { 6          adapter: "cloudflare", 7          domain: "lore.alepha.dev", 8        }, 9        staging: {10          adapter: "bay",11          host: "deploy@bay.example.com",12        },13      },14    }),15  ],16});
terminal
$alepha platform up --env production
  1. authenticating
  2. provisioning
  3. building
  4. running migrations
  5. deploying
  6. pushing secrets

https://lore.alepha.dev

Missing infrastructure is created during provisioning: D1, KV, R2 and Queues, from the bindings your code already declares, because Alepha knows the topology of your app. The command handles the rest of it too, from pushing secrets to running migrations. Alepha Platform targets Cloudflare and Bay (VPS) only.

Try it in one command

>_npx alepha@latest init my-app

100% open source, 100% MIT. The framework, Lore and Bay all live in one public repository and ship on the same version, so every release is proved by applications that use it. Nothing here is a paid tier of something else.

ready
mainTypeScript
UTF-8home.md