Introduction

Margaret is an opinionated Rust framework for web services and console applications. Its big idea: the compiler should check how your application fits together, just like it checks your types.

You describe your application with attributes. While your project builds, Margaret verifies that every piece connects, and generates the code that wires them together.

Throughout these docs, we'll build a box office that sells concert tickets. It starts as a single page and grows into a small platform: a PostgreSQL database, live seat availability, payments, sign-in with email links and Google, ticket scanners at the door, and an OpenID Connect provider that other apps sign fans in with.

A quick look

Here's the page for a single concert:

use margaret::framework::http::response::Response;
use margaret::framework::macros::process;
use margaret::framework::macros::responds_to_http;
use margaret::framework::macros::singleton;
use margaret::framework::route_method::route_method::RouteMethod;

use crate::concerts::concert_with_venue::ConcertWithVenue;

#[singleton]
#[responds_to_http(
    method = RouteMethod::Get,
    name = "show_concert",
    path = "/concerts/{concert}",
    server = "public"
)]
pub struct ShowConcert;

impl ShowConcert {
    #[process]
    pub fn respond(
        &self,
        #[route_parameter(from = "concert")] ConcertWithVenue { concert, venue }: ConcertWithVenue,
    ) -> anyhow::Result<Response> {
        Ok(Response::text(
            200,
            format!(
                "{} at {}, {}. Tickets from {} EUR.",
                concert.artist, venue.name, venue.city, concert.price
            ),
        ))
    }
}

Before this code ever runs, Margaret makes sure that:

  • the {concert} segment exists in the path,

  • ConcertWithVenue can be loaded from the database by the concert's primary key, venue included, in a single query,

  • nothing else answers GET /concerts/{concert}.

Ask for a parameter the path doesn't have, and the build stops right there:

Error: Http { source: Binding { source: RouteParameterNotInPath { subject: "responder 'crate::routes::public::show_concert::ShowConcert'", parameter: "slug", path: "/concerts/{concert}" } } }

What makes Margaret different

Checked while you build

Missing dependencies, dependency cycles, clashing routes, links to routes that don't exist, unknown views, missing assets, and WebSocket messages nobody handles are all build errors. So are database queries that would scan a table without an index, read rows without a limit, or load related rows one by one.

Exactly one way to do each thing

Margaret never picks a winner for you. When two responders answer GET /concerts/{concert}, that's a build error. Every task has a single, obvious place in your code.

Everything is explicit

Pieces connect through attributes and types, and Margaret identifies each one by its full path. Two ShowConcert structs in different modules are simply two different routes, and you can name things however you like.

Generated code you can read

Margaret writes plain Rust into a margaret module of your crate: the container, the command line, the servers, the queries for your models. You can open it, read it, and step through it. How Margaret works shows what's inside.

Settings live where they're used

When the box office needs a mail server, the constructor of the mailer asks for its address. Margaret turns those requests into typed command line flags and environment variables.

Built for services

Several HTTP servers in one process, typed WebSocket sessions, a PostgreSQL ORM, browser sessions, sign-in with any OpenID Connect provider, an OpenID Connect provider of your own, OAuth clients, mutual TLS between services, and frontend assets embedded in your binary all come included.

Is it for you?

Margaret trades flexibility for guarantees. If you like to wire things by hand or pick handlers at runtime, it will feel strict. If you'd rather have the compiler catch mistakes before you deploy, you'll feel right at home.

Margaret 0.x is an early release. APIs may change between minor versions.