A minimal sample showing how to run FastEndpoints inside an Azure Functions .NET 10 isolated worker app.
- Runtime: Azure Functions v4, .NET 10, isolated worker model, C#
- API framework: FastEndpoints (REPR — Request / Endpoint / Response pattern)
GET /api/hello?name={name} → 200 application/json
curl "http://localhost:7071/api/hello"
# {"message":"Hello, World!"}
curl "http://localhost:7071/api/hello?name=Bob"
# {"message":"Hello, Bob!"}The sample also exposes an OpenAPI document and Swagger UI (via the
FastEndpoints.Swagger package,
which is NSwag-based):
| URL | Returns |
|---|---|
http://localhost:7071/swagger |
Swagger UI (interactive) |
http://localhost:7071/swagger/v1/swagger.json |
OpenAPI 3.0 document |
curl "http://localhost:7071/swagger/v1/swagger.json"
# { "openapi": "3.0.0", "info": { "title": "Azure Functions + FastEndpoints", ... },
# "paths": { "/api/hello": { "get": { ... } } } }The Swagger/OpenAPI middleware is path-based, so it rides the same bridge as the
endpoints: FastEndpointsHost adds UseSwaggerGen() to the hand-built pipeline ahead of
routing, and the catch-all function forwards /swagger* requests into it.
FastEndpoints is built entirely on ASP.NET Core's endpoint routing and middleware
pipeline (app.UseFastEndpoints()). The Azure Functions isolated worker offers an
ASP.NET Core integration for HTTP triggers (you get a real HttpRequest /
HttpContext), but Microsoft's docs are explicit that it
"doesn't provide access to the ASP.NET Core middleware pipeline and routing capabilities."
So FastEndpoints can't simply be added as middleware. Instead this sample uses a small bridge:
HTTP request
│
▼
ApiProxyFunction a single catch-all HttpTrigger, Route = "{*path}"
│ forwards req.HttpContext
▼
FastEndpointsHost builds an ASP.NET Core routing pipeline by hand:
│ UseSwaggerGen → UseRouting → UseAuthorization
│ → UseEndpoints(MapFastEndpoints)
▼
HelloEndpoint Endpoint<HelloRequest, HelloResponse> → Get("/api/hello")
| File | Role |
|---|---|
Program.cs |
Wires the Functions ASP.NET Core integration and callsAddFastEndpointsHost(). |
FastEndpointsHost.cs (FastEndpointsHostRegistration) |
The DI registrations: routing, authorization, FastEndpoints, the OpenAPI document, and theFastEndpointsHost singleton. Shared with the tests. |
FastEndpointsHost.cs |
Builds the FastEndpoints request pipeline once and runs incomingHttpContexts through it. |
ApiProxyFunction.cs |
Catch-all HTTP trigger that forwards every request into the pipeline. |
HelloEndpoint.cs |
The actual FastEndpoints endpoint + request/response DTOs. |
- Why not
WebApplication.UseFastEndpoints()? AWebApplicationdefers its endpoint wiring until its server starts. Extracting theRequestDelegatewithout starting a server would drop the endpoint middleware. A plainApplicationBuilderwithUseRouting()+UseEndpoints(e => e.MapFastEndpoints())wires it immediately. - Clearing the endpoint. The Functions host attaches its own
ApiProxyendpoint to the request, which would short-circuit routing.FastEndpointsHostcallscontext.SetEndpoint(null)so FastEndpoints can re-match the route.
Note: the route prefix is disabled in
host.json("routePrefix": "") so FastEndpoints owns the full path (/api/hello). Define endpoint routes exactly as you want them.
Prerequisites: .NET 10 SDK and Azure Functions Core Tools v4.
cd src/AzureFunctionsWithFastEndpoints
dotnet runThen browse to http://localhost:7071/swagger.
Why
dotnet runand notfunc start? A .NET isolated project builds an auto-generated extensions assembly intobin/.dotnet runbuilds the project and launches the Functions host from that output directory, so those extensions load reliably.func startalso works for this sample, but recent Core Tools warn that running it directly against an isolated project may not load the generated extensions — sodotnet runis the recommended command. To use a specific port:func start --port 7072.
AzureWebJobsStorage is left empty in local.settings.json because this HTTP-only sample
needs no storage. The host logs a benign AzureWebJobsStorage health warning; HTTP
requests are unaffected. To silence it, run Azurite
and set "AzureWebJobsStorage": "UseDevelopmentStorage=true".
dotnet testtests/AzureFunctionsWithFastEndpoints.Tests runs requests straight through
FastEndpointsHost — no Functions host, no HTTP server — covering routing, query-string
binding, 404/405, the pre-attached-endpoint clearing, and the OpenAPI document. Both the
app and the tests call AddFastEndpointsHost(), so the tests exercise the real
registrations.
dotnet test tests/AzureFunctionsWithFastEndpoints.SmokeTeststests/AzureFunctionsWithFastEndpoints.SmokeTests drives the API over real HTTP with a
plain HttpClient — no browser automation, no extra packages beyond xUnit. The fixture
starts the actual Azure Functions host (func start) on a free port, waits for it to
serve, and shuts the process tree down afterwards — so these cover the full chain:
host.json route prefix → catch-all trigger → FastEndpoints bridge → endpoint. Requires
Azure Functions Core Tools v4.
To smoke-test an already-running or deployed app instead, point them at it:
SMOKE_BASE_URL=https://your-app.azurewebsites.net dotnet test tests/AzureFunctionsWithFastEndpoints.SmokeTests.github/workflows/ci.yml runs two jobs on every push and PR: build + unit tests
(which also uploads the published Functions package as an artifact), then API smoke
tests, which installs Core Tools and runs the smoke suite against a locally started
host.
Just add a new Endpoint<TRequest, TResponse> class — the catch-all function and the
bridge route everything automatically. No new function or Program.cs change required.
public sealed class PingEndpoint : EndpointWithoutRequest
{
public override void Configure()
{
Get("/api/ping");
AllowAnonymous();
}
public override Task HandleAsync(CancellationToken ct) => Send.OkAsync("pong");
}