Back to blog
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
Share:𝕏

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 run

Open 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

  1. No controller classes needed — map endpoints directly in Program.cs or group them
  2. Parameters are auto-bound from route, query string, or request body based on source
  3. Use Results.Ok(), Results.NotFound(), Results.Created() etc. for proper HTTP responses
  4. Minimal APIs support full DI — inject services directly in lambda parameters
  5. Add AddSwaggerGen() in development for instant API documentation

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.