The full source code for the Fast Endpoints Demo is available on GitHub

Taking FastEndpoints for a Test Drive

Introduction

FastEndpoints is an ASP.NET library designed to minimize boilerplate while maximizing performance and productivity. By embracing the REPR (Request-Endpoint-Response) design pattern, it offers an alternative to traditional MVC setups and Minimal APIs, focusing on clarity and efficiency.

Minimal APIs

Minimal APIs in .NET have marked a significant shift in the way developers approach API development within the ASP.NET Core framework. These APIs offer a streamlined, more efficient method for building HTTP APIs, requiring minimal code and configuration while still allowing for the fully functioning REST endpoints necessary for modern web applications.

Introduced in .NET 6, Minimal APIs were designed to reduce the overhead and boilerplate code traditionally associated with setting up new API endpoints in ASP.NET Core. By simplifying the API development process, they enable developers to focus more on the business logic rather than the scaffolding required to set up controllers and actions in a typical MVC application.

One of the core features of Minimal APIs is the ability to define endpoints with concise lambda expressions directly within the Program.cs file, doing away with the need for a separate Startup.cs. This approach not only simplifies the project structure but also improves the readability and maintainability of the codebase

Model binding and dependency injection are also integral parts of Minimal APIs, allowing developers to work with complex data types and services seamlessly. Model binding in Minimal APIs is designed to be straightforward, primarily relying on the deserialization of JSON request bodies to POCO (Plain Old CLR Objects) types. Dependency injection is built on ASP.NET Core's robust DI system, enabling the injection of services directly into endpoint definitions, thus promoting cleaner and more modular code

Despite their simplicity, Minimal APIs are not just for basic CRUD operations; they are fully capable of supporting complex applications. Features like model validation, middleware integration, and custom response types can be easily implemented, making Minimal APIs suitable for production-grade applications.

The introduction and rapid adoption of Minimal APIs reflect a broader industry trend towards more lightweight, performant, and modular web frameworks. They offer an attractive alternative for developers looking to build APIs in a .NET environment, providing the necessary tools and flexibility for both small-scale projects and complex applications. The future of API development in ASP.NET Core looks promising with Minimal APIs, as they continue to evolve and integrate more features with each new .NET release.

The REPR Pattern

While looking at FastEndpoints, I read up on the REPR pattern. REPR, standing for Request, Endpoint, Response, encapsulates a streamlined approach for structuring APIs that's not only intuitive but also aligns with the minimalist ethos of .NET's minimal APIs.

The REPR pattern simplifies the process of creating and managing endpoints by breaking them down into three core components. The Request part defines the incoming data structure, capturing client inputs in a structured format. The Endpoint acts as the core where the business logic resides, processing requests and preparing responses. Finally, the Response component deals with sending back the processed data to the client.

This pattern was notably implemented and extended in my FastEndpoints project. By adopting the REPR pattern, I was able to create a clear separation of concerns within the API, making endpoints more understandable and easier to maintain. This approach also facilitated a cleaner integration with services like PersonService, allowing us to focus on implementing business logic without the overhead of managing the API plumbing.

Frameworks like Reaper and Reprise have further embraced and extended the REPR pattern, offering developers additional tools and methodologies for building efficient and maintainable APIs within the ASP.NET Core ecosystem. Reaper, for instance, is designed with performance and simplicity in mind, providing a middle ground between the barebones minimal APIs and the more feature-rich FastEndpoints. On the other hand, Reprise introduces the REPR pattern into ASP.NET Core Minimal APIs, aiming to provide a thin layer of abstractions that encourages the creation of modular and convention-based implementations.

The adoption of the REPR pattern, coupled with FastEndpoints, represents a significant leap towards more efficient, maintainable, and scalable API development in .NET. It showcases the power of minimalism in API design, proving that simplicity does not necessarily come at the expense of functionality or performance.

The Problem Set

