The .NET One Person Framework — build, run, and ship a whole product solo, in C#, on one server. C# components render server-side over WebSockets or on WebAssembly, or host a typed TypeScript SPA (React, Vue, Angular, Svelte, Solid, Preact, Lit); SQLite-first data, auth, jobs, mail, cache, CQRS & one-command deploy.
31
stars
946
commits
C#
primary language
Sep 4, 2026
updated
Site ↗ · Docs ↗ · Playground ↗
You write components as plain C# classes that return a tree of HTML from Render(). State is a field, an
event handler is a delegate, and the component re-renders itself — no .razor, no JSX, no JavaScript,
nothing to write in another language:
[Route("/counter")]
public sealed partial class Counter : Component
{
private int _count;
protected override Component? Render() =>
[
H1["Counter"],
P[$"Current count: {_count}"],
Button.OnClick(() => _count++)["Click me"]
];
}
Rask is the .NET One Person Framework. One developer builds, runs and ships a complete product — the UI, the data, the auth, the background work and the deployment — from one C# codebase on one server, with SQLite as the production database. No PaaS to rent, no stack of services to glue, no second language to context-switch into. The same component runs two ways: server-rendered with live updates over a WebSocket, or fully client-side on WebAssembly as an installable offline PWA.
An empty folder to a live, HTTPS, database-backed product, by one person in one sitting:
curl -sSL https://pal-tamas.github.io/rask/rask.sh | sh # the CLI, and everything it shells out to
rask new Shop --auth # scaffold: the whole stack + a cookie login
# …write a Products slice — docs/tutorial/02-first-feature.md has the code
rask db add InitialCreate && rask db update # create + apply the SQLite migration
rask dev # run it, hot-reloading, at /products
rask deploy --host root@box --domain shop.example.com # ship it: bare box → Docker + auto-HTTPS, zero-downtime
Every step is a first-party command, and every stateful pillar it touches — auth, jobs, mail, cache,
events — rides the app's own SQLite database. Run rask with no arguments for a wizard.
Prerequisites: none. The installer adds whatever is missing — the .NET 10 SDK, the
wasm-toolsworkload that browser bundles need, Node for the SPA templates — all under$HOME, nosudo. On Windows:irm https://pal-tamas.github.io/rask/rask.ps1 | iex. Already have the .NET 10 SDK and want only the tool?dotnet tool install -g Rask.Cli. Options, install locations and uninstall: docs/installation.md.
Want the pages to run in the browser too? rask new Shop --wasm publishes a WebAssembly bundle from
that same project, and a page that can run client-side moves there once it has downloaded — no second
project, no separate build. Until then, and for any page that reaches a database, it stays live over the
socket. See render modes.
Prefer React? rask new Shop --template react scaffolds a Vite client on an ASP.NET host, with the
front end's TypeScript generated from your C# message records on every build — so
await rask.dispatch(getOrder({ id })) is typed, and renaming a property breaks the build rather
than the wire. The client is a TypeScript SPA — React, Preact, Vue, Angular, Solid, Svelte or Lit, but not JavaScript, since
every guarantee here is one a compiler makes. See docs/spa.md. (Needs Node.js.)
Want React inside a Rask app rather than instead of one? Derive a component from ReactComponent
and drop a Chart.tsx beside it — it becomes an ordinary component you place anywhere the chain goes,
a leaf in a card or a whole route. Props are declared in C# and checked in both directions, callbacks
re-enter C# over the channel every handler already uses, and the live diff leaves the subtree alone
because its own renderer owns it. LitComponent pairs with a .ts the same way. Rename a C# property
and the front end stops compiling. Needs Node, because your React does. See
docs/external-components.md.
Pick one host package per project, then add what you need. Everything below targets .NET 10 and is trim/AOT-safe.
Rask.Server and Rask.Wasm pull in Rask.Core, Rask.Html and the source generators
transitively.
| Package | Project type | Entry-point API |
|---|---|---|
Rask.Server | net10.0 ASP.NET | services.AddRask() + app.UseRask<TApp>() |
Rask.Wasm | net10.0-browser | WasmHostBuilder.CreateDefault() + host.RunAsync<TApp>() |
Rask.Wasm.Hosting | net10.0 ASP.NET (with a <ProjectReference> to the WASM project) | app.UseRask() |
Rask.Validation.DataAnnotations | any host that hosts your forms | drop DataAnnotationsValidator inside a Form |
Rask.Validation.FluentValidation | any host that hosts your forms | drop FluentValidationValidator.Validator(myValidator) inside |
Rask.Bootstrap | any host with your components | link BootstrapStyles in Head, then chain the Bs* components |
Rask.WebPush | any backend (Server app or a WASM PWA's ASP.NET host) | services.AddRaskWebPush(...) + inject IWebPushSender |
Rask.Cqrs | any .NET app (standalone; Server, WASM, or non-Rask) | services.AddRaskCqrs() + inject IDispatcher |
Rask.Cqrs.Client | a WASM app talking to its own server | services.AddRaskCqrsClient() — the same IDispatcher, now remote |
Rask.Cqrs.Server | the ASP.NET host those clients dispatch to | services.AddRaskCqrsServer() + app.MapRaskCqrs() |
Rask.Data | an EF Core app wanting a DDD base entity + interceptors | class X : Entity<Guid> + services.AddRaskData() + modelBuilder.ApplyRaskConventions() |
Rask.Outbox | an EF Core app wanting durable domain-event delivery | record E(...) : IOutboxEvent + services.AddRaskOutbox<Ctx>() + modelBuilder.AddRaskOutbox() |
Rask.Jobs | an EF Core app wanting durable background jobs | record J(...) : IJob + ICommandHandler<J> + services.AddRaskJobs<Ctx>() + modelBuilder.AddRaskJobs() |
Rask.Mail | an EF Core app wanting durable transactional email | services.AddRaskMail<Ctx>(o => o.From = ...) + modelBuilder.AddRaskMail() + inject IMailQueue |
Rask.Cache | an EF Core app wanting a database-backed cache | services.AddRaskCache<Ctx>() + modelBuilder.AddRaskCache() + inject ICache / IDistributedCache |
Rask.Logging | any app that wants its log to survive a restart | services.AddRaskLogging("Data Source=logs.db") — no TContext, no migration; inject ILogStore to read it back |
Rask.Dashboard | operating an app that uses the DB-backed pillars | services.AddRaskDashboard<Ctx>() + an AddAuthorization policy named RaskDashboardPolicies.Access, then browse /_rask |
Rask.SQLite | any .NET app using SQLite (server, mobile, trimmed/AOT) | services.AddRaskSqlite(cs) + inject IRaskSqliteConnectionFactory (incl. non-blocking ExecuteInImmediateTransactionAsync) |
Rask.SQLite.EntityFrameworkCore | an EF Core app that wants the pragmas (+ opt-in busy retry) | o.UseRaskSqlite(cs) on the DbContextOptionsBuilder |
Rask.SQLite.Litestream | server-side SQLite app wanting managed backup | services.AddRaskSqliteLitestream(...) + RestoreSqliteFromLitestreamAsync() |
Rask.SQLite.Snapshots | server-side SQLite app wanting scheduled backups | services.AddRaskSqliteSnapshots(...) (or inject ISqliteSnapshotter) |
Rask.SQLite.Browser | a WASM app wanting a real SQLite database that survives a reload | services.AddRaskBrowserSqlite("app") + o.UseSqlite(BrowserSqlite.ConnectionString("app")) |
Rask.Signaling | net10.0 ASP.NET hosting the WebRTC signaling IWebRtc needs | services.AddRaskSignaling() + app.MapRaskSignaling() — needs app.UseWebSockets() |
Rask.Testing | your *.Tests project (references your app) | RaskTest.Render(MyComponent.Title("hi")) → assert on .Html |
| The .NET One Person Framework | The doctrine, the batteries, and why one server beats a rented stack |
| Installing Rask · Getting started · Tutorial: zero to deploy | The one-line installer; the UI end to end; then a whole product, one pillar per chapter |
| Building components · Elements & the DSL | How markup is written: naming a component and chaining onto it, and what the IDE offers at each step |
| Composition · Lifecycle · Routing · Forms | Context, callbacks, children; mount/update/dispose; URLs and the form pipeline |
The rask CLI · Deployment | new / dev / db / deploy; Docker over SSH, auto-HTTPS, bare-VPS setup |
| Data · CQRS · Auth · Jobs · Email · Cache · Outbox · Logging · SQLite | The DB-backed pillars |
| Bootstrap · Browser APIs · Mobile & PWA | Typed Bootstrap 5.3, 53 typed Web-API wrappers, installable PWAs |
| Best practices · Testing · Accessibility · AOT | Patterns and pitfalls; unit + E2E; a11y; opt-in full WASM AOT |
| Migrating from Blazor · Diagnostics | Day-to-day differences side by side; every RASK build error and its fix |
The full index is docs/. To click through a real app and read its source, the
docs site ↗ is a live Rask app, the
playground ↗ compiles Rask C# in the browser with
Roslyn-powered IntelliSense, and samples/ runs locally
(dotnet run --project samples/Rask.Example.Server).
Rask is the Norwegian/Danish/Swedish word for fast, and the engine earns it: after first paint a state change ships a minimal diff — a counter tick on a 24 KB page goes out as ~41 bytes. It ships fewer bytes on the wire than Blazor on every scenario in the head-to-head suite, allocates ~40× less per update and holds a ~30% leaner retained tree per mounted page. The CI-enforced numbers are in the Rask vs Blazor baselines ↗.
Rask is pre-1.0; APIs may change between minor versions. It targets .NET 10 (net10.0 for ASP.NET
hosts, net10.0-browser for WASM). Unit suites cover the core, generators, hosts, the back-half packages
and validation, plus a Playwright E2E suite; Rask.Example.Wasm publishes with zero IL trimming warnings.
Production use at your own discretion — issues and PRs welcome.
MIT.
920 commits
26 commits
Hacker News (1)
C#
93.5%
TypeScript
4.6%
Shell
1.3%
The .NET One Person Framework — build, run, and ship a whole product solo, in C#, on one server. C# components render server-side over WebSockets or on WebAssembly, or host a typed TypeScript SPA (React, Vue, Angular, Svelte, Solid, Preact, Lit); SQLite-first data, auth, jobs, mail, cache, CQRS & one-command deploy.
31
stars
946
commits
C#
primary language
Sep 4, 2026
updated
Site ↗ · Docs ↗ · Playground ↗
You write components as plain C# classes that return a tree of HTML from Render(). State is a field, an
event handler is a delegate, and the component re-renders itself — no .razor, no JSX, no JavaScript,
nothing to write in another language:
[Route("/counter")]
public sealed partial class Counter : Component
{
private int _count;
protected override Component? Render() =>
[
H1["Counter"],
P[$"Current count: {_count}"],
Button.OnClick(() => _count++)["Click me"]
];
}
Rask is the .NET One Person Framework. One developer builds, runs and ships a complete product — the UI, the data, the auth, the background work and the deployment — from one C# codebase on one server, with SQLite as the production database. No PaaS to rent, no stack of services to glue, no second language to context-switch into. The same component runs two ways: server-rendered with live updates over a WebSocket, or fully client-side on WebAssembly as an installable offline PWA.
An empty folder to a live, HTTPS, database-backed product, by one person in one sitting:
curl -sSL https://pal-tamas.github.io/rask/rask.sh | sh # the CLI, and everything it shells out to
rask new Shop --auth # scaffold: the whole stack + a cookie login
# …write a Products slice — docs/tutorial/02-first-feature.md has the code
rask db add InitialCreate && rask db update # create + apply the SQLite migration
rask dev # run it, hot-reloading, at /products
rask deploy --host root@box --domain shop.example.com # ship it: bare box → Docker + auto-HTTPS, zero-downtime
Every step is a first-party command, and every stateful pillar it touches — auth, jobs, mail, cache,
events — rides the app's own SQLite database. Run rask with no arguments for a wizard.
Prerequisites: none. The installer adds whatever is missing — the .NET 10 SDK, the
wasm-toolsworkload that browser bundles need, Node for the SPA templates — all under$HOME, nosudo. On Windows:irm https://pal-tamas.github.io/rask/rask.ps1 | iex. Already have the .NET 10 SDK and want only the tool?dotnet tool install -g Rask.Cli. Options, install locations and uninstall: docs/installation.md.
Want the pages to run in the browser too? rask new Shop --wasm publishes a WebAssembly bundle from
that same project, and a page that can run client-side moves there once it has downloaded — no second
project, no separate build. Until then, and for any page that reaches a database, it stays live over the
socket. See render modes.
Prefer React? rask new Shop --template react scaffolds a Vite client on an ASP.NET host, with the
front end's TypeScript generated from your C# message records on every build — so
await rask.dispatch(getOrder({ id })) is typed, and renaming a property breaks the build rather
than the wire. The client is a TypeScript SPA — React, Preact, Vue, Angular, Solid, Svelte or Lit, but not JavaScript, since
every guarantee here is one a compiler makes. See docs/spa.md. (Needs Node.js.)
Want React inside a Rask app rather than instead of one? Derive a component from ReactComponent
and drop a Chart.tsx beside it — it becomes an ordinary component you place anywhere the chain goes,
a leaf in a card or a whole route. Props are declared in C# and checked in both directions, callbacks
re-enter C# over the channel every handler already uses, and the live diff leaves the subtree alone
because its own renderer owns it. LitComponent pairs with a .ts the same way. Rename a C# property
and the front end stops compiling. Needs Node, because your React does. See
docs/external-components.md.
Pick one host package per project, then add what you need. Everything below targets .NET 10 and is trim/AOT-safe.
Rask.Server and Rask.Wasm pull in Rask.Core, Rask.Html and the source generators
transitively.
| Package | Project type | Entry-point API |
|---|---|---|
Rask.Server | net10.0 ASP.NET | services.AddRask() + app.UseRask<TApp>() |
Rask.Wasm | net10.0-browser | WasmHostBuilder.CreateDefault() + host.RunAsync<TApp>() |
Rask.Wasm.Hosting | net10.0 ASP.NET (with a <ProjectReference> to the WASM project) | app.UseRask() |
Rask.Validation.DataAnnotations | any host that hosts your forms | drop DataAnnotationsValidator inside a Form |
Rask.Validation.FluentValidation | any host that hosts your forms | drop FluentValidationValidator.Validator(myValidator) inside |
Rask.Bootstrap | any host with your components | link BootstrapStyles in Head, then chain the Bs* components |
Rask.WebPush | any backend (Server app or a WASM PWA's ASP.NET host) | services.AddRaskWebPush(...) + inject IWebPushSender |
Rask.Cqrs | any .NET app (standalone; Server, WASM, or non-Rask) | services.AddRaskCqrs() + inject IDispatcher |
Rask.Cqrs.Client | a WASM app talking to its own server | services.AddRaskCqrsClient() — the same IDispatcher, now remote |
Rask.Cqrs.Server | the ASP.NET host those clients dispatch to | services.AddRaskCqrsServer() + app.MapRaskCqrs() |
Rask.Data | an EF Core app wanting a DDD base entity + interceptors | class X : Entity<Guid> + services.AddRaskData() + modelBuilder.ApplyRaskConventions() |
Rask.Outbox | an EF Core app wanting durable domain-event delivery | record E(...) : IOutboxEvent + services.AddRaskOutbox<Ctx>() + modelBuilder.AddRaskOutbox() |
Rask.Jobs | an EF Core app wanting durable background jobs | record J(...) : IJob + ICommandHandler<J> + services.AddRaskJobs<Ctx>() + modelBuilder.AddRaskJobs() |
Rask.Mail | an EF Core app wanting durable transactional email | services.AddRaskMail<Ctx>(o => o.From = ...) + modelBuilder.AddRaskMail() + inject IMailQueue |
Rask.Cache | an EF Core app wanting a database-backed cache | services.AddRaskCache<Ctx>() + modelBuilder.AddRaskCache() + inject ICache / IDistributedCache |
Rask.Logging | any app that wants its log to survive a restart | services.AddRaskLogging("Data Source=logs.db") — no TContext, no migration; inject ILogStore to read it back |
Rask.Dashboard | operating an app that uses the DB-backed pillars | services.AddRaskDashboard<Ctx>() + an AddAuthorization policy named RaskDashboardPolicies.Access, then browse /_rask |
Rask.SQLite | any .NET app using SQLite (server, mobile, trimmed/AOT) | services.AddRaskSqlite(cs) + inject IRaskSqliteConnectionFactory (incl. non-blocking ExecuteInImmediateTransactionAsync) |
Rask.SQLite.EntityFrameworkCore | an EF Core app that wants the pragmas (+ opt-in busy retry) | o.UseRaskSqlite(cs) on the DbContextOptionsBuilder |
Rask.SQLite.Litestream | server-side SQLite app wanting managed backup | services.AddRaskSqliteLitestream(...) + RestoreSqliteFromLitestreamAsync() |
Rask.SQLite.Snapshots | server-side SQLite app wanting scheduled backups | services.AddRaskSqliteSnapshots(...) (or inject ISqliteSnapshotter) |
Rask.SQLite.Browser | a WASM app wanting a real SQLite database that survives a reload | services.AddRaskBrowserSqlite("app") + o.UseSqlite(BrowserSqlite.ConnectionString("app")) |
Rask.Signaling | net10.0 ASP.NET hosting the WebRTC signaling IWebRtc needs | services.AddRaskSignaling() + app.MapRaskSignaling() — needs app.UseWebSockets() |
Rask.Testing | your *.Tests project (references your app) | RaskTest.Render(MyComponent.Title("hi")) → assert on .Html |
| The .NET One Person Framework | The doctrine, the batteries, and why one server beats a rented stack |
| Installing Rask · Getting started · Tutorial: zero to deploy | The one-line installer; the UI end to end; then a whole product, one pillar per chapter |
| Building components · Elements & the DSL | How markup is written: naming a component and chaining onto it, and what the IDE offers at each step |
| Composition · Lifecycle · Routing · Forms | Context, callbacks, children; mount/update/dispose; URLs and the form pipeline |
The rask CLI · Deployment | new / dev / db / deploy; Docker over SSH, auto-HTTPS, bare-VPS setup |
| Data · CQRS · Auth · Jobs · Email · Cache · Outbox · Logging · SQLite | The DB-backed pillars |
| Bootstrap · Browser APIs · Mobile & PWA | Typed Bootstrap 5.3, 53 typed Web-API wrappers, installable PWAs |
| Best practices · Testing · Accessibility · AOT | Patterns and pitfalls; unit + E2E; a11y; opt-in full WASM AOT |
| Migrating from Blazor · Diagnostics | Day-to-day differences side by side; every RASK build error and its fix |
The full index is docs/. To click through a real app and read its source, the
docs site ↗ is a live Rask app, the
playground ↗ compiles Rask C# in the browser with
Roslyn-powered IntelliSense, and samples/ runs locally
(dotnet run --project samples/Rask.Example.Server).
Rask is the Norwegian/Danish/Swedish word for fast, and the engine earns it: after first paint a state change ships a minimal diff — a counter tick on a 24 KB page goes out as ~41 bytes. It ships fewer bytes on the wire than Blazor on every scenario in the head-to-head suite, allocates ~40× less per update and holds a ~30% leaner retained tree per mounted page. The CI-enforced numbers are in the Rask vs Blazor baselines ↗.
Rask is pre-1.0; APIs may change between minor versions. It targets .NET 10 (net10.0 for ASP.NET
hosts, net10.0-browser for WASM). Unit suites cover the core, generators, hosts, the back-half packages
and validation, plus a Playwright E2E suite; Rask.Example.Wasm publishes with zero IL trimming warnings.
Production use at your own discretion — issues and PRs welcome.
MIT.
Hacker News (1)
920 commits
26 commits
C#
93.5%
TypeScript
4.6%
Shell
1.3%