Getting Started with Pragmatic.Endpoints
This guide will get you up and running with Pragmatic.Endpoints in 5 minutes.
Installation
Section titled “Installation”dotnet add package Pragmatic.EndpointsQuick Start
Section titled “Quick Start”1. Create Your First Endpoint
Section titled “1. Create Your First Endpoint”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);2. Register in Program.cs
Section titled “2. Register in Program.cs”var builder = WebApplication.CreateBuilder(args);
// Register generated servicesbuilder.Services.AddPragmaticEndpoints();
var app = builder.Build();
// Map all endpointsapp.MapPragmaticEndpoints();
app.Run();3. Test It
Section titled “3. Test It”curl http://localhost:5000/hello?name=Developer# {"message":"Hello, Developer!"}Key Concepts
Section titled “Key Concepts”Endpoint Types
Section titled “Endpoint Types”| Base Class | Use Case |
|---|---|
Endpoint<T> | Returns a response |
VoidEndpoint | No response body (204 No Content) |
DomainAction<T> | CQRS pattern with DI invoker |
VoidDomainAction | CQRS void operation |
Binding Attributes
Section titled “Binding Attributes”| Attribute | Source | Example |
|---|---|---|
[FromRoute] | URL path | /users/{id} → Guid Id |
[FromQuery] | Query string | ?page=1 → int Page |
[FromHeader] | HTTP headers | X-Api-Key → string ApiKey |
| None | Request body | Auto-detected public properties |
Error Handling
Section titled “Error Handling”[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 }}Adding Dependencies
Section titled “Adding Dependencies”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:
// Generatedinternal void SetDependencies( IProductRepository products, ILogger<ListProductsEndpoint> logger){ _products = products; _logger = logger;}OpenAPI Documentation
Section titled “OpenAPI Documentation”[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
DomainAction Integration
Section titled “DomainAction Integration”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.).
Next Steps
Section titled “Next Steps”- Binding Reference - All binding scenarios
- DomainAction Integration - CQRS patterns
- Response Caching - Output cache and distributed caching
- Error Handling - HTTP status mapping