Skip to content

Quickstart ​

This page is the smallest generic setup path for trying CrateStack in a Rust service.

1. Include A Schema ​

Example schema:

cstack
auth Principal {
  id String
  role String?
}

mixin AuditFields {
  createdAt DateTime @default(dbgenerated())
  updatedAt DateTime @default(dbgenerated())
}

model Post {
  @use(AuditFields)

  id String @id
  title String

  @@allow("read", auth() != null)
}

Pick the macro that matches the crate's role:

rust
// Server crate
use cratestack::include_server_schema;

include_server_schema!("schema.cstack", db = Postgres);

db = Postgres is the argument for a schema with a real datasource { provider = "postgresql" } block. A schema declaring datasource { provider = "none" } instead pairs with db = None — see Procedures-only servers below. The two must agree: passing db = Postgres against a provider = "none" schema (or vice versa) is a compile-time macro error, not a runtime one.

Embedded (mobile / desktop / browser-via-OPFS) and HTTP-client crates use different macros — see Offline-First Embedded SQLite and the client-runtime docs respectively.

2. Build The Generated Runtime ​

rust
let pool = sqlx::PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
let cool = cratestack_schema::Cratestack::builder(pool).build();

3. Use Delegates Directly ​

You can use CrateStack without mounting the generated REST layer.

rust
let visible_posts = cool
    .post()
    .find_many()
    .where_expr(
        cratestack_schema::post::author()
            .email()
            .eq("owner@example.com")
            .and(cratestack_schema::post::published().is_true()),
    )
    .order_by(cratestack_schema::post::author().email().desc())
    .limit(20)
    .run(&ctx)
    .await?;

A relation filter or sort such as author() reads the related model's rows through that model's own read policy and @@soft_delete filter (since 0.13.0): an author the caller may not read behaves as if it did not exist. If the related model declares no rule that allows reading it (@@allow for read, list or all), the filter matches nothing. See Relation filters and sorts.

4. Optionally Bind Auth For Internal Callers ​

rust
let bound = cool.bind_auth(Some(serde_json::json!({
    "id": 7,
    "role": "admin"
})))?;

let posts = bound.post().find_many().run().await?;

5. Optionally Mount Generated Routers ​

rust
let app = axum::Router::new().nest(
    "/api",
    cratestack_schema::axum::model_router(
        cool.clone(),
        (),
        cratestack_codec_cbor::CborCodec,
        AppAuthProvider,
    ),
);

The () in second position is the computed-field resolver. A schema with no @computed fields needs no resolver at all, and the macro generates impl ComputedFieldResolver for () so the unit type satisfies the bound. Replace it with your own resolver once you declare a computed field — see Computed Fields.

Procedures-only servers (no database) ​

Not every server owns a database — see Procedures-Only Servers with No Database for datasource { provider = "none" }, db = None, and the cratestack-api facade.

  1. ../guides/auth-provider for the host auth boundary
  2. ../architecture/transport-architecture for codec, framing, and envelope rules
  3. ../overview/banking-readiness for the regulated-workload primitives
  4. ../guides/offline-first-sqlite if you want to run the same schema embedded against a local SQLite database — same .cstack file, sync API on native, OPFS-backed in the browser, no tokio
  5. ../guides/no-database-procedures for procedures-only servers with no database at all

Released under the MIT License.