You're reading the Rask v1.0.0 guides.View source
All guides

Recipes

Task-first answers to "how do I do X in an app I already have?" Each recipe is the shortest path — the command, the one wiring line, and where to go deeper. The Tutorial teaches these in order on one app; this page is the lookup. Keep the Cheat sheet open alongside.

I want to… Jump to
add a CRUD feature to a database I already have
relate two entities (one-to-many, many-to-many)
require a login to reach a page
run work off the request thread
send a transactional email
cache an expensive query
publish a domain event durably
harden SQLite for production
deploy, and redeploy
test a feature

Add a feature to an existing database

A feature maps through the DbContext the project already has — add the entity, its configuration and its pages under Features/Orders/, then one line to the context:


public DbSet<Order> Orders => Set<Order>();

rask db add AddOrder && rask db update

→ Reference: data access · Learn it: Tutorial Ch 3

Give the child a foreign key and the parent a collection, then map it in the child's IEntityTypeConfiguration:


// Comment.cs
public Guid PostId { get; private set; }

// CommentConfiguration.cs
entity.HasOne<Post>().WithMany().HasForeignKey(x => x.PostId);

Use .IsRequired(false) on the foreign key for an optional relationship, and EF Core's implicit join table for many-to-many (no join entity needed).

→ Reference: the rask CLI · Learn it: Tutorial Ch 3

Require login on a page

Two gates, use both: [Authorize] at the route (redirects anonymous deep-links to /login), and the Authorize component to hide UI that anonymous users shouldn't see.


[Authorize]                                    // route-level; from Microsoft.AspNetCore.Authorization
public sealed partial class CreateProduct : Component { … }

Authorize[ NewProductButton() ]              // rendered only for signed-in users
Authorize.Roles(["admin"])[ DeleteProductButton(product.Id) ]

The login page itself is already there: /login, /register and /logout are built in, and you replace any of them by declaring your own page at the same route.

→ Reference: authentication · Learn it: Tutorial Ch 3

Run work off the request thread

Write a job record and handler, add one registration + its table, then enqueue. EnqueueAsync returns as soon as the row is written, so the request finishes immediately; a background processor runs it at-least-once.


public sealed record SendOrderReceipt(Guid OrderId) : IBackgroundJob;

builder.Services.AddRaskJobs<ProductsDbContext>(o => { /* … */ });   // needs AddRaskCqrs()
modelBuilder.AddRaskJobs();                                          // then: rask db add AddJobs && rask db update
await jobs.EnqueueAsync(new SendOrderReceipt(order.Id), CancellationToken);

→ Reference: background jobs · Learn it: Tutorial Ch 4

Send a transactional email

Write an email whose body is a Rask component, add the mail queue, then send. Delivery happens off the request thread over SMTP with backoff.


builder.Services.AddRaskMail<ProductsDbContext>(o => { /* SMTP … */ });
modelBuilder.AddRaskMail();                                          // then: rask db add AddMail && rask db update

→ Reference: transactional email · Learn it: Tutorial Ch 5

Cache an expensive query

One registration + one table, then wrap the read in GetOrAddAsync and invalidate on write.


builder.Services.AddRaskCache<ProductsDbContext>();
modelBuilder.AddRaskCache();                                         // then: rask db add AddCache && rask db update

var products = await cache.GetOrAddAsync("products", async _ => await LoadAsync(), CancellationToken);
await cache.RemoveAsync("products");                                 // when the catalog changes

→ Reference: cache · Learn it: Tutorial Ch 6

Publish a domain event through the outbox

Events are written to an OutboxMessage row in the same transaction as your data, then delivered post-commit (crash-safe, at-least-once). Declare the events, raise them from the entity, and wire the outbox:


public sealed record OrderCreated(Guid Id) : IOutboxEvent;   // then Raise(new OrderCreated(Id)) in Create

builder.Services.AddRaskData();                                     // unchanged — the outbox claims delivery
builder.Services.AddRaskOutbox<ProductsDbContext>(o => { /* … */ });
modelBuilder.AddRaskOutbox();                                        // then: rask db add AddOutbox && rask db update

→ Reference: outbox · Learn it: Tutorial Ch 7

Turn on production SQLite

UseRaskSqlite is a drop-in for .UseSqlite that installs the pragma interceptor (WAL, foreign_keys, busy_timeout on every open). Add Litestream for continuous off-box backup.


.UseRaskSqlite("Data Source=app.db")                                // was .UseSqlite("Data Source=app.db")
builder.Services.AddRaskSqliteLitestream(o => { /* S3/replica … */ });

→ Reference: production SQLite · Learn it: Tutorial Ch 8

Deploy and redeploy

rask deploy builds your Docker image on the server, runs it behind a shared Caddy proxy with automatic HTTPS, and does a health-gated zero-downtime swap. The first run on a bare box also sets it up (Docker, a non-root deploy user, firewall, SSH hardening).


rask deploy --host root@your-box.example.com --domain shop.example.com   # first time
rask deploy                                                             # after: host/domain remembered
rask deploy --github-actions                                            # write .github/workflows/deploy.yml

Needs a Dockerfilerask new writes one.

→ Reference: deployment · Learn it: Tutorial Ch 11

Test a feature

Add a sibling <Project>.Tests project and test the slice directly — the domain rules on the entity, and a SQLite round-trip through the real DbContext:


[Fact]
public void Create_sets_the_fields()
{
    var product = Product.Create("Desk", 249m, inStock: true);

    Assert.Equal("Desk", product.Name);
    Assert.NotEqual(Guid.Empty, product.Id);
}

→ Reference: testing


Command reference → the rask CLI · One-page reference → Cheat sheet · Learn it in order → Tutorial