Skip to content
.NET 4 min read

React + .NET Web API: A Practical Architecture

How I structure a React front end against a .NET Web API — project layout, the API contract, authentication and the boundaries that keep both sides independent.

By

Starter draft — adapt the specifics to the conventions you prefer before publishing.

React and .NET is a pairing I have used on several enterprise applications. Neither side is interesting on its own; what determines whether the project stays maintainable is the boundary between them.

Keep the boundary explicit#

The single decision that matters most: the API is the contract, and neither side reaches across it.

  • The front end does not know table names, ORM entities, or how a value is computed.
  • The API does not know about component structure, routing, or how a screen is laid out.

When that boundary blurs — an endpoint shaped around one screen, a response that mirrors a database table — every UI change becomes a backend change.

Project layout#

A structure that has worked well for medium-sized applications:

txt
src/
  Api/                    # ASP.NET Core Web API — controllers, DI, middleware
  Application/            # Use cases, DTOs, validation, business rules
  Domain/                 # Entities and domain logic, no infrastructure
  Infrastructure/         # EF Core, external services, implementations
  Web/                    # React application
    src/
      api/                # Typed client; one module per resource
      components/         # Reusable, presentational
      features/           # Feature folders: screens + local state
      hooks/
      lib/
      types/              # Shared types mirroring API DTOs

The important property is not the exact folder names — it is that business rules live somewhere that does not depend on ASP.NET or on Entity Framework, so they can be tested without either.

Design the API around resources, not screens#

Endpoints shaped around a particular page age badly, because pages change more often than the domain does.

txt
GET    /api/v1/orders?status=placed&page=1&pageSize=25
GET    /api/v1/orders/{id}
POST   /api/v1/orders
PATCH  /api/v1/orders/{id}/status

Conventions worth fixing early, because they are painful to change later:

  • Versioning in the path. /api/v1/ costs nothing now and buys a migration path later.
  • One envelope for collections. { data, meta: { page, pageSize, total } } everywhere.
  • One error shape. RFC 7807 problem details, or your own equivalent — but the same one for every endpoint.
  • Explicit status codes. 400 for validation, 401 versus 403 used correctly, 409 for conflicts, 422 where it applies.
json
{
  "type": "https://example.com/errors/validation",
  "title": "Validation failed",
  "status": 400,
  "errors": {
    "customerId": ["Customer does not exist."]
  }
}

If the front end can handle errors with one function, the contract is consistent enough.

A typed client on the front end#

Rather than scattering fetch calls through components, keep one module per resource:

ts
// api/orders.ts
import { http } from './http';
import type { Order, OrderStatus, Paginated } from '../types';

export const listOrders = (params: { status?: OrderStatus; page?: number }) =>
  http.get<Paginated<Order>>('/orders', { params });

export const getOrder = (id: string) => http.get<Order>(`/orders/${id}`);

export const updateOrderStatus = (id: string, status: OrderStatus) =>
  http.patch<Order>(`/orders/${id}/status`, { status });

Components then depend on functions with meaningful names and types, not on URLs. When an endpoint changes, one file changes.

Where the DTOs are stable, generating the TypeScript types from the OpenAPI document removes a whole category of drift between the two codebases.

Authentication#

For enterprise applications this usually means an identity provider rather than rolling your own: the SPA acquires a token, the API validates it, and authorisation decisions are made server-side against the token's claims.

Two rules that are not worth bending:

  1. The UI hides actions; the API enforces them. Hiding a button is a usability improvement, not a security control.
  2. Tokens are handled deliberately. Where they are stored, when they refresh, and what happens to an in-flight request when one expires.

What belongs where#

Concern Front end API
Input formatting and immediate feedback Yes —
Validation that must hold Mirror for UX Authoritative
Authorisation Render from server's answer Authoritative
Business calculations No Yes
Pagination, filtering, sorting Requests it Performs it

Anything marked authoritative on the API side may be duplicated on the front end for user experience, but never moved there.

Things I would decide early#

  • Response envelope and error shape
  • Pagination style and its defaults
  • Date and time handling — store and transmit UTC, format at the edge
  • Correlation identifiers flowing from the browser through to the logs
  • Where validation messages come from, so the UI is not translating error codes by hand

None of these are difficult decisions. They are just expensive to change once fifty endpoints exist.

Share this article LinkedIn X

Keep reading

Related articles

All articles
Career / Developer Experience 3 min read

What I Learned Working Across Multiple Technology Stacks

Seven years across .NET, PHP, React, Angular and Flutter. The frameworks differ far less than they appear to — and what transfers is more valuable tha...

#Career #.NET #Laravel
↑ ↓ to navigate · ↵ to open · esc to close Full search