Mes Recettes: Building a Modern Recipe Index with Blazor WebAssembly & Supabase

Abstract colorful shapes for Mes Recettes (Blazor + Supabase)

Mes Recettes: Building a Modern Recipe Index with Blazor WebAssembly & Supabase

Part 1 of 3 in the Mes Recettes series: Part 2 – Architecture · Part 3 – Testing & Delivery | 🇫🇷 Version

Why This Project Exists

I wanted a fast, portable way to index my cookbook recipes—linking authors, books, pages, ratings, and notes—with minimal backend overhead. Instead of standing up an API + database manually, I paired Blazor WebAssembly (C# in the browser) with Supabase (managed PostgreSQL, Auth, Realtime, REST).

Goals:

  • Single-page UX, instant navigation
  • Strongly-typed models end to end
  • Auth without reinventing flows
  • A foundation to later add search, offline support, and real-time collaboration

Technology Choices (and Why)

ConcernChoiceReason
UI & SPA runtimeBlazor WebAssembly (.NET 9)Full-stack C#, component model, ecosystem maturity
UI componentsMudBlazorMaterial look, dialogs, tables, theming
BackendSupabasePostgreSQL + Auth + Realtime + REST + row security
Data modelingPostgREST + C# attributesDirect mapping, fewer DTO layers initially
ValidationDataAnnotationsSimple, built-in, testable
TestingxUnitStandard .NET, parallel, attribute-driven
Deployment (target)Static hosting/CDN (e.g. Azure SWA / GitHub Pages + API)Cheap + global distribution

High-Level Architecture

+BM(lWuUaAdszSBeoMlrrzrSupabaseClientRAPeuoatslhttgirmeeSQL

The browser downloads the Blazor app once; subsequent data calls go straight to Supabase’s REST endpoints (via the official client SDK).

Core Code Excerpt

Program startup wires Supabase cleanly:

var options = new SupabaseOptions
{
    AutoRefreshToken = true,
    AutoConnectRealtime = true
};

builder.Services.AddSingleton(sp =>
    new Client(
        builder.Configuration["supabase:Url"] ?? string.Empty,
        builder.Configuration["supabase:Key"] ?? string.Empty,
        options
    )
);

builder.Services.AddScoped<ISupabaseAuthWrapper, SupabaseAuthWrapper>();
builder.Services.AddScoped<AuthService>();

Auth wrapper keeps client concerns isolated:

public class SupabaseAuthWrapper : ISupabaseAuthWrapper
{
    private readonly Supabase.Client _client;

    public async Task<Session?> SignIn(string email, string password) =>
        await _client.Auth.SignIn(email, password);

    public async Task SignOut() => await _client.Auth.SignOut();

    public User? CurrentUser => _client.Auth.CurrentUser;
}

Models map directly to tables via attributes:

[Table("recettes")]
public class Recipe : BaseModel
{
    [PrimaryKey("id")] public int Id { get; set; }
    [Column("name"), Required] public string Name { get; set; } = string.Empty;
    [Column("rating"), Range(1,5)] public int Rating { get; set; }
    [Column("notes")] public string? Notes { get; set; }
    [Column("book_id")] public int? BookId { get; set; }
    [Reference(typeof(Book), joinType: ReferenceAttribute.JoinType.Left, true)]
    public Book? Book { get; set; }
}

What’s Implemented vs. Planned

AreaStatus
Auth (email/password)Implemented
Models (Recipe / Book / Author / Junction)Implemented
Basic CRUD UIImplemented (dialogs & pages)
Rating validation (1–5)Implemented + unit tests
Repository / Unit of Work abstractionDocumented pattern (not yet wired)
Caching layerPlanned (pattern drafted)
Realtime updatesEnabled in config; targeted enhancement
Full-text search & indexesPlanned (SQL examples drafted)
CI/CD pipelineDocumented; workflow template pending

Lessons So Far

  1. Supabase + Blazor cuts time-to-first-feature dramatically.
  2. Keeping models close to storage early is fine; abstractions can grow later.
  3. DataAnnotations remain “good enough” for first-phase domain constraints.
  4. MudBlazor accelerates UI but theming decisions should be centralized early.

Next Enhancements

  • Introduce a RecipeService + caching decorator
  • RLS policies enforcing per-user ownership (SQL authoring phase)
  • Add integration tests hitting a staged Supabase instance
  • Real-time listener to reflect concurrent edits

Source

GitHub: https://github.com/mongeon/RecettesIndex



See also