Queries
If commands are how your system changes, queries are how it’s read. A query pulls data out and shapes it for a caller — a list, a detail view, a dashboard. Arc takes the same approach it does for commands: you express the read, and it handles parameter binding, validation, authorization, and a typed TypeScript proxy for the frontend. No hand-written API client, no untyped JSON.
And queries have a superpower commands don’t: they can be observable. An observable query holds a live connection and pushes fresh results to the client whenever the underlying data changes — so a React list re-renders the moment a command updates the read model behind it.
Two ways to define one
Section titled “Two ways to define one”| Style | What it looks like | Reach for it when |
|---|---|---|
| Model-bound | A read method declared on the model, discovered by convention | The default. Least boilerplate; the query lives with the data it reads. |
| Controller-based | A query method on a controller | You need full control over the HTTP route, or you’re integrating with existing controllers. |
A controller-based read is as plain as it looks:
[HttpGet("starting-with")]public IEnumerable<DebitAccount> StartingWith([FromQuery] string? filter) => _collection.Find(Builders<DebitAccount>.Filter.Regex( "name", $"^{filter ?? string.Empty}.*")).ToList();Parameters you declare ([FromQuery], route values) become typed arguments on the generated proxy
— so the frontend calls StartingWith.use({ filter: '' }) with the compiler checking the shape.
Request/response or live?
Section titled “Request/response or live?”This is the choice that defines a query:
| Request / response | Observable | |
|---|---|---|
| Behavior | Fetch once, return a result | Subscribe; results push on every change |
| Transport | A normal HTTP GET | SSE or WebSocket, hub-routed by default |
| Use it for | One-off reads, reports, exports | Anything a user watches — lists, dashboards, status |
Favor observable queries for screens that should stay current. The live loop — a command changes state, the read model updates, every subscribed browser re-renders — is the experience Arc is built to make effortless. In a Chronicle-backed slice that state change is an appended event and a projection; in a current-state slice it can be a MongoDB document or EF entity changing directly.
The pipeline around it
Section titled “The pipeline around it”Every query runs through a pipeline where Arc applies the cross-cutting concerns for you:
| Concern | Page |
|---|---|
| How a query is processed end to end | Query Pipeline |
| Validate query parameters | Validation |
| Authorize by role or policy | Authorization |
| Transform read models before they’re served (mask, decrypt, enrich) | Read Model Interception |
| Stream many live queries over one connection | Observable Query Hub |
| Debug a live query from the terminal | Use Observable Queries with cURL |
The payoff: typed reads in React
Section titled “The payoff: typed reads in React”As with commands, building the backend generates a typed proxy for every query. From React you call its
.use() hook and get back a strongly-typed result with its loading and validation state — see
Queries in React. One definition, read safely from C# all the
way to the component.