Backend Systemsbeginner
Your First Minimal API from Scratch
Build your first ASP.NET Core Minimal API: create an endpoint, add routing, use dependency injection, and return typed JSON responses in under 20 minutes.
Asma HafeezApril 17, 20263 min read
dotnetminimal-apiaspnet-corerestbeginner
Your First Minimal API from Scratch
Minimal APIs in ASP.NET Core let you build HTTP endpoints with minimal boilerplate — no controllers, no attributes.
Create the Project
Bash
dotnet new web -n MyFirstApi
cd MyFirstApi
dotnet runOpen http://localhost:5000 — you'll see "Hello World!".
Program.cs Explained
C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();That's the whole API. Four lines.
Add Real Endpoints
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// In-memory data store for demo
var products = new List<Product>
{
new(1, "Laptop", 999.99m),
new(2, "Mouse", 29.99m),
new(3, "Keyboard", 79.99m),
};
// GET all products
app.MapGet("/products", () => products);
// GET single product
app.MapGet("/products/{id:int}", (int id) =>
{
var product = products.FirstOrDefault(p => p.Id == id);
return product is not null
? Results.Ok(product)
: Results.NotFound(new { message = $"Product {id} not found" });
});
// POST create
app.MapPost("/products", (CreateProductRequest req) =>
{
var id = products.Count > 0 ? products.Max(p => p.Id) + 1 : 1;
var product = new Product(id, req.Name, req.Price);
products.Add(product);
return Results.Created($"/products/{id}", product);
});
// PUT update
app.MapPut("/products/{id:int}", (int id, CreateProductRequest req) =>
{
var index = products.FindIndex(p => p.Id == id);
if (index < 0) return Results.NotFound();
products[index] = new Product(id, req.Name, req.Price);
return Results.Ok(products[index]);
});
// DELETE
app.MapDelete("/products/{id:int}", (int id) =>
{
var product = products.FirstOrDefault(p => p.Id == id);
if (product is null) return Results.NotFound();
products.Remove(product);
return Results.NoContent();
});
app.Run();
// Models
record Product(int Id, string Name, decimal Price);
record CreateProductRequest(string Name, decimal Price);Route Parameters and Query Strings
C#
// Route parameter: /products/42
app.MapGet("/products/{id:int}", (int id) => Results.Ok(id));
// Query string: /products?search=laptop&maxPrice=500
app.MapGet("/products", (string? search, decimal? maxPrice) =>
{
var result = products.AsEnumerable();
if (search is not null)
result = result.Where(p => p.Name.Contains(search, StringComparison.OrdinalIgnoreCase));
if (maxPrice.HasValue)
result = result.Where(p => p.Price <= maxPrice.Value);
return Results.Ok(result.ToList());
});
// Multiple route parameters
app.MapGet("/categories/{category}/products/{id:int}", (string category, int id) =>
Results.Ok(new { category, id }));Dependency Injection
C#
// Register a service
builder.Services.AddScoped<IProductService, ProductService>();
// Inject in endpoint
app.MapGet("/products/{id:int}", async (int id, IProductService service) =>
{
var product = await service.GetByIdAsync(id);
return product is not null ? Results.Ok(product) : Results.NotFound();
});
// Inject multiple dependencies
app.MapPost("/orders", async (
CreateOrderRequest req,
IOrderService orders,
IEmailService email,
ILogger<Program> logger) =>
{
var order = await orders.CreateAsync(req);
await email.SendConfirmationAsync(order.Id);
logger.LogInformation("Order {OrderId} created", order.Id);
return Results.Created($"/orders/{order.Id}", order);
});Grouping Endpoints
C#
var productsGroup = app.MapGroup("/products");
productsGroup.MapGet("/", () => "Get all products");
productsGroup.MapGet("/{id}", (int id) => $"Get product {id}");
productsGroup.MapPost("/", () => "Create product");
// With auth
var adminGroup = app.MapGroup("/admin")
.RequireAuthorization("AdminPolicy");
adminGroup.MapDelete("/products/{id}", (int id) => Results.NoContent());Key Takeaways
- No controller classes needed — map endpoints directly in
Program.csor group them - Parameters are auto-bound from route, query string, or request body based on source
- Use
Results.Ok(),Results.NotFound(),Results.Created()etc. for proper HTTP responses - Minimal APIs support full DI — inject services directly in lambda parameters
- Add
AddSwaggerGen()in development for instant API documentation
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.