Skip to content

Fast Endpoints

Overview

FastEndpoints is an open source, developer friendly alternative to Minimal APIs & MVC that uses the REPR design pattern.

Fast endpoints are much faster than following a controller route, and are on par with minimal API performance.

Nick Chapsas has a good demo on YouTube.

Simple GET endpoint example (Return all)

After adding the NuGet package, a typical Program.cs may look something like this:

using FastEndpoints;
using FluentValidation;
using Movies.Api.Movies;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddFastEndpoints();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton(IMovieService, MovieService>();
builder.Services.AddValidatorsFromAssemblyContaining<Program>(ServiceLifetime.Singleton);

var app = builder.Build();

if (app.Environment.IsDevelpment())
{
      app.UseSwagger();
      app.UseSwaggerUI();
}

app.UseFastEndpoints();

app.Run();

So there are only two extra lines of code needed to use this middleware.

Now to create the endpoint, we can add a new class for each endpoint required (so single responsibility as opposed to a controller that has all the endpoints defined in one class), for example in a new file GetMoviesEndpoint.cs we could define the class:

namespace Movies.Api.Movies

public class GetMoviesEndpoint : EndpointWithoutRequest<MoviesResponse>
{
    private readonly IMovieService _movieService;

    public GetMoviesEndpoint(IMovieService movieService)
    {
        _movieService = movieService;
    }

    public override void Configure()
    {
        Get("/movies");
        AllowAnonymous();
    }

    public override async Task HandleAsync(CancellationToken ct)
    {
        var movies = await _movieService.GetAllAsync();
        var response = movies.MapToResponse();
        await SendOkAsync(response, ct);
    }
}

And that's it for a simple GET. There is an alternative approach to send directly in the response object:

namespace Movies.Api.Movies

public class GetMoviesEndpoint : EndpointWithoutRequest<MoviesResponse>
{
    private readonly IMovieService _movieService;

    public GetMoviesEndpoint(IMovieService movieService)
    {
        _movieService = movieService;
    }

    public override void Configure()
    {
        Get("/movies");
        AllowAnonymous();
    }

    public override async Task<MovieResponse> ExecuteAsync(CancellationToken ct)
    {
        var movies = await _movieService.GetAllAsync();
        return movies.MapToResponse();
    }
}

Example of returning a single result

Similarly, to retune a single item, in a GetMovie.cs file with associated class, we could write:

namespace Movies.Api.Movies

public class GetMovieEndpoint : EndpointWithoutRequest<MovieResponse>
{
    private readonly IMovieService _movieService;

    public GetMoviesEndpoint(IMovieService movieService)
    {
        _movieService = movieService;
    }

    public override void Configure()
    {
        Get("/movie/{idOrSlug}");
        AllowAnonymous();
    }

    public override async Task<MovieResponse> HandleAsync(GetMovieRequest req, CancellationToken ct)
    {
        var idOrSlug = Route<string>("idOrSlug");

        if (idOrSlug is null)
        {
            await SendNotFoundAsync(ct);
            return;
        }

        var result = Guid.TryParse(idOrSlug, out vat id)
            ? await _movieService.GetByIdAsync(id);
            : await _movieService.GetBySlugAsync(idOrSlug);

        if (result is null)
        {
            await SendNotFoundAsync(ct);
            return;
        }

        await SendOkAsync(result.MapToResponse(), ct);
    }
}

Example of creating an object

For this example we will use a fictional CreateMovieEndpoint class:

public class CreateMovieEndpoint :
    Endpoint<CreateMovieRequest, Results<Created<MovieResponse>, BadRequest>>
{
    private readonly IMovieService _movieService;

    public GetMoviesEndpoint(IMovieService movieService)
    {
        _movieService = movieService;
    }

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

    public override async Task<Results<Created<MovieResponse>, BadRequest>>
        ExecutedAsync(CreateMovieRequest req, CancellationToken ct)
    {
        var movie = req.MapToMovie();
        result = await _movieService.CreateAsync(movie);
        var response = result.Match<IResult>(
            _ => TypedResults.Created($"/movies/{movie.Id}", movie.MapToResponse()),
            failed => TypedResults.BadRequest(failed.MapToResponse()));

        return response switch
        {
            Created<MovieResponse> success => success,
            BadRequest badRequest => badRequest,
            _ => throw new Exception("Impossible");
        };
    }
}

Adding Validation and Mapping

FluentValidation and Mapping is added automatically by FastEndpoints, so we can use this by adding validation and mapping to the Request object, and this validation will be automatically processed. See the documentation for how this is done.

Other things

Pre and post processors are also supported, along with a bunch of other functionality. See the documentation for more details.