{"id":32,"date":"2024-02-27T14:33:09","date_gmt":"2024-02-27T14:33:09","guid":{"rendered":"https:\/\/twobitranch.io\/?p=32"},"modified":"2026-09-18T13:12:31","modified_gmt":"2026-09-18T13:12:31","slug":"net-and-minimal-apis","status":"publish","type":"post","link":"https:\/\/twobitranch.io\/index.php\/2024\/02\/27\/net-and-minimal-apis\/","title":{"rendered":".NET Minimal APIs:"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Apparently We\u2019ve Finally Admitted Controllers Were a Mistake<\/h2>\n\n\n\n<p><em>An entirely unbiased account from a developer who has been writing front ends, back ends, and increasingly desperate resignation letters for about 30 years.<\/em><\/p>\n\n\n\n<p>I have been a software developer for roughly 30 years.<\/p>\n\n\n\n<p>That means I have witnessed the entire evolution of web development, from:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cLet\u2019s put some HTML on a server.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>to:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cLet\u2019s build a distributed, event-driven, cloud-native, containerized, observable, horizontally scalable platform that renders a button.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>I have used technologies whose names have since been erased from the archaeological record.<\/p>\n\n\n\n<p>I remember when we thought frames were a good idea.<\/p>\n\n\n\n<p>I remember when JavaScript was something you apologized for putting on a page.<\/p>\n\n\n\n<p>I remember when XML was going to solve everything.<\/p>\n\n\n\n<p>I remember when SOAP was going to solve everything.<\/p>\n\n\n\n<p>I remember when REST was going to solve everything.<\/p>\n\n\n\n<p>I have learned one important thing from all of this:<\/p>\n\n\n\n<p><strong>We are really bad at leaving well enough alone.<\/strong><\/p>\n\n\n\n<p>And then along came .NET Minimal APIs.<\/p>\n\n\n\n<p>And I thought:<\/p>\n\n\n\n<p>\u201cWait a minute.<\/p>\n\n\n\n<p>Are we\u2026 just writing functions now?\u201d<\/p>\n\n\n\n<p>Apparently.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Good Old Days<\/h2>\n\n\n\n<p>When I started building web applications, the web stack was delightfully primitive.<\/p>\n\n\n\n<p>You had a server.<\/p>\n\n\n\n<p>You had a database.<\/p>\n\n\n\n<p>You had HTML.<\/p>\n\n\n\n<p>You had some JavaScript, although admitting this publicly could get you fired.<\/p>\n\n\n\n<p>And you had a vague understanding that HTTP involved something called GET and POST.<\/p>\n\n\n\n<p>Then frameworks arrived.<\/p>\n\n\n\n<p>And frameworks did what frameworks do best:<\/p>\n\n\n\n<p><strong>They created an enormous amount of infrastructure to protect you from having to write three lines of code.<\/strong><\/p>\n\n\n\n<p>Before long, creating an endpoint required approximately seventeen files.<\/p>\n\n\n\n<p>You had:<\/p>\n\n\n\n<ul>\n<li>A controller<\/li>\n\n\n\n<li>An interface<\/li>\n\n\n\n<li>A service<\/li>\n\n\n\n<li>A service implementation<\/li>\n\n\n\n<li>A repository<\/li>\n\n\n\n<li>A repository interface<\/li>\n\n\n\n<li>A DTO<\/li>\n\n\n\n<li>A DTO mapper<\/li>\n\n\n\n<li>A validator<\/li>\n\n\n\n<li>A dependency injection registration<\/li>\n\n\n\n<li>An extension method to register the dependency injection registration<\/li>\n\n\n\n<li>A configuration class<\/li>\n\n\n\n<li>An options class<\/li>\n\n\n\n<li>An AutoMapper profile<\/li>\n\n\n\n<li>A unit test<\/li>\n\n\n\n<li>A mock of the repository<\/li>\n\n\n\n<li>A mock of the service<\/li>\n\n\n\n<li>A mock of the mapper<\/li>\n\n\n\n<li>And, if you were really serious, a factory for the mock factory<\/li>\n<\/ul>\n\n\n\n<p>Eventually someone would ask:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cCan you add an endpoint that returns a customer by ID?\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>And you&#8217;d say:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cSure. Give me two days.\u201d<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Enter Minimal APIs<\/h1>\n\n\n\n<p>Then Microsoft basically looked at all of this and said:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWhat if you just\u2026 didn&#8217;t?\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>And thus Minimal APIs were born.<\/p>\n\n\n\n<p>You can now write something like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapGet(\"\/customers\/{id}\", async (int id, MyDbContext db) =&gt;\n{\n    return await db.Customers.FindAsync(id);\n});<\/code><\/pre>\n\n\n\n<p>That&#8217;s it.<\/p>\n\n\n\n<p>That&#8217;s the endpoint.<\/p>\n\n\n\n<p>There isn&#8217;t a controller.<\/p>\n\n\n\n<p>There isn&#8217;t a controller factory.<\/p>\n\n\n\n<p>There isn&#8217;t a controller interface.<\/p>\n\n\n\n<p>There isn&#8217;t a controller base class.<\/p>\n\n\n\n<p>There isn&#8217;t a controller factory factory.<\/p>\n\n\n\n<p>There is just:<\/p>\n\n\n\n<p><strong>GET <code>\/customers\/42<\/code><\/strong><\/p>\n\n\n\n<p>and some C#.<\/p>\n\n\n\n<p>At first, I didn&#8217;t trust it.<\/p>\n\n\n\n<p>I&#8217;ve been doing this too long.<\/p>\n\n\n\n<p>Whenever something looks simple, I immediately assume Microsoft has hidden something horrible behind it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u201cBut Where&#8217;s the Controller?\u201d<\/h1>\n\n\n\n<p>This is the first question every veteran developer asks.<\/p>\n\n\n\n<p>You see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapGet(\"\/orders\/{id}\", (int id) =&gt;\n{\n    return GetOrder(id);\n});<\/code><\/pre>\n\n\n\n<p>And your brain immediately starts looking around the room.<\/p>\n\n\n\n<p>\u201cWhere&#8217;s the controller?\u201d<\/p>\n\n\n\n<p>Nothing.<\/p>\n\n\n\n<p>\u201cWhere&#8217;s the action method?\u201d<\/p>\n\n\n\n<p>That&#8217;s it.<\/p>\n\n\n\n<p>\u201cWhere&#8217;s the attribute?\u201d<\/p>\n\n\n\n<p>There isn&#8217;t one.<\/p>\n\n\n\n<p>\u201cWhere&#8217;s the base class?\u201d<\/p>\n\n\n\n<p>There isn&#8217;t one.<\/p>\n\n\n\n<p>\u201cWhere do I put <code>[Authorize]<\/code>?\u201d<\/p>\n\n\n\n<p>Right there.<\/p>\n\n\n\n<p>\u201cWhere do I put <code>[ProducesResponseType]<\/code>?\u201d<\/p>\n\n\n\n<p>Right there.<\/p>\n\n\n\n<p>\u201cWhere&#8217;s the 900-line startup configuration file?\u201d<\/p>\n\n\n\n<p>Gone.<\/p>\n\n\n\n<p>At this point you become suspicious.<\/p>\n\n\n\n<p>You start walking around the codebase looking behind things.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Front-End Developer&#8217;s Perspective<\/h1>\n\n\n\n<p>I&#8217;ve spent decades doing both front-end and back-end development, which means I have developed the unique ability to be disappointed by both sides of the application.<\/p>\n\n\n\n<p>The front end says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cThe API is returning the wrong shape.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>The back end says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cThe front end is consuming the API incorrectly.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>The database says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cBoth of you are idiots.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>Minimal APIs actually make this relationship slightly more honest.<\/p>\n\n\n\n<p>Your endpoint is sitting there in plain sight:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapPost(\"\/products\", async (Product product, MyDbContext db) =&gt;\n{\n    db.Products.Add(product);\n    await db.SaveChangesAsync();\n\n    return Results.Created($\"\/products\/{product.Id}\", product);\n});<\/code><\/pre>\n\n\n\n<p>The front-end developer can look at it and understand what is happening.<\/p>\n\n\n\n<p>The back-end developer can look at it and understand what is happening.<\/p>\n\n\n\n<p>The database developer can look at it and say:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWhy are you returning the entity?\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>And thus, balance is restored to the universe.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Beautiful Thing About Minimal APIs<\/h1>\n\n\n\n<p>Minimal APIs acknowledge something we&#8217;ve collectively refused to admit for decades:<\/p>\n\n\n\n<p><strong>Sometimes an endpoint is just an endpoint.<\/strong><\/p>\n\n\n\n<p>Sometimes you don&#8217;t need an architectural ceremony around it.<\/p>\n\n\n\n<p>Sometimes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP request\n   \u2193\ndo thing\n   \u2193\nHTTP response<\/code><\/pre>\n\n\n\n<p>is actually a perfectly respectable architecture.<\/p>\n\n\n\n<p>This is especially useful for small services.<\/p>\n\n\n\n<p>Health checks.<\/p>\n\n\n\n<p>Webhooks.<\/p>\n\n\n\n<p>Internal APIs.<\/p>\n\n\n\n<p>Tiny microservices.<\/p>\n\n\n\n<p>Background-service control endpoints.<\/p>\n\n\n\n<p>Prototypes.<\/p>\n\n\n\n<p>Administrative APIs.<\/p>\n\n\n\n<p>That one endpoint you needed because somebody in accounting wants to trigger a nightly report by hitting a URL.<\/p>\n\n\n\n<p>For those things, Minimal APIs are fantastic.<\/p>\n\n\n\n<p>You don&#8217;t need to construct the Cathedral of Notre Dame because someone asked for a GET endpoint.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">But Then We Discover Dependency Injection<\/h1>\n\n\n\n<p>Here&#8217;s where things get interesting.<\/p>\n\n\n\n<p>Minimal APIs support dependency injection directly in handler parameters.<\/p>\n\n\n\n<p>So this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapGet(\"\/customers\/{id}\",\n    async (int id, CustomerService service) =&gt;\n        await service.GetCustomer(id));<\/code><\/pre>\n\n\n\n<p>is perfectly reasonable.<\/p>\n\n\n\n<p>And suddenly I&#8217;m thinking:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWait. This is actually pretty nice.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>The framework sees <code>CustomerService<\/code> and says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cOh, you need one of those? Here.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>The handler gets its dependencies.<\/p>\n\n\n\n<p>Nobody has to manually dig around inside some mystical <code>HttpContext.Services<\/code> object like they&#8217;re searching for a lost contact lens.<\/p>\n\n\n\n<p>You can even inject multiple dependencies:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapGet(\"\/reports\",\n    async (\n        ReportService reports,\n        ILogger&lt;Program&gt; logger,\n        IConfiguration config,\n        CancellationToken cancellationToken) =&gt;\n    {\n        logger.LogInformation(\"Generating report\");\n\n        return await reports.GenerateAsync(cancellationToken);\n    });<\/code><\/pre>\n\n\n\n<p>At this point, the method signature starts looking like the guest list at a family reunion.<\/p>\n\n\n\n<p>But at least it&#8217;s honest.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Filters: Because We Can&#8217;t Have Nice Things<\/h1>\n\n\n\n<p>Eventually someone says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cThis is great, but we need cross-cutting concerns.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>And the veteran developer&#8217;s eye twitches.<\/p>\n\n\n\n<p>Because we know what happens next.<\/p>\n\n\n\n<p>Someone needs:<\/p>\n\n\n\n<ul>\n<li>authorization<\/li>\n\n\n\n<li>validation<\/li>\n\n\n\n<li>logging<\/li>\n\n\n\n<li>exception handling<\/li>\n\n\n\n<li>telemetry<\/li>\n\n\n\n<li>rate limiting<\/li>\n\n\n\n<li>transactions<\/li>\n\n\n\n<li>auditing<\/li>\n\n\n\n<li>correlation IDs<\/li>\n\n\n\n<li>request timing<\/li>\n\n\n\n<li>feature flags<\/li>\n\n\n\n<li>some weird business rule from 2017<\/li>\n<\/ul>\n\n\n\n<p>And suddenly our beautiful little Minimal API endpoint has become:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapPost(\"\/widgets\",\n    async (\n        WidgetRequest request,\n        WidgetService service,\n        Validator validator,\n        AuditService audit,\n        ILogger&lt;Program&gt; logger,\n        CancellationToken cancellationToken) =&gt;\n    {\n        ...\n    })\n    .RequireAuthorization()\n    .AddEndpointFilter&lt;ValidationFilter&gt;()\n    .AddEndpointFilter&lt;AuditFilter&gt;();<\/code><\/pre>\n\n\n\n<p>Congratulations.<\/p>\n\n\n\n<p>We have reinvented enterprise architecture.<\/p>\n\n\n\n<p>It took approximately 11 minutes.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Endpoint Group<\/h1>\n\n\n\n<p>Minimal APIs do have a nice answer to this.<\/p>\n\n\n\n<p>Groups.<\/p>\n\n\n\n<p>You can do something like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var admin = app.MapGroup(\"\/admin\")\n    .RequireAuthorization(\"AdminPolicy\");\n\nadmin.MapGet(\"\/users\", ...);\nadmin.MapGet(\"\/reports\", ...);\nadmin.MapDelete(\"\/users\/{id}\", ...);<\/code><\/pre>\n\n\n\n<p>This is one of those features where you stare at it for a moment and think:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cThat&#8217;s actually sensible.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>Which is always dangerous.<\/p>\n\n\n\n<p>Because the next meeting will inevitably contain the phrase:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cLet&#8217;s create a generic endpoint group abstraction.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>And now you&#8217;re back to square one.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">OpenAPI: The Thing We Used to Call Swagger<\/h1>\n\n\n\n<p>One of the things I genuinely appreciate about modern .NET is how much API metadata can be generated without turning the application into a ritual sacrifice.<\/p>\n\n\n\n<p>Minimal APIs integrate nicely with OpenAPI tooling.<\/p>\n\n\n\n<p>You can define an endpoint:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapGet(\"\/customers\/{id}\",\n    async (int id, CustomerService service) =&gt;\n        await service.Get(id));<\/code><\/pre>\n\n\n\n<p>and expose useful API documentation.<\/p>\n\n\n\n<p>This is important because apparently we have reached the point in civilization where humans can build distributed systems spanning twelve countries but still need a webpage to tell them whether an endpoint expects:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"customerId\": 123\n}<\/code><\/pre>\n\n\n\n<p>or:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"id\": 123\n}<\/code><\/pre>\n\n\n\n<p>or:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"customer_id\": 123\n}<\/code><\/pre>\n\n\n\n<p>or, my personal favorite:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"customer\": {\n        \"customer\": {\n            \"customerId\": 123\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>OpenAPI exists because apparently naming things was the second-hardest problem in computer science.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Problem: You Can Make a Horrible Minimal API<\/h1>\n\n\n\n<p>This is the part where I have to issue a warning.<\/p>\n\n\n\n<p>Minimal APIs make it extremely easy to write bad code.<\/p>\n\n\n\n<p>You can absolutely do this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapPost(\"\/orders\", async (\n    Order order,\n    SqlConnection connection,\n    ILogger&lt;Program&gt; logger) =&gt;\n{\n    \/\/ 900 lines of business logic\n});<\/code><\/pre>\n\n\n\n<p>Please don&#8217;t.<\/p>\n\n\n\n<p>That&#8217;s not a Minimal API.<\/p>\n\n\n\n<p>That&#8217;s a cry for help.<\/p>\n\n\n\n<p>The fact that you <em>can<\/em> put everything into <code>Program.cs<\/code> does not mean you should.<\/p>\n\n\n\n<p>We&#8217;ve learned this lesson before.<\/p>\n\n\n\n<p>In the 1990s we put everything into one HTML page.<\/p>\n\n\n\n<p>Then we put everything into one JavaScript file.<\/p>\n\n\n\n<p>Then we put everything into one controller.<\/p>\n\n\n\n<p>Then we put everything into one service.<\/p>\n\n\n\n<p>Now we are being offered <code>Program.cs<\/code>.<\/p>\n\n\n\n<p><strong>Do not make the same mistake again.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Minimal Does Not Mean Architectural<\/h1>\n\n\n\n<p>This distinction is important.<\/p>\n\n\n\n<p>Minimal APIs are about reducing the ceremony around HTTP endpoints.<\/p>\n\n\n\n<p>They don&#8217;t magically eliminate:<\/p>\n\n\n\n<ul>\n<li>business logic<\/li>\n\n\n\n<li>domain modeling<\/li>\n\n\n\n<li>persistence concerns<\/li>\n\n\n\n<li>security<\/li>\n\n\n\n<li>testing<\/li>\n\n\n\n<li>observability<\/li>\n\n\n\n<li>validation<\/li>\n\n\n\n<li>maintainability<\/li>\n<\/ul>\n\n\n\n<p>You still need architecture.<\/p>\n\n\n\n<p>You just don&#8217;t necessarily need <strong>architecture cosplay<\/strong>.<\/p>\n\n\n\n<p>There&#8217;s a difference between:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWe have a clean separation of concerns.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>and:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWe have 46 projects so nobody can figure out where the code lives.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>I&#8217;ve worked on both.<\/p>\n\n\n\n<p>I know which one I prefer.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Thirty Years of Front-End Development Has Taught Me One Thing<\/h1>\n\n\n\n<p>The back end and front end have more in common than we like to admit.<\/p>\n\n\n\n<p>Front-end developers spent years trying to make HTML simpler.<\/p>\n\n\n\n<p>Then we invented:<\/p>\n\n\n\n<ul>\n<li>MVC<\/li>\n\n\n\n<li>MVVM<\/li>\n\n\n\n<li>component frameworks<\/li>\n\n\n\n<li>state management<\/li>\n\n\n\n<li>stores<\/li>\n\n\n\n<li>hooks<\/li>\n\n\n\n<li>signals<\/li>\n\n\n\n<li>observables<\/li>\n\n\n\n<li>reactive programming<\/li>\n\n\n\n<li>meta-frameworks<\/li>\n\n\n\n<li>server components<\/li>\n\n\n\n<li>build pipelines<\/li>\n<\/ul>\n\n\n\n<p>Eventually someone says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cHere&#8217;s a button.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>And the answer is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cGreat. Which state container owns it?\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>The back end did exactly the same thing.<\/p>\n\n\n\n<p>We started with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GET \/customers\/42<\/code><\/pre>\n\n\n\n<p>and eventually arrived at:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Controller\n \u2192 Mediator\n \u2192 Command\n \u2192 Handler\n \u2192 Domain Service\n \u2192 Repository\n \u2192 Unit of Work\n \u2192 ORM\n \u2192 Database<\/code><\/pre>\n\n\n\n<p>Minimal APIs are, in a strange way, a reminder that we can occasionally just write:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapGet(\"\/customers\/{id}\", ...);<\/code><\/pre>\n\n\n\n<p>and get on with our lives.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Most Dangerous Sentence in Software<\/h1>\n\n\n\n<p>After 30 years, I have learned that the most dangerous sentence in software development is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWouldn&#8217;t it be cool if&#8230;?\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>Wouldn&#8217;t it be cool if our API were event-driven?<\/p>\n\n\n\n<p>Wouldn&#8217;t it be cool if everything were microservices?<\/p>\n\n\n\n<p>Wouldn&#8217;t it be cool if we used GraphQL?<\/p>\n\n\n\n<p>Wouldn&#8217;t it be cool if we had CQRS?<\/p>\n\n\n\n<p>Wouldn&#8217;t it be cool if we put Kubernetes in front of it?<\/p>\n\n\n\n<p>Wouldn&#8217;t it be cool if we created our own abstraction over Microsoft&#8217;s abstraction?<\/p>\n\n\n\n<p>And now:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWouldn&#8217;t it be cool if we made a Minimal API?\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>Yes.<\/p>\n\n\n\n<p>It would.<\/p>\n\n\n\n<p>Just remember that <strong>minimal code isn&#8217;t the same thing as minimal thinking<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">So, Are Minimal APIs Good?<\/h1>\n\n\n\n<p>Here&#8217;s my very sophisticated 30-year technical assessment:<\/p>\n\n\n\n<p><strong>It depends.<\/strong><\/p>\n\n\n\n<p>Yes, I know.<\/p>\n\n\n\n<p>That&#8217;s disappointing.<\/p>\n\n\n\n<p>You were hoping for a definitive answer.<\/p>\n\n\n\n<p>Sorry.<\/p>\n\n\n\n<p>I spent three decades learning that definitive answers in software are usually just marketing copy with better posture.<\/p>\n\n\n\n<p>Minimal APIs are excellent when you want:<\/p>\n\n\n\n<ul>\n<li>small APIs<\/li>\n\n\n\n<li>focused services<\/li>\n\n\n\n<li>straightforward endpoints<\/li>\n\n\n\n<li>lightweight HTTP services<\/li>\n\n\n\n<li>webhooks<\/li>\n\n\n\n<li>internal tools<\/li>\n\n\n\n<li>prototypes<\/li>\n\n\n\n<li>simple CRUD APIs<\/li>\n<\/ul>\n\n\n\n<p>They&#8217;re less compelling when you have a massive application with hundreds of complicated endpoints and a team that strongly benefits from conventional controller organization and conventions.<\/p>\n\n\n\n<p>And that&#8217;s okay.<\/p>\n\n\n\n<p>You don&#8217;t get extra developer points for using the newest thing.<\/p>\n\n\n\n<p>Nobody gets to Heaven because they successfully replaced 47 controllers with lambda expressions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">The Real Innovation<\/h1>\n\n\n\n<p>The most interesting thing about Minimal APIs isn&#8217;t actually that they&#8217;re minimal.<\/p>\n\n\n\n<p>It&#8217;s that modern .NET finally seems comfortable admitting:<\/p>\n\n\n\n<p><strong>Not every application needs a framework-shaped building around it.<\/strong><\/p>\n\n\n\n<p>Sometimes you need a skyscraper.<\/p>\n\n\n\n<p>Sometimes you need a house.<\/p>\n\n\n\n<p>Sometimes you need a garden shed with an HTTP endpoint.<\/p>\n\n\n\n<p>And sometimes the requirement really is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWhen Stripe calls this URL, update the order and return 200.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>For that, maybe we don&#8217;t need twelve interfaces.<\/p>\n\n\n\n<p>Maybe we don&#8217;t need a factory.<\/p>\n\n\n\n<p>Maybe we don&#8217;t need a repository abstraction over Entity Framework which is itself abstracting a database abstraction.<\/p>\n\n\n\n<p>Maybe we can just write the damn endpoint.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">After 30 Years&#8230;<\/h1>\n\n\n\n<p>I&#8217;ve watched programming languages come and go.<\/p>\n\n\n\n<p>I&#8217;ve watched architectures rise and fall.<\/p>\n\n\n\n<p>I&#8217;ve watched JavaScript become a serious programming language.<\/p>\n\n\n\n<p>I&#8217;ve watched CSS become something people write entire books about.<\/p>\n\n\n\n<p>I&#8217;ve watched XML die, come back as configuration, and then die again.<\/p>\n\n\n\n<p>I&#8217;ve watched the industry rediscover functional programming approximately every seven years.<\/p>\n\n\n\n<p>And I&#8217;ve watched us repeatedly build enormous systems to solve problems that could have been solved with a function and a database.<\/p>\n\n\n\n<p>So when I see this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.MapGet(\"\/hello\", () =&gt; \"Hello, world!\");<\/code><\/pre>\n\n\n\n<p>I don&#8217;t laugh.<\/p>\n\n\n\n<p>I don&#8217;t complain.<\/p>\n\n\n\n<p>I don&#8217;t ask where the controller is.<\/p>\n\n\n\n<p>I just stare at it quietly.<\/p>\n\n\n\n<p>Because somewhere deep inside my 30-year-old developer soul, beneath the scars from Web Forms and the psychological damage caused by Internet Explorer 6&#8230;<\/p>\n\n\n\n<p><strong>I think I&#8217;ve finally found peace.<\/strong><\/p>\n\n\n\n<p>Then someone walks into my office and says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u201cWe need to put this behind a service mesh.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>And just like that, I&#8217;m looking at jobs on LinkedIn again.<\/p>\n\n\n\n<p>If you want, I can also turn this into a <strong>much more savage \u201cold developer yelling at the cloud\u201d version<\/strong>, with jokes about Web Forms, SOAP, Entity Framework, Angular, React, Docker, Kubernetes, and microservices.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Apparently We\u2019ve Finally Admitted Controllers Were a Mistake An entirely unbiased account from a developer who has been writing front ends, back ends, and increasingly desperate resignation letters for about 30 years. I have been a software developer for roughly 30 years. That means I have witnessed the entire evolution of web development, from: \u201cLet\u2019s&hellip;&nbsp;<a href=\"https:\/\/twobitranch.io\/index.php\/2024\/02\/27\/net-and-minimal-apis\/\" class=\"\" rel=\"bookmark\">Read More &raquo;<span class=\"screen-reader-text\">.NET Minimal APIs:<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts\/32"}],"collection":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/comments?post=32"}],"version-history":[{"count":2,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts\/32\/revisions"}],"predecessor-version":[{"id":70,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/posts\/32\/revisions\/70"}],"wp:attachment":[{"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/media?parent=32"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/categories?post=32"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/twobitranch.io\/index.php\/wp-json\/wp\/v2\/tags?post=32"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}