Skip to main content

Command Palette

Search for a command to run...

FastEndpoints in .NET

Updated
5 min read
FastEndpoints in .NET

ASP.NET Core has always been flexible. From the early days of MVC controllers, through Web API, to today’s Minimal APIs, the framework provides multiple ways to expose HTTP endpoints. With each generation, Microsoft has attempted to balance productivity with explicitness and performance. Yet, a gap remains between the lightweight Minimal APIs and the strong structure of controllers.

This is the space where FastEndpoints has carved out a niche. Built on top of ASP.NET Core, it provides a convention driven, strongly typed way to define endpoints. For people who find controllers bloated and Minimal APIs too loose, FastEndpoints offers a sweet spot.

Minimal APIs are concise, but once a project grows, Program.cs can become a graveyard of lambdas. Controllers, on the other hand, provide a good level of organisation but often lead to bloated classes mixing multiple concerns.

FastEndpoints provides a one class per endpoint model. Each endpoint encapsulates

  • The request type

  • The response type

  • Validation rules

  • The handler method

This makes endpoints self contained and easy to navigate. They’re strongly typed, fully testable, and integrate smoothly with dependency injection.

The Basic Endpoint

Here’s the a create order example:

public class CreateOrderRequest
{
    public string ProductCode { get; set; } = default!;
    public int Quantity { get; set; }
}

public class CreateOrderResponse
{
    public Guid OrderId { get; set; }
    public string Status { get; set; } = default!;
}
public class CreateOrderEndpoint 
    : Endpoint<CreateOrderRequest, CreateOrderResponse>
{
    public override void Configure()
    {
        Post("/orders");
        AllowAnonymous();
    }

    public override async Task HandleAsync(CreateOrderRequest req, CancellationToken stopToken)
    {
        // business logic could be injected via services
        var orderId = Guid.NewGuid();

        await SendAsync(new CreateOrderResponse
        {
            OrderId = orderId,
            Status = "Created"
        });
    }
}

In less than 30 lines, we have an endpoint that is typed end to end, request, response, and handler. Unlike Minimal APIs, there’s no ambiguity over what data comes in or goes out.

Validation

One of the strongest features of FastEndpoints is its built in support for FluentValidation. Instead of manually checking request models, you attach a validator

public class CreateOrderValidator : Validator<CreateOrderRequest>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.ProductCode)
            .NotEmpty().WithMessage("Product code is required");

        RuleFor(x => x.Quantity)
            .GreaterThan(0).WithMessage("Quantity must be greater than zero");
    }
}

Validation runs before the handler is executed, and errors are automatically formatted as HTTP 400 responses. This pattern avoids scattering validation logic across services or controllers.

Dependency Injection and Mediation

Endpoints aren’t isolated, they consume application services. DI works seamlessly

public class CreateOrderEndpoint 
    : Endpoint<CreateOrderRequest, CreateOrderResponse>
{
    private readonly IOrderService _orders;

    public CreateOrderEndpoint(IOrderService orders) => _orders = orders;

    public override void Configure()
    {
        Post("/orders");
    }

    public override async Task HandleAsync(CreateOrderRequest req, CancellationToken stopToken)
    {
        var result = await _orders.CreateAsync(req.ProductCode, req.Quantity, stopToken);

        await SendAsync(new CreateOrderResponse
        {
            OrderId = result.Id,
            Status = result.Status
        });
    }
}

If you prefer a mediator style architecture (CQRS), FastEndpoints integrates cleanly with MediatR. Each endpoint can become a thin adapter, forwarding requests to handlers.

Advanced Features

Grouping Endpoints

You can group endpoints under route prefixes, similar to controllers

public class OrdersGroup : Group
{
    public OrdersGroup()
    {
        Configure("orders", ep => ep.Description(b => b.WithTags("Orders")));
    }
}

Now all endpoints in this group inherit the /orders prefix and documentation tags.

Endpoint Filters

FastEndpoints supports middleware like filters at the endpoint level.

public class LoggingFilter : IGlobalPreProcessor
{
    public Task PreProcessAsync(object req, HttpContext context, List<ValidationFailure> failures, CancellationToken ct)
    {
        Console.WriteLine($"Incoming request: {context.Request.Path}");
        return Task.CompletedTask;
    }
}

This allows cross cutting concerns (logging, auditing, security checks) without contaminating business logic.

Swagger Integration

FastEndpoints autogenerates Swagger docs using NSwag or Swashbuckle. Because endpoints are strongly typed, request/response contracts appear in OpenAPI automatically.

builder.Services.AddFastEndpoints()
                .SwaggerDocument();

You get well structured, accurate documentation without the pain of decorating controllers with attributes.

Performance Considerations

Benchmarks consistently show that FastEndpoints is at least as fast as Minimal APIs, and sometimes faster, due to optimised request binding. Binding is based on conventions

  • Simple types bind from route/query/form/body automatically

  • Complex types bind from JSON body by default

This eliminates the reflection heavy model binding pipeline used in MVC.

In high throughput APIs, this translates into measurable gains. While you won’t replace gRPC with FastEndpoints, it’s performant enough for most web workloads.

Architecture with FastEndpoints

A realistic structure for an enterprise API.

src/
 ├─ Company.Orders.Api
 │   ├─ Endpoints/
 │   │   ├─ CreateOrderEndpoint.cs
 │   │   ├─ GetOrderEndpoint.cs
 │   │   └─ CancelOrderEndpoint.cs
 │   └─ Groups/
 ├─ Company.Orders.Application
 │   ├─ Commands/
 │   ├─ Queries/
 │   └─ Services/
 ├─ Company.Orders.Domain
 │   ├─ Entities/
 │   └─ Events/
 └─ Company.Orders.Infrastructure
     └─ Persistence/

Each endpoint is thin, validation, DI, response. The heavy lifting happens in Application/Domain layers. This makes endpoints easy to replace, extend, or move behind a BFF (Backend-for-Frontend) later.

Testing Endpoints

Testing endpoints is straightforward because they’re classes. You can instantiate them directly, inject fakes, and call HandleAsync.

[Test]
public async Task CreateOrderEndpoint_Returns_OrderId()
{
    var fakeService = new FakeOrderService();
    var endpoint = new CreateOrderEndpoint(fakeService);

    var request = new CreateOrderRequest
    {
        ProductCode = "ABC123",
        Quantity = 5
    };

    await endpoint.HandleAsync(request, CancellationToken.None);

    var response = endpoint.Response;
    Assert.That(response.OrderId, Is.Not.EqualTo(Guid.Empty));
}

No need to spin up a TestServer if you want unit level validation.

Where FastEndpoints shine

FastEndpoints could be good in:

  • Medium sized APIs where Minimal APIs grow messy.

  • Microservices where each service has a clear set of endpoints, validators, and handlers.

  • Projects with strong validation/auditing needs.

Keep in mind, if you’re building a massive monolith with hundreds of endpoints, you may find controllers offer better discoverability with attributes. If you’re already happy with Minimal APIs, this may feel redundant. FastEndpoints sits neatly between controllers and Minimal APIs. It delivers strong typing, validation, organisation, and performance, while remaining lighter than traditional MVC. For people seeking clarity without boilerplate, it’s a strong option.

The key is to evaluate it pragmatically. Don’t adopt it because it’s trendy, adopt it if you feel Minimal APIs are too loose and controllers too heavy. For many modern .NET systems, especially microservices and BFFs, FastEndpoints offers a nice balance.

Check out the git repo here…

https://github.com/FastEndpoints/FastEndpoints