A React front-end paired with a .NET back-end is one of the most productive stacks around — but the two ends pull in different directions on structure. React gives you a clean component tree; the classic .NET controller stack, by comparison, can feel heavy for what is often a straightforward JSON API. Minimal APIs fixed the weight problem, and Carter fixes the one thing Minimal APIs left open: organisation. Here is the setup we reach for on modern React + .NET projects.
Why Minimal APIs — and where they get messy
Minimal APIs let you define an endpoint in a single expressive line, with far less ceremony than a controller. The trade-off shows up as the project grows: everything tends to pile into Program.cs, and a hundred app.MapGet(...) calls in one file is its own kind of mess. You have traded boilerplate for a lack of structure.
Carter: structure without the weight
Carter is a thin library over ASP.NET Core routing that reintroduces modularity without dragging back the full controller machinery. You group related endpoints into a module — a small class implementing ICarterModule — and Carter discovers and wires them up for you. Registration is two lines:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCarter();
var app = builder.Build();
app.MapCarter();
app.Run();
An endpoint module
Each feature gets its own module. Everything about “products” lives in one place, close together and easy to find:
public class ProductModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/products");
group.MapGet("/", async (IProductService svc) =>
Results.Ok(await svc.GetAllAsync()));
group.MapGet("/{id:int}", async (int id, IProductService svc) =>
await svc.FindAsync(id) is { } p
? Results.Ok(p)
: Results.NotFound());
group.MapPost("/", async (CreateProduct cmd, IProductService svc) =>
{
var created = await svc.CreateAsync(cmd);
return Results.Created($"/api/products/{created.Id}", created);
});
}
}
Add a new feature area and you add a new module — you never touch a growing central file. That is the property that keeps a codebase pleasant to work in as it scales.
Validation that stays out of the handler
Carter integrates cleanly with FluentValidation, so input rules live in a validator rather than cluttering the endpoint. The handler stays focused on the happy path, and invalid requests are turned away with a proper problem response before your logic runs:
public class CreateProductValidator : AbstractValidator<CreateProduct>
{
public CreateProductValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(120);
RuleFor(x => x.Price).GreaterThan(0);
}
}
How it fits the React side
From React’s point of view this is just a clean, predictable JSON API — fetch or your query library of choice against /api/products, typed responses, standard status codes. The value of Carter is entirely on the back-end: your endpoints are grouped by feature the same way your React components are grouped by feature, so the two halves of the app mirror each other and a developer can move between them without re-learning the layout.
Why we like this stack
- Low ceremony. Minimal APIs keep each endpoint to what it actually does.
- Real structure. Carter modules keep features isolated and discoverable as the project grows.
- Clean separation. Validation, handlers, and services stay in their own lanes.
- A back-end shaped like the front-end. Feature-per-module on the server mirrors feature-per-folder in React.
Takeaway
Minimal APIs solved .NET’s boilerplate problem; Carter solves the organisation problem they left behind, without dragging back the weight of controllers. For a React front-end talking to a .NET back-end, it is a lightweight, tidy foundation that ages well. We build exactly this kind of React + .NET application for e-commerce and logistics platforms — get in touch if you are starting one or cleaning one up.