Creating Your First Minimal API
Bootstrap a Minimal API project, define route handlers, and return typed results with minimal boilerplate.
Creating Your First Minimal API is a free C# Academy lesson on CoddyKit. This is lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, and your progress syncs across the web and the CoddyKit app. The C# Academy course includes 4 lessons in total.
What Are Minimal APIs?
Minimal APIs, introduced in .NET 6, let you build HTTP endpoints with minimal ceremony — no controllers, no action attributes, just route handlers defined directly in Program.cs. They're ideal for microservices and lightweight APIs.
The Simplest Minimal API
A complete HTTP API in just a few lines. The MapGet, MapPost, MapPut, and MapDelete methods define route handlers for each HTTP verb.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello, Minimal API!");
app.MapGet("/ping", () => Results.Ok(new { status = "pong" }));
app.Run();
// That's it — no Startup.cs, no controllersTyped Results for Proper HTTP Responses
Use Results or TypedResults for correct HTTP status codes and content types. TypedResults is preferred for OpenAPI schema inference.
app.MapGet("/products/{id}", async (int id, AppDbContext db) =>
{
var product = await db.Products.FindAsync(id);
return product is null
? Results.NotFound()
: Results.Ok(product);
});
app.MapPost("/products", async (Product product, AppDbContext db) =>
{
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Created($"/products/{product.Id}", product);
});Route Parameters & Query Strings
Route parameters are captured from the URL path, query string parameters are automatically bound from the query, and the request body is deserialized from JSON.
// Route param {id} + query param ?includeDeleted
app.MapGet("/orders/{id}", async (
int id,
bool includeDeleted = false,
AppDbContext db) =>
{
var query = db.Orders.AsQueryable();
if (!includeDeleted) query = query.Where(o => !o.IsDeleted);
var order = await query.FirstOrDefaultAsync(o => o.Id == id);
return order is null ? Results.NotFound() : Results.Ok(order);
});Dependency Injection in Route Handlers
Services registered in the DI container can be injected directly as route handler parameters. The framework resolves them automatically.
builder.Services.AddScoped<ProductService>();
app.MapGet("/products", async (ProductService svc) =>
{
var products = await svc.GetAllAsync();
return Results.Ok(products);
});
app.MapDelete("/products/{id}", async (int id, ProductService svc) =>
{
var deleted = await svc.DeleteAsync(id);
return deleted ? Results.NoContent() : Results.NotFound();
});Request Body Binding
Parameters that match registered services are injected; everything else is bound from the request body (JSON by default). Use [FromBody] explicitly if needed.
record CreateProductRequest(string Name, decimal Price, int Stock);
app.MapPost("/products", async (
CreateProductRequest req,
ProductService svc) =>
{
var product = await svc.CreateAsync(req.Name, req.Price, req.Stock);
return TypedResults.Created($"/products/{product.Id}", product);
});Returning Different Status Codes
Results provides factory methods for all common HTTP responses. Use them for semantically correct REST APIs.
app.MapPut("/products/{id}", async (int id, Product update, AppDbContext db) =>
{
var existing = await db.Products.FindAsync(id);
if (existing is null) return Results.NotFound();
existing.Name = update.Name;
existing.Price = update.Price;
await db.SaveChangesAsync();
return Results.Ok(existing);
});
// Other useful Results:
// Results.BadRequest("message")
// Results.Conflict()
// Results.UnprocessableEntity(errors)
// Results.Accepted()Adding Metadata with WithName & WithTags
Attach metadata to endpoints to improve documentation and routing. Use WithName, WithTags, and WithSummary for organized OpenAPI output.
app.MapGet("/products/{id}", GetProduct)
.WithName("GetProductById")
.WithTags("Products")
.WithSummary("Retrieves a product by its ID")
.Produces<Product>()
.Produces(404);
static async Task<IResult> GetProduct(int id, AppDbContext db)
{
var p = await db.Products.FindAsync(id);
return p is null ? Results.NotFound() : Results.Ok(p);
}Authorization in Minimal APIs
Apply RequireAuthorization() to protect endpoints, or use AllowAnonymous() to opt out. Policies work the same as in controllers.
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/profile", (ClaimsPrincipal user) =>
Results.Ok(user.Identity!.Name))
.RequireAuthorization();
app.MapGet("/public", () => "No auth needed")
.AllowAnonymous();Organizing with Static Methods
For larger APIs, move route handlers to static methods or extension methods to keep Program.cs clean and navigable.
// Extension method groups endpoints by feature
public static class ProductEndpoints
{
public static void MapProductEndpoints(this WebApplication app)
{
app.MapGet("/products", GetAll);
app.MapGet("/products/{id}", GetById);
app.MapPost("/products", Create);
}
private static async Task<IResult> GetAll(AppDbContext db)
=> Results.Ok(await db.Products.AsNoTracking().ToListAsync());
// ... other handlers
}
// In Program.cs:
app.MapProductEndpoints();Real-World: Full CRUD Minimal API
A complete minimal CRUD API for a Todo resource — concise, testable, and production-ready.
app.MapGet("/todos", async (AppDbContext db) =>
Results.Ok(await db.Todos.AsNoTracking().ToListAsync()));
app.MapGet("/todos/{id}", async (int id, AppDbContext db) =>
{
var todo = await db.Todos.FindAsync(id);
return todo is null ? Results.NotFound() : Results.Ok(todo);
});
app.MapPost("/todos", async (Todo todo, AppDbContext db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todos/{todo.Id}", todo);
});
app.MapDelete("/todos/{id}", async (int id, AppDbContext db) =>
{
int n = await db.Todos.Where(t => t.Id == id).ExecuteDeleteAsync();
return n > 0 ? Results.NoContent() : Results.NotFound();
});Quick Check
How does a Minimal API route handler receive a registered DI service?
Recap: Creating Your First Minimal API
Key takeaways:
- MapGet/Post/Put/Delete define route handlers directly in Program.cs
- Use
Results/TypedResultsfor correct HTTP status codes - Route, query, body, and DI parameters are all bound automatically
- Use
RequireAuthorization()andAllowAnonymous()for auth - Organize large APIs with extension methods or route groups
Learn C# with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 93
- Lessons
- 346
Frequently Asked Questions
Is the “Creating Your First Minimal API” lesson free?
Yes — the full text of “Creating Your First Minimal API” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO. The C# Academy course includes 4 lessons in total.
What will I learn in “Creating Your First Minimal API”?
Bootstrap a Minimal API project, define route handlers, and return typed results with minimal boilerplate. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start C# Academy?
No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 1 of 4.
How long does the “Creating Your First Minimal API” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this C# Academy lesson?
Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Creating Your First Minimal API
- Route Groups, Parameters & Validation
- Middleware & Filters in Minimal APIs
- OpenAPI, Versioning & Deployment