For our demo, I am addressing a common but crucial problem set in the realm of web APIs: managing a simple database of entities—in this case, Person entities—via a web interface. This involves creating a CRUD (Create, Read, Update, Delete) API, a foundational component in many web applications, allowing users to interact with a database in a structured way.

The core of our problem set revolves around managing Person entities, each representing an individual with properties such as name, age, and email. The API will need to provide functionality to:

  1. Create a new Person: Add a new entry to our database with the provided details.
  2. Read/Retrieve Persons: Fetch one or more Person entries from the database, either all at once or based on specific criteria like ID.
  3. Update an existing Person: Modify the details of an existing Person entry.
  4. Delete a Person: Remove an entry from the database.

This CRUD functionality forms the backbone of many web services, from social networks to e-commerce platforms, making it an essential skill set for developers.

The Person Class

The Person class is a simple data model representing individuals in the system. Here's what I created for our Person entity:

public class Person
{
  public Guid Id { get; set; } // A unique identifier for each person
  public string FirstName { get; set; } // The person's first name
  public string LastName { get; set; } // The person's last name
  public int Age { get; set; } // The person's age
  public string Email { get; set; } // The person's email address
}

Each Person has a unique Id, along with basic information like FirstName, LastName, Age, and Email. This class structure is straightforward yet flexible enough to be expanded for more complex applications.

For the full CRUD API, this Person class will be central to the operations created with Fast Endpoints. When creating a new Person, the API will accept details like name, age, and email to construct a Person object to be stored. For reading, the API will fetch and return Person objects, either individually by Id or as a collection. Updating will involve modifying the properties of an existing Person, and deleting will remove a Person from our storage based on their Id.

This demo demonstrates not just the mechanics of building a CRUD API with FastEndpoints but also the underlying principles of web API development, such as RESTful design, HTTP methods, status codes, and more, providing a solid foundation for tackling more complex problem sets in the future.

Setting Up

Starting with a basic setup, I added FastEndpoints to a new .NET project and configured the necessary services in `Program.cs`. The ease of getting started was impressive, immediately making the development process smoother.

using FastEndpointApi.services.person;
using FastEndpoints;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFastEndpoints();
builder.Services.AddSingleton<IPersonService, PersonService>();
var app = builder.Build();
app.UseFastEndpoints();
app.Run();

Creating the Person Service

The `PersonService` class encapsulated the business logic for managing Person entities. Byabstracting the data layer from the API endpoints, we achieved a clean separation of concerns, promoting code reusability and maintainability. Here is the interface for the `PersonService` class.

