Create a production-ready REST API with JWT authentication, Scalar API documentation, health checks, structured logging, and comprehensive error handling using Noundry libraries.
# Clone the starter template
$ git clone https://github.com/noundry/NoundryApiStarter MyApi
# Navigate to the API project
$ cd MyApi/src/NoundryAPIStarter.Api
# Restore and run
$ dotnet restore
$ dotnet run
# API docs at https://localhost:5001/scalar/v1
Clone the Noundry API Starter from GitHub. All Noundry packages come pre-configured.
$ git clone https://github.com/noundry/NoundryApiStarter MyApi
Navigate to the API project and restore NuGet packages.
$ cd MyApi/src/NoundryAPIStarter.Api
$ dotnet restore
Update appsettings.json with your connection string.
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=MyApiDb;Username=postgres;Password=secret"
}
}
{
"Jwt": {
"Secret": "your-256-bit-secret-key-here",
"Issuer": "MyApi",
"Audience": "MyApiClients",
"ExpirationMinutes": 60
}
}
// Add a new endpoint group
var products = app.MapGroup("/api/products")
.RequireAuthorization();
products.MapGet("/", async (
[FromServices] IProductService service) =>
{
var products = await service.GetAllAsync();
return TypedResults.Ok(products);
});
products.MapPost("/", async (
CreateProductDto dto,
[FromServices] IProductService service) =>
{
var product = await service.CreateAsync(dto);
return TypedResults.Created(
$"/api/products/{product.Id}", product);
});
# Start the API
$ dotnet run
# Open Scalar API docs
# https://localhost:5001/scalar/v1
# Test with cURL
$ curl https://localhost:5001/api/products \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
MyApi/
Endpoints/ # API endpoint groups
Models/ # Entity models & DTOs
Data/ # Tuxedo repositories, queries
Services/ # Business logic
Middleware/ # Error handling, logging
Auth/ # JWT token service
Program.cs # Minimal API entry point
appsettings.json # Configuration
Dockerfile # Container support
The template uses .NET Minimal APIs with Noundry libraries pre-configured.
var builder = WebApplication.CreateBuilder(args);
// Add Noundry services
builder.Services.AddAuthnz(builder.Configuration);
builder.Services.AddTuxedo(builder.Configuration
.GetConnectionString("DefaultConnection"));
// Add Scalar API documentation
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();
// Add health checks
builder.Services.AddHealthChecks();
var app = builder.Build();
// Middleware pipeline
app.MapOpenApi();
app.MapScalarApiReference();
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/health");
app.Run();
Catches unhandled exceptions and returns consistent problem details (RFC 7807) JSON responses with correlation IDs for debugging.
Structured logging via Serilog with request correlation IDs, timing, and context enrichment for every HTTP request.
Pre-configured CORS with environment-based allowed origins. Development allows localhost, production restricts to configured domains.
Built-in sliding window rate limiter with configurable limits per client IP and authenticated user to prevent abuse.
# Authenticate and get a JWT token
$ curl -X POST https://localhost:5001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@example.com", "password": "Password123!"}'
# Use the token for authenticated requests
$ curl https://localhost:5001/api/products \
-H "Authorization: Bearer eyJhbGciOi..."
# Create a new resource
$ curl -X POST https://localhost:5001/api/products \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" \
-d '{"name": "Widget", "price": 9.99}'
# Check health
$ curl https://localhost:5001/health
When mixing [FromForm] / IFormFile with service parameters in .NET 10 minimal APIs, you must annotate services with [FromServices]. Otherwise .NET infers them as Body parameters and crashes on startup with: "An action cannot use both form and JSON body parameters".
<!-- Key packages in the API template -->
<PackageReference Include="Noundry.Authnz" />
<PackageReference Include="Noundry.Tuxedo" />
<PackageReference Include="Noundry.DotEnvX" />
<PackageReference Include="Noundry.Guardian" />
<PackageReference Include="Noundry.Sod" />
JWT authentication, API keys, and role-based authorization.
Lightweight ORM with Dapper-compatible API for database access.
Environment variable management with encryption for secrets.
Request pipeline, exception handling, and observability.
Input validation with fluent API and automatic error responses.
Use Noundry Spinup to provision a security-hardened VM on any cloud, then deploy your API with Docker.
# Install Spinup
$ dotnet tool install -g Noundry.Spinup
# Set your cloud credentials (example: DigitalOcean)
$ export DIGITALOCEAN_TOKEN=your-api-token
# Provision a VM (AWS, Azure, GCP, or DigitalOcean)
$ spinup provision --cloud digitalocean --region nyc3 --resource vm --name myapi -y
# Or use AWS
$ export AWS_ACCESS_KEY_ID=your-key
$ export AWS_SECRET_ACCESS_KEY=your-secret
$ spinup provision --cloud aws --region us-east-1 --resource vm --name myapi -y
Spinup automatically hardens the VM with UFW firewall, fail2ban, SSH key-only auth, and your IP whitelisted.
# Build for production
$ dotnet publish -c Release -o ./publish
# Or use Docker
$ docker build -t myapi .
# SSH into your VM (key auto-generated by Spinup)
$ ssh -i ~/.spinup/workspaces/myapi/myapi_ssh_key.pem ubuntu@<public-ip>
# Deploy with Docker on the VM
$ docker run -d -p 80:80 --env-file .env myapi