Your First Minimal API
A minimal API maps each address and method straight to a lambda in Program.cs, with MapGet, MapPost, MapPut and MapDelete. The lambda's parameters come from the route and the body, and Results.NotFound(), Results.Created() and friends choose the status code.
Apprentice · ASP.NET Core · REST API · Updated
After this lesson you can
- Map GET, POST, PUT and DELETE endpoints to lambdas in a minimal API
- Return a status code with Results, such as Results.NotFound() or Results.Created()
Key takeaways
- A minimal API maps each method and address to a lambda in
Program.cswithMapGet,MapPost,MapPutorMapDelete. - A parameter such as
int idgets its value from{id}in the address; a class parameter such asTodo todocomes from the JSON body. - Return an object or a list, and it's sent as JSON with
200 OK. - Return a
Resultsmethod, such asResults.NotFound(),Results.Created(...)orResults.NoContent(), to answer with a different status code. - ASP.NET Core writes JSON property names in camelCase, so
Titlebecomestitle.
A Web API is a web app that answers with data instead of pages. A browser asks an MVC app for /products and gets back HTML to show. A program asks an API for /api/products and gets back JSON to use. Mobile apps, JavaScript in web pages, and other servers are all clients of APIs.
ASP.NET Core has two ways to build one. This lesson shows the smallest: a minimal API.
Creating the project
In Visual Studio, create a new project from the ASP.NET Core Web API template, and on the last page clear the Use controllers box. (In a terminal: dotnet new webapi -o TodoApi.) The template's Program.cs holds a sample weather forecast; replace it with the to-do list below.
A to-do list API
Here's a whole API in Program.cs: a to-do list kept in memory, with an endpoint for each thing a client can do.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
// The to-do list, kept in memory: it starts again whenever the app restarts.
List<Todo> todos = new List<Todo>
{
new Todo { Id = 1, Title = "Buy milk", Done = false },
new Todo { Id = 2, Title = "Call the dentist", Done = true }
};
app.MapGet("/todos", () => todos);
app.MapGet("/todos/{id}", (int id) =>
{
Todo? todo = todos.Find(t => t.Id == id);
if (todo == null)
{
return Results.NotFound();
}
return Results.Ok(todo);
});
app.MapPost("/todos", (Todo todo) =>
{
todo.Id = todos.Count == 0 ? 1 : todos.Max(t => t.Id) + 1;
todos.Add(todo);
return Results.Created( quot;/todos/{todo.Id}", todo);
});
app.MapPut("/todos/{id}", (int id, Todo changed) =>
{
Todo? todo = todos.Find(t => t.Id == id);
if (todo == null)
{
return Results.NotFound();
}
todo.Title = changed.Title;
todo.Done = changed.Done;
return Results.Ok(todo);
});
app.MapDelete("/todos/{id}", (int id) =>
{
int removed = todos.RemoveAll(t => t.Id == id);
return removed == 0 ? Results.NotFound() : Results.NoContent();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string Title { get; set; } = "";
public bool Done { get; set; }
}
Endpoints
Each Map... call creates an endpoint: a method and an address, joined to the code that answers it.
| Call | Answers |
|---|---|
app.MapGet("/todos", ...) |
GET /todos |
app.MapGet("/todos/{id}", ...) |
GET /todos/2, GET /todos/7, … |
app.MapPost("/todos", ...) |
POST /todos |
app.MapPut("/todos/{id}", ...) |
PUT /todos/2 |
app.MapDelete("/todos/{id}", ...) |
DELETE /todos/2 |
The code that answers is a lambda, like the ones you gave to LINQ. ASP.NET Core fills in its parameters for you:
int idcomes from the{id}in the address, converted to anint:/todos/2givesid = 2.Todo todocomes from the request's JSON body, turned into aTodoobject. That's called model binding, as in MVC.
Whatever the lambda returns becomes the response. Return a list or an object, as the first MapGet does, and it's sent as JSON with 200 OK.
Choosing the status code with Results
When 200 OK isn't the right answer, return one of the Results methods instead:
| Return | Answers |
|---|---|
Results.Ok(todo) |
200 OK, with the to-do as JSON, as the MapPut answers with the changed to-do |
Results.Created("/todos/3", todo) |
201 Created, with a Location header and the new to-do |
Results.NoContent() |
204 No Content: done, with nothing to send back, as after a DELETE |
Results.NotFound() |
404 Not Found |
Results.BadRequest("Give the to-do a title") |
400 Bad Request, with a message |
Trying it
Run the app with Ctrl+F5, and send these requests from the project's .http file (use your project's own address):
@HostAddress = http://localhost:5248
GET {{HostAddress}}/todos/9
###
POST {{HostAddress}}/todos
Content-Type: application/json
{
"title": "Water the plants",
"done": false
}
###
The first answers 404 Not Found: there's no to-do 9. The second answers:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: /todos/3
{
"id": 3,
"title": "Water the plants",
"done": false
}
JSON property names start with a small letter (title, done): ASP.NET Core converts C#'s Title to the camelCase that JavaScript clients expect, and back again when it reads a body.
Stop the app and start it again, and the new to-do is gone: the list only lives in memory. Real APIs keep their data in a database, as the next topic's do.
Check yourself
2 questions to try on the lesson page.
So what
A minimal API is an address, a method and a lambda, repeated for each thing a client can do. It's the quickest way to put data on the web, and the same ideas, endpoints, model binding and status codes, carry straight over to controllers.