public interface IPersonService
{
  PersonEntity CreatePerson(PersonEntity person);
  void DeletePerson(Guid id);
  PersonEntity ReadPerson(Guid id);
  List<PersonEntity> ReadPersons();
  PersonEntity UpdatePerson(Guid id, PersonEntity updatedPerson);
}
public class PersonService : IPersonService
{
  private readonly List<PersonEntity> _people = new();  // In-memory storage for Person entities
  public PersonEntity CreatePerson(PersonEntity person)
  {
    person.Id = Guid.NewGuid();
    _people.Add(person);
    return person;
  }

Integrating the `PersonService` through dependency injection demonstrated FastEndpoints' seamless integration with ASP.NET's core features. This allowed us to focus on business logic without worrying about the plumbing. Here is the code to register the service in the `Program.cs` file.

builder.Services.AddSingleton<IPersonService, PersonService>();

Separate concerns with Domain Mapper

The domain mapper functionality in FastEndpoints simplifies the manual mapping process between request DTOs (Data Transfer Objects) and domain entities. By introducing a dedicated CreatePersonMapper class, I can efficiently transform data from entities to response DTOs and vice versa. This separation of concerns ensures cleaner and more maintainable code, making FastEndpoints an appealing choice for those who prefer manual mapping over auto-mapping libraries.

FastEndpoints Documentation - Mapping Logic in a Separate Class

public class CreatePersonMapper : Mapper<CreatePersonRequest, PersonResponse, PersonEntity>
{
  public override PersonEntity ToEntity(CreatePersonRequest r) => new()
  {
    FirstName = r.FirstName,
    LastName = r.LastName,
    Age = r.Age,
    Email = r.Email
  };
  public override PersonResponse FromEntity(PersonEntity e) => new()
  {
    FullName = $"{e.FirstName} {e.LastName}",
    IsOver18 = e.Age >= 18,
    PersonId = e.Id.ToString()
  };
}

My First Fast Endpoint

Time to put it all together. I created my first Fast Endpoint for the Create Person method. The `CreatePersonEndpoint`, was succinct yet expressive, showcasing FastEndpoints' capability to handle complex scenarios with minimal fuss.

public class CreatePersonEndpoint(IPersonService _personService) : Endpoint<CreatePersonRequest, PersonResponse, CreatePersonMapper>
{
  public override void Configure()
  {
    Post("/api/person/create");
    AllowAnonymous();
  }
  public override Task<PersonResponse> HandleAsync(CreatePersonRequest request)
  {
    PersonEntity entity = Map.ToEntity(req);
    entity = _personService.CreatePerson(entity);
    Response = Map.FromEntity(entity);
    return SendAsync(Response, cancellation: ct);
  }
}

Next Steps and Conclusion

The next steps was to finish out the CRUD operations for the Person entity. I created the Read, Update, and Delete endpoints, each following the same pattern as the Create endpoint. The result was a clean, efficient API that showcased the power of FastEndpoints and the REPR pattern.

I added support for Swagger, enabling interactive documentation for the API.

I created a GitHub action to build and deploy the API to and Azure Web App on each push to the main branch. Here is the link to FastEndpoints Demo on Azure: https://fastendpointsdemo.azurewebsites.net FastEndpoints Demo Home Page

Taking FastEndpoints for a test drive was a fun project to quickly get an understanding of the Fast Endpoint libary. Its adherence to the REPR pattern, combined with ASP.NET's robust ecosystem, presents a compelling approach to API development. Whether you're building a complex system or a simple microservice, FastEndpoints offers the tools and flexibility needed to deliver high-quality APIs efficiently.

FastEndpoints shines in its simplicity and effectiveness. The project we embarked on served as a practical demonstration of how quickly one can get up and running with high-performing APIs in .NET. For developers looking to streamline their API development process, taking FastEndpoints for a test drive might just be the answer.

References

I could not have completed this project without the help of many teachers and mentors both in person and online. Here are some of the resources I used to complete this project.

GUIDs vs Auto-Incrementing Integers

Choosing between GUIDs and auto-incrementing integers for primary keys is a common decision in database design. While both have their advantages and drawbacks, the choice often depends on the specific requirements of the application. For scenarios where global uniqueness is essential, such as distributed systems or disconnected data creation, GUIDs offer a robust solution. On the other hand, auto-incrementing integers provide better performance and simplicity, making them suitable for large databases with predictable access patterns. When faced with this decision, developers should carefully evaluate the trade-offs and select the option that best aligns with their project's needs.

For the Person entity, I opted to use GUIDs as the primary key. This choice was driven by the need of keeping the active items in memory and not wanting to relay on a system to create auto-incremented integers.

Using GUID
Pros
  • They are globally unique without the need for a centralized authority to manage key allocation.
  • They are useful in scenarios where data needs to be created offline and then synchronized with a central database, as they eliminate the risk of key collisions.
Cons
  • Larger size may impact database performance and increase index size.
  • Alphanumeric nature can complicate debugging and data analysis.
Using Auto-Incrementing Integers
Pros
  • Better performance and smaller index size, beneficial for large databases.
  • Simplicity and predictability can enhance user experience.
Cons
  • Potential security risks, as sequential patterns can be exploited.
  • Can complicate data integration in distributed database environments.