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
Add a related entity
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 Dockerfile — rask 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