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.