Minimal API adalah gaya membangun HTTP API pada ASP.NET Core yang mendaftarkan endpoint langsung pada objek aplikasi tanpa kelas controller. Satu pemanggilan MapGet atau MapPost sudah menghasilkan endpoint yang siap melayani request, sedangkan serialisasi JSON dan pembacaan parameter rute ditangani kerangka kerja secara otomatis. Topik ini membahas struktur dasar aplikasi minimal API, pendaftaran rute beserta parameternya, dan cara tabel routing mencocokkan request dengan endpoint, ditutup dengan demo yang menjalankan server dan menguji endpoint dalam satu proses sehingga keluarannya dapat direproduksi.
Struktur dasar aplikasi minimal API#
Setiap aplikasi minimal API tersusun dari tiga langkah, yaitu menyiapkan builder, membangun aplikasi, dan mendaftarkan endpoint pada objek hasilnya. WebApplication.CreateBuilder menyiapkan konfigurasi, layanan, dan logging, sedangkan Build() mengemas semuanya menjadi objek WebApplication. Endpoint didaftarkan dengan metode MapGet, MapPost, MapPut, dan MapDelete yang menerima pola rute beserta handler berupa lambda, yaitu fungsi tanpa nama yang dijalankan setiap kali rutenya dipanggil. Pada bentuk satu berkas, arahan #:sdk Microsoft.NET.Sdk.Web di baris pertama menggantikan berkas proyek karena membawa referensi kerangka kerja web beserta implicit usings-nya, sehingga satu berkas Program.cs sudah cukup untuk aplikasi yang dapat dijalankan.
Metode app.Run() adalah cara termudah menjalankan server karena menahan proses sampai dihentikan, tetapi perilaku menahan itu tidak cocok untuk demo yang harus menguji endpoint dari proses yang sama. Demo pada topik ini karenanya menetapkan alamat dengan app.Urls.Add, memulai server dengan app.StartAsync(), dan menghentikannya dengan app.StopAsync(). Dengan pola tersebut, HttpClient dapat memanggil endpoint milik prosesnya sendiri, keluaran yang dicetak selalu sama pada setiap eksekusi, dan tidak ada proses latar belakang yang perlu ditutup manual.
Bentuk paling ringkas dari struktur tersebut justru menyerupai berkas yang dihasilkan templat dotnet new web, seperti berikut.
// tidak-dijalankan
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Halo, dunia!");
app.Run();Potongan di atas tidak dijalankan karena app.Run() menahan proses sampai dihentikan, sedangkan demo pada bagian berikutnya membutuhkan proses yang dapat menguji dirinya sendiri.
Mendaftarkan endpoint dan membaca parameternya#
Pola rute pada MapGet berisi segmen tetap dan segmen parameter. Segmen tetap seperti /tugas dicocokkan apa adanya, sedangkan segmen {id:int} menyatakan parameter bernama id dengan batasan :int yang hanya cocok untuk bilangan bulat. Nilai parameter diteruskan ke handler sebagai argumen, sehingga lambda dengan parameter int id langsung menerima angka yang sudah diurai. Bentuk keluaran handler mengikuti tipenya, yaitu string dikirim sebagai teks biasa, sedangkan objek dan kumpulan objek diserialisasi menjadi JSON dengan nama property gaya camelCase.
Aplikasi satu berkas menuntut metadata JSON yang dinyatakan secara eksplisit. Kelas KonteksJson yang diberi atribut JsonSerializable pada setiap tipe yang dikembalikan menyediakan metadata itu, dan ConfigureHttpJsonOptions mengarahkan serializer ke konteks tersebut. Pada proyek csproj biasa, langkah ini tidak diperlukan karena metadata refleksi sudah tersedia secara default.
Program berikut menyusun API daftar tugas dengan tiga endpoint, menjalankannya pada alamat tetap, lalu memanggil lima variasi request untuk memperlihatkan pencocokan rute yang berhasil maupun yang gagal.
#:sdk Microsoft.NET.Sdk.Web
// Program: API minimal daftar tugas yang dijalankan dan diuji dalam satu proses
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
// provider log dibuang agar keluaran demo tidak memuat baris log server
builder.Logging.ClearProviders();
// metadata JSON diarahkan ke konteks sumber hasil generasi di bagian bawah berkas
builder.Services.ConfigureHttpJsonOptions(o =>
o.SerializerOptions.TypeInfoResolver = KonteksJson.Default);
var app = builder.Build();
var daftar = new List<Tugas>
{
new(1, "Kirim laporan"),
new(2, "Tinjau rancangan"),
};
// setiap MapGet mendaftarkan satu endpoint beserta pola rutenya
app.MapGet("/", () => "API tugas siap melayani");
app.MapGet("/tugas", () => daftar);
app.MapGet("/tugas/{id:int}", (int id) =>
{
var tugas = daftar.FirstOrDefault(t => t.Id == id);
return tugas is null ? Results.NotFound() : Results.Ok(tugas);
});
// alamat ditetapkan tetap agar keluaran demo dapat direproduksi
app.Urls.Add("http://127.0.0.1:5151");
await app.StartAsync();
var klien = new HttpClient();
var alamat = "http://127.0.0.1:5151";
// helper: satu request mencetak baris status lalu isi responsnya
async Task Cetak(string jalur)
{
HttpResponseMessage res = await klien.GetAsync($"{alamat}{jalur}");
Console.WriteLine($"GET {jalur} -> {(int)res.StatusCode}");
string isi = await res.Content.ReadAsStringAsync();
if (isi.Length > 0) Console.WriteLine(isi);
}
await Cetak("/");
await Cetak("/tugas");
await Cetak("/tugas/2");
await Cetak("/tugas/99");
await Cetak("/tugas/abc");
await app.StopAsync();
[JsonSerializable(typeof(List<Tugas>))]
[JsonSerializable(typeof(Tugas))]
internal partial class KonteksJson : JsonSerializerContext { }
record Tugas(int Id, string Judul);Jalankan program dari folder topik dengan perintah berikut.
# aktifkan .NET yang terpasang di sandbox
export DOTNET_ROOT=/home/kal/.dotnet PATH=/home/kal/.dotnet:$PATH DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
# jalankan program dari folder topik
dotnet run --file Program.csGET / -> 200
API tugas siap melayani
GET /tugas -> 200
[{"id":1,"judul":"Kirim laporan"},{"id":2,"judul":"Tinjau rancangan"}]
GET /tugas/2 -> 200
{"id":2,"judul":"Tinjau rancangan"}
GET /tugas/99 -> 404
GET /tugas/abc -> 404Keluaran tersebut memperlihatkan tiga bentuk respons dari tiga endpoint. Rute / mengirim string, sehingga isi responsnya berupa teks biasa tanpa tanda kutip. Rute /tugas mengembalikan kumpulan objek, dan hasilnya adalah array JSON dengan nama property id dan judul yang diubah menjadi gaya camelCase oleh serializer. Rute /tugas/2 mengembalikan satu objek yang diserialisasi dengan aturan yang sama.
Cara tabel routing mencocokkan request#
Semua rute yang didaftarkan lewat MapGet dan kawannya dikumpulkan menjadi tabel routing saat aplikasi dimulai. Setiap entri tabel menyimpan pola rute, batasan parameter, dan handler yang dirangkai menjadi satu endpoint. Ketika request datang, komponen matcher membandingkan jalur URL dengan pola pada tabel, lalu menjalankan endpoint pertama yang cocok. Apabila tidak ada pola yang cocok, request dijawab 404 tanpa satu handler pun dipanggil.
Keluaran demo memperlihatkan dua jalur menuju 404 yang asalnya berbeda. Request ke /tugas/99 lolos batasan :int, tetapi handler tidak menemukan tugas bernomor 99, sehingga Results.NotFound() mengirim 404 dari dalam handler. Request ke /tugas/abc gagal lebih awal karena abc bukan bilangan bulat, sehingga tidak ada endpoint yang dipilih dan routing mengirim 404 tanpa isi. Pemisahan ini penting saat menyelidiki API yang baru dibangun, karena 404 dari routing menunjukkan masalah pola rute, sedangkan 404 dari handler menunjukkan masalah data. Kecocokan yang ambigu juga ditolak, misalnya bila dua pola rute sama-sama cocok untuk satu URL, sehingga ASP.NET Core membalas dengan error alih-alih menebak salah satu endpoint.
Kesalahan umum#
- Memakai
GetStringAsyncuntuk endpoint yang menjawab 404. Metode ini melemparHttpRequestExceptionuntuk semua status error, sehingga status dan isi respons tidak terbaca. Perbaikannya adalah memakaiGetAsynclalu membacaStatusCodedan isi responsnya secara terpisah. - Mengharapkan isi pesan dari
Results.NotFound()tanpa argumen. Pemanggilan itu mengirim respons 404 kosong, sehingga pesan penjelas harus dikirim lewatResults.NotFound(objek)bila klien membutuhkan alasan penolakan. - Melupakan atribut
JsonSerializablepada tipe baru yang dikembalikan endpoint. Serializer aplikasi satu berkas tidak memiliki metadata untuk tipe tersebut, sehingga request gagal dengan errorNotSupportedException. Tambahkan tipe itu keKonteksJsonsetiap kali handler mengembalikan bentuk data baru. - Menjalankan demo saat port 5151 sedang dipakai proses lain. Server gagal dimulai dengan pesan alamat sudah digunakan, sehingga alamat pada
app.Urls.Adddiganti atau proses pemakainya dihentikan lebih dahulu.
Ringkasan#
- Minimal API mendaftarkan endpoint lewat
MapGet,MapPost, dan kawan-kawannya tanpa kelas controller, dengan satu arahan#:sdkyang menggantikan berkas proyek pada bentuk satu berkas. - Segmen
{id:int}menyatakan parameter rute dengan batasan bilangan bulat, dan nilai parameter diteruskan ke handler sebagai argumen yang sudah diurai. - Tabel routing dibentuk saat aplikasi dimulai, dan 404 dapat berasal dari routing ketika tidak ada pola yang cocok atau dari handler ketika data tidak ditemukan.
- Pola
StartAsyncdanStopAsyncmembuat server berjalan di dalam proses demo, sehinggaHttpClientdapat menguji endpoint dan keluarannya tetap deterministik.
Referensi#
- Minimal APIs overview — Microsoft Learn — Panduan resmi minimal API, mulai dari pendaftaran endpoint sampai parameter dan responsnya.
- Routing in ASP.NET Core — Microsoft Learn — Dokumentasi tabel routing, pola rute, dan batasan parameter.
- Minimal APIs quick reference — Microsoft Learn — Ringkasan cepat API yang tersedia untuk membangun endpoint minimal API.