Skip to content

Getting Started with Pragmatic.Endpoints

This guide will get you up and running with Pragmatic.Endpoints in 5 minutes.

Terminal window
dotnet add package Pragmatic.Endpoints
using Pragmatic.Endpoints;
using Pragmatic.Result;
[Endpoint(HttpVerb.Get, "/hello")]
public partial class HelloEndpoint : Endpoint<HelloResponse>
{
[FromQuery]
public string? Name { get; set; }
public override Task<Result<HelloResponse>> HandleAsync(CancellationToken ct)
{
var greeting = $"Hello, {Name ?? "World"}!";
return Task.FromResult<Result<HelloResponse>>(new HelloResponse(greeting));
}
}
public record HelloResponse(string Message);
var builder = WebApplication.CreateBuilder(args);
// Register generated services
builder.Services.AddPragmaticEndpoints();
var app = builder.Build();
// Map all endpoints
app.MapPragmaticEndpoints();
app.Run();
Terminal window
curl http://localhost:5000/hello?name=Developer
# {"message":"Hello, Developer!"}
Base ClassUse Case
Endpoint<T>Returns a response
VoidEndpointNo response body (204 No Content)
DomainAction<T>CQRS pattern with DI invoker
VoidDomainActionCQRS void operation
AttributeSourceExample
[FromRoute]URL path/users/{id}Guid Id
[FromQuery]Query string?page=1int Page
[FromHeader]HTTP headersX-Api-Keystring ApiKey
NoneRequest bodyAuto-detected public properties
[Endpoint(HttpVerb.Get, "/users/{id}")]
public partial class GetUserEndpoint : Endpoint<UserDto>
{
private IUserRepository _users = null!;
[FromRoute]
public Guid Id { get; set; }
public override async Task<Result<UserDto>> HandleAsync(CancellationToken ct)
{
var user = await _users.GetByIdAsync(Id, ct);
if (user is null)
return new NotFoundError("User", Id); // Returns 404
return new UserDto(user); // Returns 200
}
}

Private fields are auto-detected as dependencies:

[Endpoint(HttpVerb.Get, "/products")]
public partial class ListProductsEndpoint : Endpoint<List<ProductDto>>
{
// These become constructor parameters in generated handler
private IProductRepository _products = null!;
private ILogger<ListProductsEndpoint> _logger = null!;
public override async Task<Result<List<ProductDto>>> HandleAsync(CancellationToken ct)
{
_logger.LogInformation("Listing products");
var products = await _products.GetAllAsync(ct);
return products.Select(p => new ProductDto(p)).ToList();
}
}

The generator creates a SetDependencies method:

// Generated
internal void SetDependencies(
IProductRepository products,
ILogger<ListProductsEndpoint> logger)
{
_products = products;
_logger = logger;
}
[Endpoint(HttpVerb.Post, "/orders")]
[EndpointSummary("Create Order")]
[EndpointDescription("Creates a new order for the authenticated customer.")]
[Tags("Orders")]
[HttpStatus(201)]
public partial class CreateOrderEndpoint : Endpoint<OrderResponse>
{
/// <summary>
/// The customer ID.
/// </summary>
public required Guid CustomerId { get; set; }
/// <summary>
/// Items to order.
/// </summary>
public required List<OrderItem> Items { get; set; }
}

This generates:

  • Summary: “Create Order”
  • Description: “Creates a new order…”
  • Tags: [“Orders”]
  • Success Response: 201 Created with OrderResponse
  • Request Body: Generated DTO with property documentation

For CQRS patterns with the Actions module:

[Endpoint(HttpVerb.Delete, "/users/{id}")]
[Tags("Users")]
public partial class DeleteUser : VoidDomainAction<NotFoundError, ForbiddenError>
{
private IUserRepository Users { get; set; } = null!;
[FromRoute]
public Guid Id { get; init; }
public override async Task<VoidResult<IError>> Execute(CancellationToken ct)
{
var user = await Users.GetByIdAsync(Id, ct);
if (user is null)
return new NotFoundError("User", Id);
if (!user.CanBeDeleted)
return new ForbiddenError("Cannot delete active user");
await Users.DeleteAsync(Id, ct);
return Success;
}
}

The generator uses DomainActionInvoker<T> for full pipeline support (validation, filters, etc.).