ronnythedev/dotnet-clean-architecture-skills/07-2-dotnet-minimal-api-endpoints

Claude Code and agent skill by ronnythedev/dotnet-clean-architecture-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Generates Minimal API endpoints following Microsoft's recommended approach. Creates fast, testable HTTP APIs with minimal code using MapGet/MapPost/MapPut/MapDelete. Preferred over controller-based APIs for new projects.

Install

npx vskill@latest install ronnythedev/dotnet-clean-architecture-skills/07-2-dotnet-minimal-api-endpoints

SKILL.md

Minimal API Endpoints Generator

Overview

Microsoft's recommended approach for new projects. Minimal APIs provide a simplified, high-performance way to build HTTP APIs with less boilerplate than controllers.

Benefits Over Controllers
  • ✅ Simpler syntax - Less code, more productivity
  • ✅ Better performance - Reduced overhead
  • ✅ Easier testing - Testable handler methods
  • ✅ Modern approach - Latest .NET features
  • ✅ Less ceremony - No controller classes needed

Quick Reference

HTTP MethodExtension MethodUse Case
MapGetRead single/listapp.MapGet("/users/{id}", ...)
MapPostCreateapp.MapPost("/users", ...)
MapPutUpdate (full)app.MapPut("/users/{id}", ...)
MapDeleteDeleteapp.MapDelete("/users/{id}", ...)

Endpoint Structure

/API/Endpoints/
├── {Feature}/
│ ├── {Feature}Endpoints.cs
│ └── Request{Action}{Entity}.cs
└── ...

Template: Complete CRUD Endpoints

csharp
// src/{name}.api/Endpoints/{Feature}/{Feature}Endpoints.cs
using {name}.application.{feature}.Create{Entity};
using {name}.application.{feature}.Delete{Entity};
using {name}.application.{feature}.Get{Entity}ById;
using {name}.application.{feature}.Get{Entities};
using {name}.application.{feature}.Update{Entity};
using {name}.infrastructure.authorization;
using MediatR;
using Microsoft.AspNetCore.Http.HttpResults;
namespace {name}.api.Endpoints.{Feature};
/// <summary>
/// Endpoints for {Entity} management
/// </summary>
public static class {Feature}Endpoints
{
/// <summary>
/// Maps all {Entity} endpoints
/// </summary>
public static RouteGroupBuilder Map{Feature}Endpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/{entities}")
.WithTags("{Feature}")
.RequireAuthorization();
group.MapGet("/{id:guid}", GetById)
.WithName("Get{Entity}ById")
.WithSummary("Get {entity} by ID")
.Produces<{Entity}Response>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/", GetAll)
.WithName("GetAll{Entities}")
.WithSummary("Get all {entities}")
.Produces<IReadOnlyList<{Entity}ListResponse>>(StatusCodes.Status200OK);
group.MapPost("/", Create)
.WithName("Create{Entity}")
.WithSummary("Create new {entity}")
.RequireAuthorization(Permissions.{Entities}Write)
.Produces<Guid>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest);
group.MapPut("/{id:guid}", Update)
.WithName("Update{Entity}")
.WithSummary("Update existing {entity}")
.RequireAuthorization(Permissions.{Entities}Write)
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest);
group.MapDelete("/{id:guid}", Delete)
.WithName("Delete{Entity}")
.WithSummary("Delete {entity}")
.RequireAuthorization(Permissions.{Entities}Write)
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status404NotFound);
return group;
}
// ═══════════════════════════════════════════════════════════════
// HANDLER METHODS
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Gets an {entity} by ID
/// </summary>
private static async Task<Results<Ok<{Entity}Response>, NotFound>> GetById(
Guid id,
ISender sender,
CancellationToken cancellationToken)
{
var query = new Get{Entity}ByIdQuery(id);
var result = await sender.Send(query, cancellationToken);
return result.IsSuccess
? TypedResults.Ok(result.Value)
: TypedResults.NotFound();
}
/// <summary>
/// Gets all {entities}
/// </summary>
private static async Task<Ok<IReadOnlyList<{Entity}ListResponse>>> GetAll(
ISender sender,
CancellationToken cancellationToken)
{
var query = new GetAll{Entities}Query();
var result = await sender.Send(query, cancellationToken);
return TypedResults.Ok(result.Value);
}
/// <summary>
/// Creates a new {entity}
/// </summary>
private static async Task<Results<Created<Guid>, BadRequest<Error>>> Create(
RequestCreate{Entity} request,
ISender sender,
CancellationToken cancellationToken)
{
var command = new Create{Entity}Command(
request.Name,
request.Description);
var result = await sender.Send(command, cancellationToken);
return result.IsSuccess
? TypedResults.Created($"/api/{entities}/{result.Value}", result.Value)
: TypedResults.BadRequest(result.Error);
}
/// <summary>
/// Updates an existing {entity}
/// </summary>
private static async Task<Results<NoContent, NotFound, BadRequest<Error>>> Update(
Guid id,
RequestUpdate{Entity} request,
ISender sender,
CancellationToken cancellationToken)
{
var command = new Update{Entity}Command(id, request.Name, request.Description);
var result = await sender.Send(command, cancellationToken);
if (result.IsFailure)
{
return result.Error.Code.Contains("NotFound")
? TypedResults.NotFound()
: TypedResults.BadRequest(result.Error);
}
return TypedResults.NoContent();
}
/// <summary>
/// Deletes an {entity}
/// </summary>
private static async Task<Results<NoContent, NotFound>> Delete(
Guid id,
ISender sender,
CancellationToken cancellationToken)
{
var command = new Delete{Entity}Command(id);
var result = await sender.Send(command, cancellationToken);
return result.IsSuccess
? TypedResults.NoContent()
: TypedResults.NotFound();
}
}

