Skip to content

The Cratis Web Application template

The cratis template is the full-stack starting point for a Cratis application. One command gives you a running application with the whole stack wired: Arc for commands and queries over HTTP, Chronicle for the event log, MongoDB for read models, and a React frontend that talks to generated TypeScript proxies.

Terminal window
dotnet new cratis -n MyStore

You need the .NET 10 SDK (net10.0 is the only target framework), Docker with the Compose plugin, and Node.js ^20.19.0 or >=22.12.0. Getting started takes you through scaffolding, running, and registering your first item.

OptionValuesDefault
--DatabaseMongoDB, PostgreSQL, MsSql, SQLiteMongoDB
--packageManageryarn, pnpm, npm, noneyarn
--Frameworknet10.0net10.0
--SkipAiUpdateInstructionstrue, falsefalse

With a database other than MongoDB, the project references Cratis.Arc.EntityFrameworkCore instead of Cratis.Arc.MongoDB, and appsettings.json sets Cratis:Chronicle:DefaultSinkTypeId to SQL. Chronicle then projects read models into a table (Listings) in a database named chronicle+<your event store>, and ConnectionStrings:Cratis points Entity Framework Core at that database. The Chronicle container in docker-compose.yml stores its data in a PostgreSQL or SQL Server container, or, for SQLite, in a chronicle-data folder in your project that the backend reads directly:

Terminal window
dotnet new cratis -n MyStore --Database PostgreSQL
MyStore/
├── Program.cs — app setup: Arc, Chronicle, Swagger, static files
├── appsettings.json — Chronicle and database connection settings, route conventions
├── docker-compose.yml — Chronicle dev container + Aspire dashboard
├── package.json — frontend tooling (Vite, React, Tailwind)
├── .frontend/ — Vite and TypeScript configuration
├── App.tsx, Home.tsx — frontend application shell
└── SomeModule/ — a sample domain module
└── SomeFeature/ — a feature with two vertical slices
├── SomeName.cs — a ConceptAs<T> value type
├── SomeId.cs — an EventSourceId<T> identity type
├── Registration/ — command slice: Register command, Registered event, reactor, dialog
├── Listing/ — read-model slice: Listing projected from events, list page
├── SomeFeature.tsx — the feature's composition page
└── index.ts — TypeScript barrel export

The Registration slice shows the command side: a model-bound command that returns the event source id it chose plus the event to append. There is no controller, handler class, or route registration. This excerpt from SomeModule/SomeFeature/Registration/Registration.cs omits the event and reactor declared in the same file:

[Command]
public record Register(SomeName Name)
{
public (SomeId, Registered) Handle()
{
var eventSourceId = SomeId.New();
return (eventSourceId, new(Name));
}
}

SomeId is a strongly-typed identity — a Guid-backed value deriving from EventSourceId<Guid> — rather than a raw Guid. Every feature gets its own identity type this way instead of passing bare Guids around.

The Listing slice shows the read side: a read model projected from Registered events, with an observable query for all instances. Name maps from the event by convention, and [SetFromContext] copies the event source id from the event’s context. This is the MongoDB variant from Listing/Listing.cs; the other databases query CratisAppDbContext instead:

[ReadModel]
[FromEvent<Registered>]
public record Listing(
Guid Id,
SomeName Name,
[SetFromContext<Registered>(nameof(EventContext.EventSourceId))] EventSourceId EventSourceId)
{
public static ISubject<IEnumerable<Listing>> AllListings(IMongoCollection<Listing> collection) =>
collection.Observe();
}

With Chronicle and the backend running, post to the command route and the whole loop runs: Arc handles the command, Chronicle appends the event, the reactor logs it, and the projection updates the read model.

Terminal window
curl -X POST http://localhost:5000/api/some-module/some-feature/registration \
-H "Content-Type: application/json" \
-d '{ "name": "Cratis" }'

The response carries "isSuccess":true and the new identity in response, and the backend logs Registered: Cratis. Open the Demo page of the frontend to see the item in the list.

The route comes from the namespace: CratisApp.SomeModule.SomeFeature.Registration with the first segment skipped (SegmentsToSkipForRoute: 1) and the command name left out (IncludeCommandNameInRoute: false). These settings live in appsettings.json and have matching CratisProxies* properties in the .csproj; change them together, or the generated proxies call routes the backend does not map. See Proxy generator configuration.

TypeScript proxies are generated at build time

Section titled “TypeScript proxies are generated at build time”

Every dotnet build regenerates typed TypeScript proxies for your commands and queries next to their C# sources: Registration.cs produces Registration.ts. The frontend imports them and gets compile-time safety across the stack. Change a C# property and the frontend stops compiling until it catches up.

From a file in the Registration folder, you can execute the command directly:

import { Register } from './Registration';
const command = new Register();
command.name = 'Cratis';
const result = await command.execute();

The sample UI does not call execute() itself. RegisterDialog.tsx passes the Register type to a Cratis Components CommandDialog, which binds the form fields and executes the command.

Copy the SomeFeature folder shape: one folder per feature, one folder per slice, C# and TypeScript side by side. A typical feature holds a command slice, a query slice, and the React components that use them. The Cratis AI skills follow this same structure when an assistant scaffolds slices for you.

The scaffolded project includes a .cratis/ai.json with the Cratis AI profiles, languages, and coding agent harnesses for this template — the full-stack selection covering Arc, Chronicle, Components, C#, and TypeScript. Make sure the Cratis CLI is installed, then run:

Terminal window
cratis ai update

That installs the AI rules, skills, and harness integration for the coding agents you selected (AGENTS.md plus .claude/, .cursor/, .github/, .opencode/, and .pi/) and records what it installed in .cratis/ai.manifest.json. dotnet new prints this reminder after scaffolding; cratis new runs the update for you. Running it again refreshes only Cratis-managed files. See the CLI AI documentation for the full command reference.