Skip to content

Blueprint: Write Endpoint

Complete IEndpoint scaffold. Copy to apps/api/src/{ProjectName}.WebApi/Endpoints/Posts/Create/.


CreatePostRequest.cs

sealed record CreatePostRequest
{
public required string Title { get; init; }
public required string Content { get; init; }
}

CreatePostResponse.cs

sealed record CreatePostResponse
{
public required Guid Id { get; init; }
}

CreatePostApiMappings.cs

internal static class CreatePostApiMappings
{
internal static CreatePostCommand ToCommand(this CreatePostRequest request, AuthorId authorId)
{
return new CreatePostCommand
{
Id = PostId.New(),
Title = request.Title,
Content = request.Content,
AuthorId = authorId
};
}
internal static CreatePostResponse ToResponse(this PostId id)
{
return new CreatePostResponse { Id = id.Value };
}
}

CreatePostEndpoint.cs

sealed class CreatePostEndpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapPost("/posts", HandleAsync)
.WithName("CreatePost")
.WithTags("Posts")
.WithSummary("Creates a new draft post.")
.Produces<CreatePostResponse>(StatusCodes.Status201Created)
.ProducesProblem(StatusCodes.Status400BadRequest)
.RequireAuthorization()
.RequireRateLimiting(RateLimitPolicies.AuthenticatedApi);
}
private static async Task<IResult> HandleAsync(
CreatePostRequest request,
ICommandMediator commandMediator,
IHttpContextAccessor httpContextAccessor,
CancellationToken cancellationToken)
{
var authorId = httpContextAccessor.HttpContext!.User.GetAuthorId();
var command = request.ToCommand(authorId);
var postId = await commandMediator.SendAsync(command, cancellationToken);
return Results.Created($"/posts/{postId.Value}", postId.ToResponse());
}
}

See docs/conventions/backend/api-layer.md for read endpoints and idempotency headers.