Backend Intermediate 18 min

FastEndpoints: The REPR Pattern for .NET APIs

Ditch bloated controllers. Build high-performance, maintainable APIs using the Request-Endpoint-Response (REPR) pattern with FastEndpoints.

By Victor Robin Updated:

Introduction

BlueRobin’s API started as a traditional ASP.NET Core controller-based project. Within three months, I had a DocumentsController with 14 action methods, 6 injected dependencies, and over 800 lines of code. Every new endpoint required scrolling past unrelated methods. Switching to FastEndpoints was like going from a cluttered open-plan office to private offices—each endpoint got its own space, its own dependencies, and its own tests.

For years, ASP.NET Core developers have been stuck in the “Controller” trap. You have a DocumentsController that grows to 2,000 lines of code, managing Dependencies for Reading, Writing, Deleting, and Searching documents. It’s a violation of the Single Responsibility Principle [Clean Architecture] — Robert C. Martin , 2012 .

FastEndpoints [FastEndpoints Documentation] — Dhananjay Kumar , 2024 implements the REPR Pattern (Request-Endpoint-Response) [REPR Design Pattern] — Steve Smith , 2024 . Every endpoint is a distinct class. It’s like having a Controller with exactly one method. This naturally guides you toward Vertical Slice Architecture [Vertical Slice Architecture] — Jimmy Bogard , 2018 .

Why FastEndpoints Matters:

  • Performance: It’s faster than MVC Controllers and uses less memory [ASP.NET Core Performance Benchmarks] — TechEmpower , 2024 .
  • Organization: Related logic (Request, Response, Validation, Handler) stays together in one file or folder.
  • No More Bloat: You never need private helper methods shared by 10 unrelated actions.

What We’ll Build

In this guide, we will replace a traditional Controller action with a FastEndpoint. We will cover:

  1. The Request DTO: Binding data from JSON, Route, or Query.
  2. The Validator: FluentValidation integration without boilerplate.
  3. The Endpoint: Implementing the handler logic.
  4. The Response: Sending typed responses with 200/201/400 status codes.

Architecture Overview

flowchart LR
    subgraph Client["📱 Client"]
        Req["POST /api/documents"]
    end

    subgraph Server["⚡ FastEndpoints Pipeline"]
        DTO["📦 Request DTO\n(Binding)"] --> Val["🛡️ Validator\n(FluentValidation)"]
        Val --> EP["💼 Endpoint Class\n(Handler)"]
        EP --> Dom["🧱 Domain Logic\n(Service/Aggregate)"]
        Dom --> EP
        EP --> Resp["📤 Response DTO"]
    end

    Client --> DTO
    Resp --> Client

    classDef primary fill:#7c3aed,color:#fff
    classDef secondary fill:#06b6d4,color:#fff
    classDef db fill:#f43f5e,color:#fff
    classDef warning fill:#fbbf24,color:#000

    class Client warning
    class Server primary
    class EP primary

Implementation

The Request & Validation

FastEndpoints has FluentValidation [FluentValidation Documentation] — Jeremy Skinner , 2024 built-in. You don’t need to register validators manually; it scans and registers them automatically.

// 1. The Request DTO
public sealed record CreateDocumentRequest
{
    public string Title { get; init; }
    public IFormFile File { get; init; }
    public string[] Tags { get; init; }
}

// 2. The Validator
public sealed class CreateDocumentValidator : Validator<CreateDocumentRequest>
{
    public CreateDocumentValidator()
    {
        RuleFor(x => x.Title)
            .NotEmpty()
            .MaximumLength(100)
            .WithMessage("Title prevents chaos.");

        RuleFor(x => x.File)
            .NotNull()
            .Must(f => f.Length > 0)
            .WithMessage("Cannot upload empty air.");
    }
}

The Endpoint

The Endpoint class replaces the Controller Action. It inherits from Endpoint<TRequest, TResponse>.

public sealed class CreateDocumentEndpoint : Endpoint<CreateDocumentRequest, CreateDocumentResponse>
{
    private readonly IDocumentService _service;
    private readonly IUserContext _user;

    public CreateDocumentEndpoint(IDocumentService service, IUserContext user)
    {
        _service = service;
        _user = user;
    }

    public override void Configure()
    {
        Post("/api/documents");
        AllowFileUploads(); // Helper for multipart/form-data
        Policies("StartProcessing"); // Auth Policy
        Summary(s => 
        {
            s.Summary = "Uploads a new document";
            s.Description = "Uploads a file and starts the OCR process.";
            s.Response<CreateDocumentResponse>(201, "Document created successfully");
        });
    }

    public override async Task HandleAsync(CreateDocumentRequest req, CancellationToken ct)
    {
        // 1. Call Domain Logic
        var result = await _service.UploadAsync(
            ownerId: _user.CustomId, 
            title: req.Title, 
            stream: req.File.OpenReadStream(),
            ct: ct
        );

        // 2. Map Response
        var response = new CreateDocumentResponse
        {
            DocumentId = result.Id.Value,
            Status = "Processing",
            EstimatedTime = "5 seconds"
        };

        // 3. Send 201 Created
        await SendCreatedAtAsync<GetDocumentEndpoint>(
            new { id = result.Id.Value }, 
            response, 
            cancellation: ct
        );
    }
}

Vertical Slice Organization

Files should be grouped by Feature, not by Type.

✅ Do this:

/Features
  /Documents
    /Create
      CreateDocumentEndpoint.cs
      CreateDocumentRequest.cs
      CreateDocumentValidator.cs
      CreateDocumentResponse.cs
    /Get
      GetDocumentEndpoint.cs

❌ NOT this:

/Controllers
  DocumentsController.cs
/Models
  CreateDocumentRequest.cs
/Validators
  CreateDocumentValidator.cs

Conclusion

FastEndpoints removes the friction from API development. It gives you the raw speed of Minimal APIs with the structure and discipline required for large enterprise systems.

After migrating BlueRobin’s API from controllers to FastEndpoints, the codebase went from 3 controller files averaging 600 lines each to 45 endpoint files averaging 40 lines each. The total line count barely changed, but the navigability and testability improved dramatically. Every endpoint is independently testable, independently deployable (conceptually), and independently understandable. If you are starting a new .NET API project, I strongly recommend starting with FastEndpoints from day one rather than retrofitting later.

Next Steps:

  • Combine this with Domain Events to trigger processing after the API response.
  • Learn how Aggregates handle the actual logic inside _service.UploadAsync.
  • See the Shared Kernel that provides the base types used across all endpoints.

Further Reading

  • [FastEndpoints Documentation] — Dhananjay Kumar , 2024 — The official documentation covering all features including versioning, rate limiting, and Swagger integration.
  • [Vertical Slice Architecture] — Jimmy Bogard , 2018 — Jimmy Bogard’s influential post on organizing code by feature rather than technical layer.
  • [REPR Design Pattern] — Steve Smith , 2024 — The Request-Endpoint-Response pattern that FastEndpoints implements, explained by Steve Smith.