Skip to content

Proxy Generation

Cratis Arc automatically generates TypeScript proxies for your backend commands and queries, providing seamless integration between your React frontend and backend APIs.

Setup and Configuration: For detailed information about setting up proxy generation, configuration options, and build integration, see the Backend Proxy Generation documentation.

One of the key benefits of the generated proxies is compile-time type safety. Your IDE will provide:

  • IntelliSense: Auto-completion for all available commands and queries
  • Type Checking: Compile-time verification of parameter types and return values
  • Refactoring Support: Automatic updates when backend APIs change
  • Navigation: Go-to-definition support for exploring generated types

This eliminates the need to reference Swagger documentation or manually write API integration code, as everything is generated and type-safe directly in your TypeScript codebase.

Once proxy generation is configured in your backend projects, you’ll get automatically generated TypeScript proxies that provide compile-time type safety and intellisense support for all your commands and queries.

Install the base @cratis/arc NPM package in your React project, as the generated proxies inherit from and leverage types found in this package:

Terminal window
npm install @cratis/arc

Commands represent actions you want to perform and correspond to HttpPost operations on your backend controllers. The generated proxies inherit from the Command type found in @cratis/arc/commands and provide type-safe access to all command parameters.

Arc supports both backend command styles and generates equivalent TypeScript proxies for each:

For a backend controller action like this:

[Route("/api/accounts/debit")]
public class DebitAccounts : Controller
{
[HttpPost]
public Task OpenDebitAccount([FromBody] OpenDebitAccount create)
{
// Implementation...
}
}

You’ll get a generated TypeScript command that flattens all parameters (from route, query string, and body) into properties:

import { OpenDebitAccount } from './generated/commands';
const command = new OpenDebitAccount();
// Set properties and execute

For a model-bound command like this:

[Command]
public record OpenDebitAccount(string Name, decimal InitialBalance)
{
public Task Handle(IEventLog eventLog)
{
// Implementation...
return Task.CompletedTask;
}
}

You’ll get a generated TypeScript command with the same command name and strongly typed properties:

import { OpenDebitAccount } from './generated/commands';
const command = new OpenDebitAccount();
command.name = 'Primary account';
command.initialBalance = 500;

The generated command automatically handles route parameters, query string arguments, and request body serialization.

For detailed information on using commands in React, see the Commands documentation.

Queries represent data retrieval operations that correspond to HttpGet operations on your backend controllers. They can return either single items or collections and support parameters from routes or query strings.

Arc supports both backend query styles and generates equivalent TypeScript query proxies for each:

For a backend controller action like this:

[HttpGet]
public IEnumerable<DebitAccount> AllAccounts()
{
// Get data and return
}

You’ll get a generated TypeScript query that provides a React hook via the .use() method:

import { AllAccounts } from './generated/queries';
const MyComponent = () => {
const [result, perform] = AllAccounts.use();
// result is of type `QueryResultWithState<DebitAccount[]>`
// Contains: data, isPerforming, error, etc.
return (
<div>
{result.isPerforming && <span>Loading...</span>}
{result.data?.map(account => <div key={account.id}>{account.name}</div>)}
</div>
);
};

For a model-bound query like this:

[ReadModel]
public record DebitAccount(AccountId Id, AccountName Name, CustomerId Owner, decimal Balance)
{
public static IEnumerable<DebitAccount> GetAllAccounts(IMongoCollection<DebitAccount> collection)
=> collection.Find(_ => true).ToList();
}

You’ll get a generated TypeScript query proxy with the same ergonomic hook pattern:

import { GetAllAccounts } from './generated/queries';
const MyComponent = () => {
const [result, perform] = GetAllAccounts.use();
return (
<div>
{result.isPerforming && <span>Loading...</span>}
{result.data?.map(account => <div key={account.id}>{account.name}</div>)}
</div>
);
};

The return type QueryResultWithState<> provides additional metadata about the query state, including whether the query is currently executing (isPerforming), making it easy to implement loading indicators and error handling.

Every generated query proxy also exposes useSuspense() and useSuspenseWithPaging() static methods. These forward to useSuspenseQuery / useSuspenseObservableQuery and are designed for use inside QueryBoundary (or a <Suspense> + QueryErrorBoundary pair), where the component suspends while data loads and any server-side errors are propagated to the boundary.

import { AllAccounts } from './generated/queries';
function AccountList() {
// Component suspends until the query resolves
const [result, perform] = AllAccounts.useSuspense();
return (
<ul>
{result.data.map(account => <li key={account.id}>{account.name}</li>)}
</ul>
);
}

See Suspense Queries for full documentation and error handling patterns.

Observable queries provide real-time updates to your React components, typically using WebSockets for live data synchronization.

Backend Setup: To learn how to implement observable queries on the backend, see Controller-based Observable Queries and Model-bound Observable Queries.

Observable queries are generated the same way as regular queries, but they don’t provide a manual perform method in the returned tuple. Instead, they automatically subscribe to updates and re-render your React components when the underlying data changes, providing a transparent and seamless real-time experience.

The proxy generator maintains your backend folder structure while generating TypeScript files based on the namespaces of your source files. Each namespace segment typically becomes a subfolder in the generated output.

For detailed information about configuring the output structure, including how to skip namespace segments and customize the generated folder hierarchy, see the Backend Proxy Generation Configuration section.