DEV Community

Cover image for Easiest way to build the fastest REST API in C# and .NET 7 using CQRS
Artur Kedzior
Artur Kedzior

Posted on • Updated on

Easiest way to build the fastest REST API in C# and .NET 7 using CQRS

Sometime ago I stumbled upon this amazing library built by amazing people: https://fast-endpoints.com

It was also featured on this platform https://dev.to/djnitehawk/building-rest-apis-in-net-6-the-easy-way-3h0d (written by the author).

I gave it a go and I was impressed how easy and fast it was to set it all up. Since I'm not a big fan of REPR pattern almost all my projects are using CQRS pattern with a help of MediatR ](https://github.com/jbogard/MediatR) I immediately started going over something similar that Fast Endpoints offer which is a command bus.

So I put the project together with certain goals:

  1. Keep everything in command/query handlers
  2. Keep API as thin as possible
  3. Execute handlers within Azure Functions (keeping them thin as well)
  4. Make handlers easily unit testable

I put all this up on Github FastArchitecture 🔥

You can see it in action on a real-world project Salarioo.com 🔥

The API endpoint ended up being a single line:

public class GetOrdersEndpoint : ApiEndpoint<GetOrders.Query, GetOrders.Response>
{
    public override void Configure()
    {
        Get("orders/list");
        AllowAnonymous();
        ResponseCache(60);
    }

    public override async Task HandleAsync(GetOrders.Query query, CancellationToken ct) => await SendAsync(query, ct);
}
Enter fullscreen mode Exit fullscreen mode

The Query handler in a self contained class that contains:

  • Query definition (the input)
  • Query response (the output)
  • Handler (stuff that happens within execution)
public static class GetOrders
{
    public sealed class Query : IQuery<IHandlerResponse<Response>>
    {
    }

    public sealed class Response
    {
        public IReadOnlyCollection<OrderListModel> Orders { get; private set; }

        public Response(IReadOnlyCollection<Domain.Order> orders)
        {
            Orders = orders.Select(OrderListModel.Create).ToList();
        }
    }

    public sealed class Handler : QueryHandler<Query, Response>
    {
        public Handler(IHandlerContext context) : base(context)
        {
        }

        public override async Task<IHandlerResponse<Response>> ExecuteAsync(Query query, CancellationToken ct)
        {
            var orders = await DbContext
                .Orders
                .ToListAsync(ct);

            return Success(new Response(orders));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is an example of Command handler with built-in Fluent Validation and fire and forget style:

public static class ConfirmAllOrders
{
    public sealed class Command : ICommand
    {
        public string Name { get; set; } = "";
    }

    public sealed class MyValidator : Validator<Command>
    {
        public MyValidator()
        {
            RuleFor(x => x.Name)
                .MinimumLength(5)
                .WithMessage("Order name is too short!");
        }
    }

    public sealed class Handler : Abstractions.CommandHandler<Command>
    {
        public Handler(IHandlerContext context) : base(context)
        {
        }

        public override async Task<IHandlerResponse> ExecuteAsync(Command command, CancellationToken ct)
        {
            var orders = await DbContext
                   .Orders
                   .ToListAsync(ct);

            orders.ForEach(x => x.SetConfrimed());

            await DbContext.SaveChangesAsync(ct);

            return Success();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Handlers allow 3 possible responses:

  1. Response without content:
return Success()
Enter fullscreen mode Exit fullscreen mode
  1. Response with some content:
return Success(responseObj)
Enter fullscreen mode Exit fullscreen mode
  1. An error*:
return Error("I felt a great disturbance in the Force, as if millions of voices suddenly cried out in terror and were suddenly silenced. I fear something terrible has happened.")
Enter fullscreen mode Exit fullscreen mode

*soon to be implemented

Here is an example of running it from Azure Functions:

public class ConfirmAllOrdersFunction : FunctionBase<ConfirmAllOrdersFunction>
{

    public ConfirmAllOrdersFunction(ILogger logger) : base(logger)
    {
    }

    [Function(nameof(ConfirmAllOrdersFunction))]
    public async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        var command = new ConfirmAllOrders.Command();

        await ExecuteAsync<ConfirmAllOrders.Command>(command, req.FunctionContext);
    }
}
Enter fullscreen mode Exit fullscreen mode

🔥 Interested?
🔥 Checkout the complete source code on Github FastArchitecture

Top comments (0)