Template: Request DTOs

csharp
// src/{name}.api/Endpoints/{Feature}/RequestCreate{Entity}.cs
namespace {name}.api.Endpoints.{Feature};
/// <summary>
/// Request to create a new {entity}
/// </summary>
public sealed record RequestCreate{Entity}(
string Name,
string? Description);
/// <summary>
/// Request to update an existing {entity}
/// </summary>
public sealed record RequestUpdate{Entity}(
string Name,
string? Description);

Template: Program.cs Registration

csharp
// src/{name}.api/Program.cs
using {name}.api.Endpoints.{Feature};
using {name}.application;
using {name}.infrastructure;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
// Map endpoints
app.Map{Feature}Endpoints();
// Add more endpoint groups here
app.Run();

Template: Complex Endpoints with MapGroup

csharp
// src/{name}.api/Endpoints/{Feature}/{Feature}Endpoints.cs
public static class {Feature}Endpoints
{
public static RouteGroupBuilder Map{Feature}Endpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/{entities}")
.WithTags("{Feature}")
.RequireAuthorization();
// Standard CRUD
group.MapGet("/{id:guid}", GetById);
group.MapGet("/", GetAll);
group.MapPost("/", Create);
group.MapPut("/{id:guid}", Update);
group.MapDelete("/{id:guid}", Delete);
// Custom operations
group.MapPost("/{id:guid}/activate", Activate)
.WithName("Activate{Entity}")
.RequireAuthorization(Permissions.{Entities}Write);
group.MapPost("/{id:guid}/deactivate", Deactivate)
.WithName("Deactivate{Entity}")
.RequireAuthorization(Permissions.{Entities}Write);
// Search
group.MapGet("/search", Search)
.WithName("Search{Entities}")
.AllowAnonymous();
// Related resources
var childrenGroup = group.MapGroup("/{parentId:guid}/children")
.WithTags("{Feature} - Children");
childrenGroup.MapGet("/", GetChildren);
childrenGroup.MapPost("/", AddChild);
childrenGroup.MapDelete("/{childId:guid}", RemoveChild);
return group;
}
private static async Task<Results<Ok, BadRequest<Error>>> Activate(
Guid id,
ISender sender,
CancellationToken cancellationToken)
{
var command = new Activate{Entity}Command(id);
var result = await sender.Send(command, cancellationToken);
return result.IsSuccess
? TypedResults.Ok()
: TypedResults.BadRequest(result.Error);
}
private static async Task<Ok<IReadOnlyList<{Entity}Response>>> Search(
string? term,
int pageNumber,
int pageSize,
ISender sender,
CancellationToken cancellationToken)
{
var query = new Search{Entities}Query(term, pageNumber, pageSize);
var result = await sender.Send(query, cancellationToken);
return TypedResults.Ok(result.Value);
}
}

Template: Endpoint Filters (Validation)

csharp
// src/{name}.api/Endpoints/Filters/ValidationFilter.cs
using {name}.domain.abstractions;
namespace {name}.api.Endpoints.Filters;
/// <summary>
/// Endpoint filter for validation
/// </summary>
public class ValidationFilter<T> : IEndpointFilter where T : class
{
private readonly IValidator<T> _validator;
public ValidationFilter(IValidator<T> validator)
{
_validator = validator;
}
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var request = context.Arguments.OfType<T>().FirstOrDefault();
if (request is null)
{
return await next(context);
}
var validationResult = await _validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
return TypedResults.BadRequest(new Error(
"Validation.Failed",
string.Join(", ", validationResult.Errors.Select(e => e.ErrorMessage))));
}
return await next(context);
}
}
// Usage in endpoint
group.MapPost("/", Create)
.AddEndpointFilter<ValidationFilter<RequestCreate{Entity}>>();

Template: Authorization

csharp
// Multiple authorization options
// 1. Require authentication for all endpoints in group
var group = routes.MapGroup("/api/{entities}")
.RequireAuthorization();
// 2. Specific permission on endpoint
group.MapPost("/", Create)
.RequireAuthorization(Permissions.{Entities}Write);
// 3. Multiple policies
group.MapDelete("/{id:guid}", Delete)
.RequireAuthorization(Permissions.{Entities}Write, Permissions.Admin);
// 4. Allow anonymous (override group auth)
group.MapGet("/public", GetPublicData)
.AllowAnonymous();
// 5. Roles
group.MapPost("/admin/action", AdminAction)
.RequireAuthorization(policy => policy.RequireRole("Admin", "SuperAdmin"));

Template: API Versioning with MapGroup

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
64
Repository forks
11
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ronnythedev/dotnet-clean-architecture-skills/07-2-dotnet-minimal-api-endpoints/badge)](https://verified-skill.com/skills/ronnythedev/dotnet-clean-architecture-skills/07-2-dotnet-minimal-api-endpoints)

Something wrong with this skill or its page? Tell us.