What makes a Web API "done" vs "shippable"
We have reviewed countless student APIs that return 200 OK for everything and store passwords in plain text. Shippable APIs handle errors predictably, validate input at the boundary, authenticate callers, log structured data, and deploy repeatably. ASP.NET Core gives you the building blocks; this guide shows how we wire them together for real services.
Project structure that scales
Start with the minimal hosting model in .NET 8, then split by feature or layer as complexity grows:
- Api — controllers or minimal endpoints, filters, middleware.
- Application — use cases, DTOs, validators, interfaces.
- Domain — entities and domain rules without EF attributes if you want clean architecture.
- Infrastructure — EF Core, email, external HTTP clients.
For a learning project, a single project is fine. For team work, separation pays off when you test application logic without spinning up a database.
Minimal APIs vs controllers
Minimal APIs are excellent for microservices and small surfaces. Controllers shine when you have many endpoints, consistent conventions, and filter attributes. Pick one style per service—mixing both without reason confuses reviewers.
app.MapPost("/orders", async (CreateOrderRequest req, IOrderService svc) =>
{
var result = await svc.CreateAsync(req);
return result.IsSuccess
? Results.Created($"/orders/{'{'}result.Value.Id{'}'}", result.Value)
: Results.ValidationProblem(result.Errors);
});
Validation and problem details
Use FluentValidation or data annotations, but always return RFC 7807 Problem Details for client errors. Consistent error shapes matter more than whether you prefer FluentValidation syntax.
Return 404 for missing resources, 409 for conflicts, 422 or 400 for validation—document your choices in OpenAPI.
OpenAPI and client generation
Enable Swashbuckle or built-in OpenAPI in .NET 9+. Treat the spec as a contract: version breaking changes, generate TypeScript or C# clients for frontends and BFFs, and run contract tests in CI when the API is consumed by multiple teams.
Authentication and authorization
JWT for SPAs and mobile
Issue short-lived access tokens, rotate refresh tokens carefully, and validate issuer, audience, and signing keys. Store secrets in user secrets locally and Key Vault in production.
Policies and claims
Prefer policy-based authorization over sprinkling role checks in every action. Define policies like CanManageOrders that map to claims or requirements.
Data access patterns
Inject DbContext scoped per request. Use repositories only if they add value—many teams use DbContext directly with clear query objects or specifications. Always async all the way to the database for I/O-bound endpoints.
Add pagination for list endpoints: cursor-based for high-volume feeds, offset for admin screens with modest data.
Cross-cutting concerns
- Logging — Serilog with JSON output; include correlation IDs from middleware.
- Health checks — liveness vs readiness; check SQL, Redis, downstream HTTP.
- Rate limiting — built-in middleware in .NET 7+ for public APIs.
- CORS — explicit origins; never
*with credentials.
Testing strategy
- Unit test application services with mocked interfaces.
- Integration tests with
WebApplicationFactoryhitting real HTTP pipeline and test database. - Smoke tests post-deploy against staging environment.
Deployment options
Docker
Multi-stage Dockerfile: restore and publish in build stage, run on aspnet runtime image. Set ASPNETCORE_URLS, run as non-root user, and pass configuration via environment variables.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
Azure App Service
Deploy from GitHub Actions: build, test, publish zip or container, swap slots for zero-downtime releases. Enable Application Insights for requests, dependencies, and exceptions.
Kubernetes
When you outgrow App Service: Deployments, Services, Ingress, horizontal pod autoscaling, and secrets from Azure Key Vault provider. Health probe paths must match your readiness endpoint.
Production checklist
- HTTPS enforced, HSTS enabled.
- No detailed exception messages to clients in production.
- Database migrations automated or run with controlled pipeline step.
- Secrets rotated; no connection strings in repo.
- Backup and restore tested for SQL database.
Building the API is half the job; operating it is the rest. If you can deploy on every merge to main, roll back in minutes, and explain your auth model to a new teammate, you are operating at production level—not merely tutorial level.