# Getting started with Rask

Rask is **the full-stack .NET web framework** — UI, data, auth, background work, realtime and deploy, all
in C#, for a team of one or fifty ([read the philosophy](https://rask.sh/docs/guides/one-person-framework/)). This guide starts with
the UI; the [tutorial](https://rask.sh/docs/guides/00-overview/) adds the rest of the stack. You build the UI as plain C#
classes — no `.razor`, no JSX, no JavaScript to write. A component is a class that returns a tree
of HTML from `Render()`, and the *same* component runs either server-rendered (live updates over a
WebSocket) or fully client-side in the browser on WebAssembly.

This is a zero-to-running guide for someone new to Rask. By the end you'll have an app on screen,
you'll understand the files the template gave you, and you'll have written your own component, an event
handler that updates the UI, and a route. It assumes you're comfortable with C# — we explain the
Rask-specific ideas, not the language.

> **Coming from Blazor?** Skim [migrating from Blazor](https://rask.sh/docs/guides/migration-from-blazor/) for the concept
> mapping (`@page` → `[Route]`, `[Parameter]` → a property, `EventCallback` → `Callback`). **Just
> want to look first?** Click through the [live demo](https://rask.sh/docs/) — a full
> multi-page Rask app, no install needed.

## Before you start

Rask requires the **.NET 10 SDK** — every package ships for .NET 10, the LTS release. Confirm you have it:

```bash
dotnet --version      # must be ≥ 10.0
```

If that prints an older version (or errors), install the .NET 10 SDK from
[dotnet.microsoft.com](https://dotnet.microsoft.com/download) first.

> **WASM only:** the `wasm` and `wasm-hosted` templates also need the browser
> WebAssembly tooling — install it once with `dotnet workload install wasm-tools`. If you're starting
> with the server template (recommended below), you can skip this.

## 1. Scaffold a project

Scaffolding is done by the [`rask` CLI](https://rask.sh/docs/guides/cli/) (`Rask.Cli`, a global .NET tool). **Not sure which host
to pick? Choose the default `server` template** — it's a single ASP.NET project that runs with no extra
setup, and the components you write are identical across hosts, so nothing you learn here is wasted if
you switch later.

```bash
curl -sSL https://rask.sh/rask.sh | sh   # one-time: the rask CLI + what it needs

rask new MyApp                       # create a server app in ./MyApp (server is the default)
```

The installer adds the .NET SDK if you don't have one, plus `dotnet-ef`, the `wasm-tools`
workload and Node — all under `$HOME`, no `sudo`. With the SDK already in place,
`dotnet tool install -g Rask.Cli` is enough. See [Installing Rask](https://rask.sh/docs/guides/installation/).

`rask new` ships three C# templates and a front-end template per framework:

| `--template`        | What you get                                                                                  |
|---------------------|-----------------------------------------------------------------------------------------------|
| `server` (default)  | One ASP.NET project. Components render on the server; live updates ship over a WebSocket. **Best default.** |
| `wasm`              | One browser-WASM project that publishes to a static `wwwroot/` you can host anywhere (GitHub Pages, S3, nginx). Bring your own API. |
| `wasm-hosted`       | A WebAssembly app in `Client/`, served by its own ASP.NET host — the browser runs the UI, the server keeps the database, the handlers and every battery. |
| `react`, `preact`, `vue`, `angular`, `solid`, `svelte`, `lit` | That framework's TypeScript app in `client/`, on the same kind of host, calling your C# messages through a generated typed client — see [single-page app front ends](https://rask.sh/docs/guides/spa/#scaffolding-one). |

The first three emit the same starter pages in C#, so the rest of this guide applies whichever of them you chose.

**Each arrives with every battery it can carry.** On `server` that is a SQLite database, the
[Rask.Cqrs](https://rask.sh/docs/guides/cqrs/) mediator, background jobs, transactional email, a cache, a transactional outbox,
[file storage](https://rask.sh/docs/guides/file-storage/) for uploads, scheduled backups, a durable log store, the [operator dashboard](https://rask.sh/docs/guides/dashboard/), an installable
[PWA](https://rask.sh/docs/guides/pwa/) with Web Push, a Dockerfile for [`rask deploy`](https://rask.sh/docs/guides/cli/), and the localization machinery —
wiring, not sample pages. `wasm` takes the PWA and the Dockerfile; the rest need a host to put a
database in. Languages are configured rather than chosen on the command line — a server app starts
with English in `appsettings.json` (`Rask:Cultures`), and adding another is an entry in
that list. A browser-WASM
app registers none, because a language there means shipping ICU: roughly a megabyte of extra download
that an app formatting nothing culture-sensitive should not pay by default. See
[localization](https://rask.sh/docs/guides/localization/).

Almost nothing is left to you. Sign-in comes with the app — register, sign in and sign out work out of
the box, and the first account you create is the administrator (see [authentication](https://rask.sh/docs/guides/authentication/)).
Styling is not a flag either: every project is Tailwind. To leave a battery out, name it:
`rask new MyApp --no-push --no-ops`. The full flag list is in [the CLI reference](https://rask.sh/docs/guides/cli/).

## 2. Run it

```bash
cd MyApp
rask dev
```

Open the URL printed in the console. **You should see** a single **"Hello, Rask! 👋"** welcome card that
lists the `rask` commands you'll use next. The starter app is deliberately minimal — a clean shell with one
page — so there's nothing to delete before you start building.

> **Edit-and-refresh with hot reload.** `rask dev` is the live inner loop (`dotnet run` starts the app
> without it): edit a component's `Render()` (or its scoped `.css`/`.ts`), a `[Route]` template, or a CQRS handler and save —
> **C# Hot Reload** applies the change to the running app and Rask re-renders the open session in place, no
> manual rebuild or browser refresh. A small "Hot reload applied" pill confirms it landed. Edits the
> runtime can't apply (adding a type, changing a signature) restart the app instead, and the page reloads
> itself. The full list is in [what hot-reloads](https://rask.sh/docs/guides/cli/#what-hot-reloads).

> **First build is slower, and the IDE may look broken — that's expected.** The first build is when
> Rask's source generators run. Until then your IDE may flag `HomePage` or
> `Routes.HomePage()` as undefined — they're *generated* members that don't exist until you build. Build
> once, then reload the solution so IntelliSense picks them up. (More on this in
> [Troubleshooting](#troubleshooting) below.)

## 3. Tour of what the template generated

Before writing code, here's what's in the project and why. The `server` template is small on purpose (the
WASM templates differ mainly in `Program.cs`):

- **`Program.cs`** — the host, in one line:

  ```csharp
  using MyApp.Features.Shared;

  RaskApp.Create(args).Run<App>();
  ```

  `RaskApp` is every battery the template supports, **on by default** — the database, mediator, accounts,
  background jobs, transactional email, cache, outbox, file storage, operator dashboard, durable logs, Web
  Push, snapshots and continuous backup (inert until `Rask:Litestream:ReplicaUrl` is set) — plus the host
  work every app repeats: health checks at `/health`, the exception handler and HSTS outside Development,
  HTTPS redirection, static assets, authentication, `App` mounted as the whole site, and the document
  around it — charset, viewport, the UI kit's stylesheet and theme, your `Styles/app.css` — so `App.cs` is a
  title and `Render() => Router`. An app that does
  without a battery says so here, and a scaffold made with `--no-<battery>`
  ([`rask new`](https://rask.sh/docs/guides/cli/#rask-new--scaffold-a-project)) has that line written for it:

  ```csharp
  var app = RaskApp.Create(args);

  app.Configure(c => c.Jobs.Off());

  app.Run<App>();
  ```

  Your own services go on `app.Services` and your own endpoints in `app.MapEndpoints(e => …)`, between
  `Create` and `Run`.

  **Settings are not code.** A From address, a connection string, a session cap — every Rask setting lives
  in `appsettings.json` under `Rask`, each battery reading its own section (mail reads `Rask:Mail`), and an
  environment variable overrides any of them with double underscores (`Rask__Mail__From`). A callback —
  `app.Configure(c => c.Mail.Configure(o => …))` — runs after configuration and wins over both, for the
  rare value that has to be code. The scaffolded `appsettings.json` starts on development defaults — a
  local `app.db` at `Rask:ConnectionStrings:App` — that every one of those sources overrides. See
  [Configuration](https://rask.sh/docs/guides/configuration/).

- **No database context to write.** `RaskAppDbContext` maps every aggregate you declare plus every
  battery's tables; code that needs the context itself injects `IDbContextFactory<RaskAppDbContext>`
  ([Rask.Data](https://rask.sh/docs/guides/data/)).

- **`Features/Shared/App.cs`** — the **root component** `App`: it renders straight into
  `<body>` — Rask builds the document around it — and drops a `Router` where the current page appears.
  `<head>` is framework-managed — Rask writes the charset, viewport and stylesheets, and the tab's title
  — the page's `PageTitle`, then the app's name — is its one `HeadAssets` line (more in [section 7](#7-the-document-and-the-headassets-override)).
  Beside it, `Features/Shared/User.cs` is the app's account type.

- **`Features/Home/HomePage.cs`** — the `/` route, a small welcome card. Edit or replace it; it's your
  starting point.

- **`{Project}.csproj`** and **`Properties/launchSettings.json`** — the project file (framework package
  references, source generators) and the local run profile (URLs, environment).

- **`{Project}.Tests/`** — a test project with one passing test, `Home_page_greets_the_visitor`, which
  renders `HomePage` in-process with [`Rask.Testing`](https://rask.sh/docs/guides/testing/) and checks its greeting. `dotnet test`
  runs it; `{Project}.slnx` lists both projects. `rask new --no-tests` leaves it out.

That's the whole starter app — no example `Counter` or `Weather` pages to clean up. You'll add your own
screens next; a **scoped `.css`** or **`.ts`** file is as easy as dropping `{Component}.css` next to a
`{Component}.cs` (same folder, same base name) — its selectors apply only to that component, no leaks.

## 4. Your first component

Every component is a `sealed partial class : Component`. Override `Render()` and return a tree of HTML
written as a **chain**: name a component and dot onto it — `Div.Class("greeting")`. The name *is* the
component, so pressing `.` lists everything it has, each step carrying its own doc comment. A tag you set
nothing on needs no parentheses at all (`H1["Hi"]`). Children attach through an **indexer** on every
component — `Div[ ... ]` — and strings, other components, and value types all convert to a child node
automatically:

```csharp
public sealed partial class Greeting : Component
{
    protected override Component? Render() =>
        Div.Class("greeting")[
            H1["Hello, world!"],
            P["Welcome to your new Rask app — ", Strong["it's all C#"], "."],
            Span[42]                          // value types convert too — no .ToString()
        ];
}
```

`Render()` returns `Component?`, which accepts three shapes — you'll mostly use the first two:

- a single node — `Render() => Div[...]`;
- a **collection expression** for several top-level nodes with no wrapper — `Render() => [H1["Title"],
  P["Body"]]`;
- `null` — render nothing.

> **Safe by default — good to know, not needed yet.** Two security defaults are worth knowing about but
> won't get in your way:
> - **Strings are HTML-encoded.** A plain string becomes a `Text` node, so `P["<b>hi</b>"]` shows the
>   angle brackets as text. When you genuinely need verbatim markup, use `Raw("<b>hi</b>")`.
> - **URL attributes are scheme-sanitized.** `href`/`src`/etc. neutralize dangerous schemes
>   (`javascript:` → `about:blank`) so a user-supplied URL can't run script on click. For a URL you
>   fully control, opt out per-call with `RaskUrl.Trusted(...)`.
>
> See [best practices](https://rask.sh/docs/guides/best-practices/) for the full security picture.

## 5. Add interactivity

Keep local state in fields and wire event handlers as lambdas. After the handler runs, **the
component that owns it re-renders automatically** — you never call `StateHasChanged()` by hand for a
local update. A click does a server round-trip (server host) or a local re-render (WASM host); the same
code works for both.

```csharp
[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"]
        ];
}
```

### Going further: child → parent communication

A child declares a `Callback<int>` property (`Callback` when the event carries nothing), and the chain step
that sets it wraps the handler so invoking it re-renders the **parent** that owns the lambda. The caller
hands it a sync or an async handler through the same step, an unset one does nothing when invoked, and the
child stays oblivious to the parent:

```csharp
public sealed partial class RatingStars : Component
{
    public int Value { get; set; }
    public Callback<int> OnRate { get; set; }           // an event: optional, never a required step

    protected override Component? Render() =>
        Div[
            Enumerable.Range(1, 5).Select(i => Button.Key(i)
                .OnClick(() => OnRate.Invoke(i))[i <= Value ? "★" : "☆"])   // child invokes; parent re-renders
        ];
}

public sealed partial class RatingDemo : Component
{
    private int _rating;

    protected override Component? Render() =>
        [
            RatingStars.Value(_rating).OnRate(n => _rating = n),   // lambda captures this
            P[_rating == 0 ? "Click a star." : $"You rated: {_rating}/5"]
        ];
}
```

## 6. Why `HomePage` already chains (the generated surface)

You never write a builder by hand. For each concrete `Component`, the generator emits a **chain entry**
and a step per public settable property — that's why `HomePage`, `Counter`, and your own `Greeting` can
be named and dotted onto. Which shape a property takes is derived from its declaration:

| Property shape                                    | In the chain                                    |
|---------------------------------------------------|-------------------------------------------------|
| Non-nullable, **no initializer**                  | a **step** — required before the component exists |
| Nullable (`T?` / `Nullable<T>`), no initializer   | an optional setter                              |
| Has an initializer (`= ...`)                      | an optional setter — your default wins          |
| `[SkipFactory]` (property or class)               | **excluded** — no step, no setter               |
| `Children`                                        | always excluded (children attach via the indexer) |

```csharp
public sealed partial class Card : Component
{
    public required string Title { get; set; }     // a step: Card.Title("…") opens the chain
    public string? Subtitle { get; set; }          // an optional setter
    public int Elevation { get; set; } = 1;        // an optional setter — your default wins
    [SkipFactory] public int Internal { get; set; }// excluded explicitly
    // → Card.Title("Pricing").Subtitle("per seat").Elevation(2)
}
```

The steps come first and in any order; miss one and there is no component to render, so the mistake is a
compile error where you made it rather than a null at runtime. The class must be `partial` — that is
where the generator puts the surface.

Live — a `Greeting` with a required `Name` and an optional `Title`, built with
`Greeting.Name("Ada")…`:


**Inject framework services (`HttpClient`, `RouteState`, `IJSRuntime`) through the
constructor, not as properties** — a non-nullable settable property would become a *required step*
(and `required` on a property with a DI-only constructor is the **RASK002** warning). Inject
through the primary constructor instead:

```csharp
public sealed partial class Weather(IWeatherForecastService service) : Component { ... }
```


`[SkipFactory]` keeps a property settable in code but off the chain — useful for seeding
cached internal state the caller shouldn't pass. The counter below starts at 7 (its `Initial` is
`[SkipFactory]`, seeded in `OnMount`) and keeps its state across re-renders like any private field:


## 7. The document and the `HeadAssets` override

Your root component (the `TApp` you pass to the host — `App` in the template) renders straight into
`<body>`. Rask composes the document around it: the doctype, `<html>`, a `<head>` filled from every
mounted component's `HeadAssets` override (plus the charset, viewport, the UI kit's stylesheet, your
`Styles/app.css`, and the scoped CSS and JS the page needs), and a `<body>` holding what the root
rendered and the auto-appended runtime `<script>`. So a root is just its title and a `Router`:

```csharp
public sealed partial class App(RouteState route) : Component
{
    // The tab's title: what the current page calls itself, then the app's name.
    protected override Component? HeadAssets => Title[route.Title is { } page ? $"{page} | My Rask App" : "My Rask App"];

    protected override Component? Render() => Router;
}
```

The two attributes an app usually wants on the shell are overrides of their own, read off the root:
`HtmlLang` — the `lang` on `<html>`, `"en"` by default, `null` to omit it — and `BodyClass`, the
`class` on `<body>`, `null` by default:

```csharp
protected override string? HtmlLang => "fr";
protected override string? BodyClass => "bg-base-200";
```

Anything those two can't express — another attribute on `<html>`, an element wrapped around the app —
is a `Shell` override. It receives the framework's `<head>` and the app's rendered body as
**parameters**, so place both: drop `head` and the page loses every head asset.

```csharp
protected override Component Shell(Component head, Component body) =>
    Html.Lang("en").Dir(Dir.Rtl)[head, Body.Class("dark")[body]];
```

The doctype is still emitted ahead of whatever `Shell` returns, and the runtime `<script>` still lands
in `<body>` — neither is yours to add. The `body` parameter is the app's content, not the element: a shell
that writes `Html[head, body]` gets a `<body>` put around everything after the head, and a warning naming
the override, because without one the runtime and the session id have nowhere to go and the page never connects. `Shell` is evaluated once per render, *before* your `Render()`
runs, so it can't observe state that render produces; keep anything reactive in the body or in `HeadAssets`.

A page says what it is called, and the root's `Title` line above reads it — the same `route.Title` a layout
puts in its breadcrumb ([a page's title](https://rask.sh/docs/guides/routing/#a-pages-title--pagetitle-and-routetitle)):

```csharp
protected override string? PageTitle => "Welcome";
```

Any component can contribute to `<head>` while it's in the tree by overriding `HeadAssets` — a `<meta>`, a
stylesheet, a preload. `<title>` and `<base>` are singleton tags, and the last contributor wins.

> **Guardrails:** two compile-time checks catch the common mistakes (full list in
> [diagnostics](https://rask.sh/docs/guides/diagnostics/)) — **RASK021** if the root renders the shell itself, and **RASK019** if
> you pass children to `Head[…]` instead of using the override.

> **Already have an app?** Delete the shell from your root's `Render()` and return what was inside
> `<body>` (usually just `Router`). Its pieces move to the overrides that own them: the `lang` on
> `Html` becomes `HtmlLang`, the `Class` on `Body` becomes `BodyClass`, the `Head[…]` slot just
> goes away (your head contributions belong in the `HeadAssets` override), and anything left over
> becomes a `Shell` override. `Doctype`, `Html`, `Head`, and `Body` are still ordinary tag components —
> they're what you build a document out of by hand (`ToHtml()`, an email body), just not the app's page.

## 8. Add a route

Put `[Route("/path")]` on a component to register it as a page (`Rask.Core.Routing` comes in through the scaffold's
`GlobalUsings.cs`). `[RouteParam]` and `[QueryParam]` bind URL pieces to properties, and every
route gets a generated, type-safe URL builder:

```csharp
[Route("/users/{id}")]
public sealed partial class UserPage : Component
{
    [RouteParam] public int Id { get; set; }
    [QueryParam] public string? Tab { get; set; }

    protected override Component? Render() =>
        Span[$"User #{Id} — {Tab ?? "overview"}"];
}

// elsewhere — type-safe, refactor-proof:
NavLink.Href(Routes.UserPage(Id: 42))["View user"];
```

The `Router` in your root component matches the current path and renders the page. To navigate from
an event handler, go to the route itself — `Routes.HomePage().Go()`, nothing injected — or change this page's
query with `Go.With("tab", "settings")`. For nested layouts
(`[ParentRoute]` + `Outlet`), 404 pages (`[NotFound]`), and the full routing model, see
[routing](https://rask.sh/docs/guides/routing/).

## Troubleshooting

The snags you're most likely to hit on a fresh project:

- **The IDE flags `HomePage` or `Routes.HomePage()` as undefined.** These are
  *source-generated* — the chain surface for every component, the URL builder for every `[Route]`. They don't
  exist until the generator runs, which happens on build. Run `dotnet build` once, then reload the
  solution / restart the language server.

- **`net10.0` / `net10.0-browser` won't restore, or a WASM publish fails.** You're missing the .NET
  SDK (`dotnet --version` must be ≥ `10.0`) or, for WASM, the workload — install it with
  `dotnet workload install wasm-tools`.

- **A scoped `.css` / `.ts` file isn't taking effect.** The sibling file must sit in the **same folder**
  as its component and share the **base name** (`Card.cs` ↔ `Card.css`). A mismatch is a build error
  (`RASK015`–`RASK018`) — check the build output.

- **Blank page or 404s on `/_rask/...` assets behind a reverse proxy or sub-path.** The app is running
  under a URL prefix the framework doesn't know about — set `PathBase` ([configuration](https://rask.sh/docs/guides/configuration/)),
  and build any hand-written asset URL as `LiveOptions.PathBase + "/…"` ([JS interop](https://rask.sh/docs/guides/js-interop/)).
  For a WASM bundle published under a prefix (GitHub Pages project sites), publish with
  `-p:RaskPathBase=/my-repo` — see [Deploying to a sub-path](https://rask.sh/docs/guides/pwa/#deploying-github-pages--sub-paths).

## Next steps

You now have a running, routed, interactive app. From here, the rest of the stack takes it to a
shipped product — and the **[zero-to-deploy tutorial](https://rask.sh/docs/guides/00-overview/)** walks that whole path
step by step (database, auth, jobs, email, cache, events, and deployment). In short:

1. **Build a feature** → [tutorial chapter 2](https://rask.sh/docs/guides/02-first-feature/) builds a database-backed
   Products catalog by hand: declare the entity, read it off its face (`Product.Where(…)`), write through
   commands whose handlers save with EF Core, build list, create, edit and delete pages, then `rask db add` —
   the app applies the migration itself when it starts. The database is a
   battery like the rest, so there is nothing to register.
2. **Make SQLite production-ready** → [Why one server, no PaaS](https://rask.sh/docs/guides/sqlite/) — WAL, busy-timeout, and
   continuous backup so one SQLite file is your production database.
3. **Ship to one server** → the scaffold already carries a production Dockerfile; [`rask deploy`](https://rask.sh/docs/guides/cli/)
   puts the whole app on one box.

Read **[the doctrine](https://rask.sh/docs/guides/one-person-framework/)** for the why. Reference guides for the next thing you need:

- **Build a form** → [forms](https://rask.sh/docs/guides/forms/) — `Form.Model(m)`, `Input.Bind(() => model.X)`, validation.
- **Add more routes / layouts** → [routing](https://rask.sh/docs/guides/routing/) — nested layouts, route/query params, `Go`.
- **Load or save data** → [Rask.Data](https://rask.sh/docs/guides/data/) — declare a model, read and write it off the type; or
  [plain EF Core](https://rask.sh/docs/guides/data-access/) with a `DbContext` of your own.
- **Run code on mount / after render** → [lifecycle](https://rask.sh/docs/guides/lifecycle/) — `OnMount`, `OnUpdated`, `OnFirstRender`, `OnRendered`, `OnUnmount`.
- **Share state without prop-drilling** → [composition](https://rask.sh/docs/guides/composition/) — context, callbacks, `VirtualizeModel`.
- **Add a login** → [authentication](https://rask.sh/docs/guides/authentication/) — cookie sessions and OIDC on Server and WASM.
- **Test your components** → [testing](https://rask.sh/docs/guides/testing/) — unit-testing components and rendered HTML.
- **Write idiomatic Rask** → [best practices](https://rask.sh/docs/guides/best-practices/) — patterns and pitfalls that keep an app correct, secure, and fast.
- **Decode a build error** → [diagnostics](https://rask.sh/docs/guides/diagnostics/) — every RASK0xx analyzer ID and its fix.

**Keep handy while you build:** the [cheat sheet](https://rask.sh/docs/guides/cheatsheet/) (every command + wiring line on one
page) and the [recipes](https://rask.sh/docs/guides/recipes/) (task-first "how do I do X?").
