【.NET 10】FastAPIユーザーが Minimal API を試してみた〜本当に代替になるのか検証
はじめに
普段は Python の FastAPI でAPIサーバーを書いているのですが、
「.NET 10 の Minimal API がかなり進化しているらしい」という噂を聞いて、実際に試してみました。
この記事は、FastAPI の主要機能に一つずつ対応させる形で、.NET 10 Minimal API で同じことができるか検証した話になります。
結論から言うと、想像以上に「FastAPIっぽく」書けて驚きました。
対象読者
- FastAPI の経験があり、C# / .NET にも興味がある方
- .NET 10 の Minimal API でどこまでできるか知りたい方
- API フレームワークの選定で悩んでいる方
環境
- .NET SDK: 10.0.103
- OS: Ubuntu 24.04 (WSL2)
-
NuGet パッケージ:
-
Microsoft.AspNetCore.OpenApi10.0.3 -
Scalar.AspNetCore2.12.46
-
まず、対応表を作ってみた
FastAPI が支持される理由を整理して、.NET 10 でどう対応できるか表にしてみました。
| FastAPI の特徴 | FastAPI (Python) | .NET 10 Minimal API (C#) |
|---|---|---|
| 簡潔なルーティング |
@app.get("/") デコレータ |
app.MapGet("/", ...) メソッドチェーン |
| データモデルと型安全性 | Pydantic BaseModel
|
record 型 + Data Annotations |
| 自動バリデーション | Pydantic が自動で検証 | ネイティブサポート(AddValidation) |
| 自動 API ドキュメント | Swagger UI / ReDoc(標準) | OpenAPI + Scalar(モダン UI) |
| 非同期処理 | async def |
async Task<IResult> |
| 依存性の注入 (DI) | Depends() |
ビルトイン DI コンテナ |
| JSON シリアライズ | 自動(json/orjson) |
System.Text.Json(自動・高速) |
これだけ見ると、かなり対抗できそうです。
では、一つずつ実際のコードで見ていきます。
検証1: ルーティングの書き心地
FastAPI
FastAPI では、デコレータで直感的にエンドポイントを定義できます。
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"id": user_id, "name": "たろう"}
.NET 10 Minimal API
.NET 10 では app.MapGet などのメソッドチェーンで同じことを書きます。
ファイル1つで完結する点も FastAPI と同じです。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{id}", (int id) =>
new User(id, "たろう"));
app.Run();
record User(int Id, string Name);
record 型を使うと、Pydantic の BaseModel と同じ感覚でデータモデルを1行で定義できます。
これは思った以上に書き心地が良いです。
検証2: 自動バリデーション
FastAPI 最大の武器と言っても過言ではない自動バリデーション。
型にアノテーションをつけるだけで、不正なリクエストを勝手に弾いてくれるアレです。
FastAPI
from pydantic import BaseModel, Field
class User(BaseModel):
id: int = Field(gt=0)
name: str = Field(min_length=2, max_length=50)
age: int = Field(ge=18, le=120)
@app.post("/users")
async def create_user(user: User):
return user
不正なデータを送ると 422 Unprocessable Entity が自動で返ります。
これが本当に楽なんですよね。
.NET 10 Minimal API
実は、これまでの .NET Minimal API には標準の自動バリデーションがありませんでした。
外部ライブラリ(FluentValidation や MiniValidation)を入れてフィルターを書く必要があり、これが FastAPI と比べた時の最大の弱点でした。
しかし、.NET 10 でついにネイティブの自動バリデーションが入りました!
この機能は Request Delegate Generator (RDG) という仕組みの強化として導入されています。
RDG がコンパイル時にバリデーションコードを自動生成するため、実行時のオーバーヘッドがほぼゼロという点も FastAPI(実行時チェック)に対する強みです。
仕組みを図にすると、こんなイメージです。
設定は2ステップだけです。
ステップ1: .csproj にジェネレーターの設定を追加
<PropertyGroup>
<!-- バリデーション用インターセプターの生成を有効化 -->
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Validation.Generated</InterceptorsNamespaces>
</PropertyGroup>
ステップ2: Program.cs で AddValidation() を呼ぶ
var builder = WebApplication.CreateBuilder(args);
// ★これだけで自動バリデーションが有効になる
builder.Services.AddValidation();
var app = builder.Build();
app.MapPost("/users", (User user) =>
{
// ここに到達した時点で user は検証済み
return Results.Created($"/users/{user.Id}", user);
})
.WithName("CreateUser");
モデル側は Data Annotations を付けるだけ。
public record User(
[Required]
[Range(1, int.MaxValue)]
int Id,
[Required]
[StringLength(50, MinimumLength = 2)]
string Name,
[Range(18, 120)]
int Age
);
不正なデータを送ると、FastAPI と同じように自動で弾かれます。
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Id": ["The field Id must be between 1 and 2147483647."],
"Name": ["The field Name must be a string with a minimum length of 2 and a maximum length of 50."],
"Age": ["The field Age must be between 18 and 120."]
}
}
ステータスコードの違いに注意
FastAPI は 422 Unprocessable Entity を返しますが、.NET は標準で 400 Bad Request を返します。
FastAPI から移行する場合、クライアント側のエラーハンドリングでステータスコードを判定している箇所があれば修正が必要です。
これはかなり大きなアップデートだと思います。
FastAPI との差が一気に縮まりました。
「A が空なら B を必須にする」といった相関チェックが必要な場合は、FluentValidation を併用するのも有効です。
ただ、単純なチェックなら標準機能で完全に事足ります。
検証3: API ドキュメント自動生成
FastAPI の Swagger UI と ReDoc は、コードを書くだけで美しいドキュメントが出てくる、あの体験です。
.NET 10 Minimal API
.NET 10 では、Scalar というモダンな API ドキュメント UI を使います。
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // OpenAPI 定義を生成(/openapi/v1.json)
app.MapScalarApiReference(); // Scalar UI を提供(/scalar)
}
たった2行追加するだけで、こんな画面が手に入ります。
正直、FastAPI の ReDoc より見た目が良いです。
ダークモード標準対応で、JavaScript / Python / C# など複数言語のコードサンプルも自動表示されます。
.NET 9 以降、従来の Swashbuckle(Swagger UI)は標準から外れました。
今後は Scalar が .NET エコシステムのスタンダードになりつつあります。
検証4: 依存性の注入(DI)
FastAPI では Depends() でDB接続やサービスを注入します。
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users")
async def get_users(db: Session = Depends(get_db)):
return db.query(User).all()
.NET 10
.NET にはフレームワークレベルで DI コンテナが組み込まれています。
追加ライブラリは一切不要です。
builder.Services.AddScoped<IUserService, UserService>();
app.MapGet("/users", (IUserService service) =>
service.GetAllUsers());
引数にインターフェースを書くだけで、フレームワークが自動的にインスタンスを注入してくれます。
FastAPI の Depends() よりもさらにシンプルだと感じました。
完成したコード
最終的に出来上がった Program.cs がこちらです。
このファイル1つで API サーバーが完成します。
using System.ComponentModel.DataAnnotations;
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// API ドキュメント生成と自動バリデーションを登録
builder.Services.AddOpenApi();
builder.Services.AddValidation();
var app = builder.Build();
// 開発環境でのみ API ドキュメントを有効化
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.UseHttpsRedirection();
var users = new Dictionary<int, User>();
// POST /users - ユーザー作成(Data Annotations により自動バリデーション実行)
app.MapPost("/users", (User user) =>
{
users[user.Id] = user;
return Results.Created($"/users/{user.Id}", user);
})
.WithName("CreateUser");
// GET /users/{id} - ユーザー取得
app.MapGet("/users/{id}", (int id) =>
{
return users.TryGetValue(id, out var user)
? Results.Ok(user)
: Results.NotFound(new { Message = "User not found" });
})
.WithName("GetUser");
app.Run();
// Pydantic 風のデータモデル
public record User(
[Required]
[Range(1, int.MaxValue)]
int Id,
[Required]
[StringLength(50, MinimumLength = 2)]
string Name,
[Range(18, 120)]
int Age
);
試してみたい方へ
手元で動かす場合は、以下のコマンドを実行してください。
# プロジェクトの作成
dotnet new webapi -minimal -o minimal_api
cd minimal_api
# NuGet パッケージの追加
dotnet add package Scalar.AspNetCore
# 実行
dotnet run
.csproj に InterceptorsNamespaces の設定を忘れずに追加してください。
起動後、以下の URL にアクセスできます:
-
API ドキュメント(Scalar):
http://localhost:<port>/scalar/v1 -
OpenAPI 定義:
http://localhost:<port>/openapi/v1.json
※ポート番号は dotnet run 実行時のコンソール出力(Now listening on: ...)を確認してください。
おまけ: Native AOT でさらに差をつける
FastAPI(Python)に対する .NET の隠し球が Native AOT(Ahead-of-Time コンパイル)です。
通常の JIT コンパイルでも十分高速ですが、Native AOT を使うとランタイム不要の単一バイナリにコンパイルできます。
dotnet publish -c Release -r linux-x64 /p:PublishAot=true
これにより:
- 🚀 起動時間が数ミリ秒(JIT のウォームアップがゼロ)
- 📦 メモリ消費が極小(ランタイムを含まない)
- 🐳 コンテナイメージが超軽量
AWS Lambda や Cloud Run など、コールドスタートが気になるサーバーレス / コンテナ環境では特に効果が大きいです。
Python ベースの FastAPI では原理的に達成できない領域なので、ここは .NET の圧倒的な強みだと感じました。
まとめ
| FastAPI の強み | .NET 10 での実現方法 | 評価 |
|---|---|---|
| 簡潔なルーティング |
app.MapGet / app.MapPost
|
✅ 同等 |
| Pydantic バリデーション | Data Annotations + AddValidation
|
✅ 同等 |
| Swagger UI / ReDoc | Scalar(よりモダン) | ✅ 同等以上 |
DI(Depends) |
ビルトイン DI コンテナ | ✅ 同等以上 |
| 非同期処理 | async/await |
✅ 同等 |
| 実行速度 | JIT/AOT コンパイル | 🚀 .NET が圧倒的に速い |
| デプロイサイズ | Native AOT | 🚀 サーバーレスに最適 |
正直、ここまでFastAPIに近い書き心地になっているとは思いませんでした。
特に .NET 10 で標準のバリデーションが入ったのは大きくて、**「外部ライブラリなしでFastAPIと同等のことができる」**と言える状態になっています。
コンパイル言語ゆえの圧倒的なパフォーマンス、Native AOT によるサーバーレス適性、そして静的型付けによるリファクタリングの安全性を考えると、
Python エコシステム(機械学習ライブラリ等)への依存がなければ、十分に代替候補になり得ると感じました。
参考記事
※この記事の文章は、Antigravityで作成したベースをもとに、筆者が構成・加筆したものです。
