<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Naveed Ausaf</title>
    <description>The latest articles on Forem by Naveed Ausaf (@nausaf).</description>
    <link>https://forem.com/nausaf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F946134%2F34dd68a4-6313-49a5-b490-545ef06c4aea.png</url>
      <title>Forem: Naveed Ausaf</title>
      <link>https://forem.com/nausaf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nausaf"/>
    <language>en</language>
    <item>
      <title>OpenAPI in .NET 10: From Setup to Build-Time Generation (with Scalar UI)</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Mon, 20 Apr 2026 18:30:27 +0000</pubDate>
      <link>https://forem.com/nausaf/openapi-in-net-from-setup-to-build-time-generation-with-scalar-ui-1bio</link>
      <guid>https://forem.com/nausaf/openapi-in-net-from-setup-to-build-time-generation-with-scalar-ui-1bio</guid>
      <description>&lt;h2&gt;
  
  
  Why OpenAPI and Build-Time Generation Matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;OpenAPI&lt;/strong&gt; is the industry standard for describing REST APIs. &lt;/p&gt;

&lt;p&gt;By providing machine-readable descriptions of your endpoints, OpenAPI enables a rich ecosystem of tools such as&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://scalar.com/" rel="noopener noreferrer"&gt;Scalar UI&lt;/a&gt;&lt;/strong&gt;, which turns the raw JSON of an OpenAPI document into beautiful, interactive documentation of your API where you can also test the API like you would with Postman or Hoppscotch.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://stoplight.io/open-source/spectral" rel="noopener noreferrer"&gt;Spectral&lt;/a&gt;&lt;/strong&gt;, which lints an OpenAPI document for issues and inconsistencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://learn.microsoft.com/en-us/openapi/kiota/overview" rel="noopener noreferrer"&gt;Microsoft Kiota&lt;/a&gt;&lt;/strong&gt;, which generates strongly typed client SDKs so you don't have to write manual HTTP wrapper code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ASP.NET Core has &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview?view=aspnetcore-10.0" rel="noopener noreferrer"&gt;excellent built-in support&lt;/a&gt; for auto-generating OpenAPI documents from your code, an approach known as &lt;strong&gt;Code-First&lt;/strong&gt; (as opposed to &lt;a href="https://apisyouwonthate.com/blog/a-developers-guide-to-api-design-first/" rel="noopener noreferrer"&gt;&lt;strong&gt;Design-First&lt;/strong&gt;&lt;/a&gt;, where you author the document by hand upfront).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post I will show you how to set up OpenAPI in your .NET APIs&lt;/strong&gt;. This setup will include :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAPI document generation&lt;/li&gt;
&lt;li&gt;Scalar UI for interactive browsing of the OpenAPI spec and API testing&lt;/li&gt;
&lt;li&gt;build-time generation so that the OpenAPI document is produced during every build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last of these — build-time generation — matters more than it might seem, and I'll explain exactly why when we get to it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here's what Scalar UI looks like once it's set up:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0q1n8xlw8u6d4851xiq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0q1n8xlw8u6d4851xiq.png" alt=" " width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Sample Code
&lt;/h2&gt;

&lt;p&gt;You can see OpenAPI document generation in action in a .NET 10 minimal API in the &lt;a href="https://github.com/naveedausaf/routing-patterns-openapi" rel="noopener noreferrer"&gt;sample code repo on GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To run the project, press F5 in VS Code or GitHub Codespace, or run &lt;code&gt;dotnet run&lt;/code&gt; on the command line. &lt;/p&gt;

&lt;p&gt;This would launch the Scalar UI shown above in which you would be able to browse the generated OpenAPI document and test the API.&lt;/p&gt;
&lt;h2&gt;
  
  
  Set up OpenAPI document generation and Scalar UI
&lt;/h2&gt;

&lt;p&gt;In order to set up OpenAPI document generation and Scalar UI in your .NET minimal API project, proceed as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add a reference to package &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt;.&lt;br&gt;
This package provides classes that enable automatic OpenAPI document generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add statements &lt;code&gt;builder.Services.AddOpenApi();&lt;/code&gt; and &lt;code&gt;app.MapOpenApi();&lt;/code&gt; to &lt;code&gt;Program.cs&lt;/code&gt;, as &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-10.0&amp;amp;tabs=net-cli%2Cvisual-studio-code" rel="noopener noreferrer"&gt;described here in MS Docs&lt;/a&gt;.&lt;br&gt;
This enables the OpenAPI document for the API to be auto-generated and served at &lt;code&gt;/openapi/v1.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// adds and registers the OpenAPI document generator and related services in the DI container&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// exposes endpoint `/openapi/v1.json` over which the autogenerated OpenAPI document for the API is served&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Modify Handlers:&lt;/strong&gt; Several extension methods provided by the package &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt; can be &lt;strong&gt;chained to route prefix and handler registrations&lt;/strong&gt; to provide OpenAPI metadata about them.&lt;/p&gt;

&lt;p&gt;These augment the metadata that OpenAPI services automatically deduce from handler signatures (parameter lists, return types etc.).&lt;/p&gt;

&lt;p&gt;I gather together endpoint handlers for all operations at a given route segment, such as &lt;code&gt;/product&lt;/code&gt;, in a static &lt;a href="https://dev.to/nausaf/patterns-for-routing-in-aspnet-minimal-apis-1e8c#pattern-1-handlers-class"&gt;&lt;em&gt;Handlers&lt;/em&gt; class&lt;/a&gt; (&lt;code&gt;ProductHandlers&lt;/code&gt; in the snippet below; see sample code for the full implementation). &lt;/p&gt;

&lt;p&gt;In this class I also expose a static &lt;code&gt;MapRoutesAndDescribe&lt;/code&gt; method that both registers those handlers and chains OpenAPI extension methods like &lt;code&gt;.WithTags()&lt;/code&gt;, &lt;code&gt;.WithSummary()&lt;/code&gt; and others to declare OpenAPI metadata on both the individual handlers and on the route prefix (&lt;code&gt;/products&lt;/code&gt; below):&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;RouteGroupBuilder&lt;/span&gt; &lt;span class="nf"&gt;MapRoutesAndDescribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RouteGroupBuilder&lt;/span&gt; &lt;span class="n"&gt;baseRouteGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// OpenAPI Tag "Product Operations" is added to the group of handlers in this Handlers class under Route prefix `/products`&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;routeBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseRouteGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RoutePrefix&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product Operations"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;routeBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HandleCreateProduct&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandlerNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateProduct&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Creates a new product in the system."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;routeBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HandleGetProduct&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandlerNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProduct&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetches the product details for a given product id."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;routeBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Whether you use &lt;em&gt;Handlers&lt;/em&gt; classes like the one given above to register groups of handlers in your API, or register them in some other way, chain &lt;code&gt;.WithTags()&lt;/code&gt;, &lt;code&gt;.WithSummary()&lt;/code&gt; and &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/include-metadata?view=aspnetcore-10.0&amp;amp;tabs=minimal-apis" rel="noopener noreferrer"&gt;other extension methods given here&lt;/a&gt; to add metadata to individual handler registrations.&lt;/p&gt;

&lt;p&gt;If you create a &lt;code&gt;RouteGroupBuilder&lt;/code&gt; for the route segment for a group of handlers (as the Handlers class above does), then chain a &lt;code&gt;WithTags()&lt;/code&gt; to this also, as I have done above.&lt;/p&gt;

&lt;p&gt;When viewed in Scalar UI (which we'll set up shortly), the result should look something like the screenshot below. In this, I have highlighted the bits that correspond to the metadata declared in snippet above in red outline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7pufng660ye8dugozgf1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7pufng660ye8dugozgf1.png" alt=" " width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Add XML documentation comments on handlers and on the types of parameters that are bound to HTTP request&lt;/strong&gt;. For example, on the handler:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Creates a new product in the database.&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;param name="p"&amp;gt;Details of the product to be created.&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;response code="201"&amp;gt;Product created successfully. URL to GET the newly created product is in the &amp;lt;c&amp;gt;Location&amp;lt;/c&amp;gt; response header.&amp;lt;/response&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleCreateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateProductArgs&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LinkGenerator&lt;/span&gt; &lt;span class="n"&gt;linkGen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;And since &lt;code&gt;CreateProductArgs p&lt;/code&gt;, being the only complex-type parameter in the handler's parameter list, is automatically bound to the JSON body of the HTTP request, we need to provide XML documentation comments for it too:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Details of the product to be created&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CreateProductArgs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Name of the product&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// A description for the product&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// URL of an image of the product&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ImageUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Price of the product. Must be greater than or equal to zero.&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The XML documentation comments given above generate OpenAPI documentation for the endpoint handler that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4dq6k84i7te54m7aa19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4dq6k84i7te54m7aa19.png" alt=" " width="733" height="761"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At this step, please ensure the following:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only provide &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; tags for those parameters that are bound to request parameters&lt;/strong&gt;. 
For example &lt;code&gt;CreateProductArgs p&lt;/code&gt; above, being the only complex type parameter in the handler's parameter list, is automatically bound to JSON request body in a minimal API. So I have included a &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; tag for it.
On the other hand, &lt;code&gt;IProductService productService&lt;/code&gt; and &lt;code&gt;LinkGenerator linkGen&lt;/code&gt; are not bound to contents of the HTTP request (both of these happen to be sourced from DI container). Therefore I have made sure that there is no &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; tag for either of them.
&lt;strong&gt;Why do this?&lt;/strong&gt; because if we include a &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; tag in XML documentation comments for a parameter that is not model-bound (i.e. not bound to any part of the HTTP request) then its documentation shows up spuriously as part of the request for the endpoint in generated OpenAPI spec. Screenshot below shows this happening if I include a &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; tag for &lt;code&gt;LinkGenerator linkGen&lt;/code&gt; parameter in the above handler which is resolved from DI container:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famfgj62wbbbgg3ej9aie.png" alt=" " width="721" height="798"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* **Handler methods should NOT be `private`**. XML documentation comments of `private` handlers are not visible to OpenAPI document generator. This is why I made them `internal` in the code shown above.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Configure &lt;code&gt;.csproj&lt;/code&gt; to collect the XML documentation comments in your project and compile them into file &lt;code&gt;bin/&amp;lt;build configuration e.g. Debug&amp;gt;/net10.0/&amp;lt;project name&amp;gt;.xml&lt;/code&gt;.&lt;br&gt;
When this file is present, it will be automatically used in generating OpenAPI document.&lt;br&gt;
&lt;strong&gt;To configure generation of the XML documentation comments file at build time,&lt;/strong&gt; place &lt;code&gt;&amp;lt;GenerateDocumentationFile&amp;gt;true&amp;lt;/GenerateDocumentationFile&amp;gt;&lt;/code&gt; in your API project's &lt;code&gt;.csproj&lt;/code&gt; file within a &lt;code&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/code&gt; element, e.g. :&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!--Turn on generation of .xml into build output folder that would contain all the XML documentation comments in the project--&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;GenerateDocumentationFile&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/GenerateDocumentationFile&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;When you build your API project (e.g. with &lt;code&gt;dotnet build&lt;/code&gt;), because of including the above attribute in &lt;code&gt;.csproj&lt;/code&gt;, you would get warnings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdubcm3r7e6l09lghpc5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdubcm3r7e6l09lghpc5a.png" alt=" " width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These warnings would be of one or both of the following two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs1591" rel="noopener noreferrer"&gt;CS1591&lt;/a&gt; warns you that XML documentation comments are missing altogether on a type &lt;em&gt;or&lt;/em&gt; member that is publicly visible.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs1573" rel="noopener noreferrer"&gt;CS1573&lt;/a&gt; is reported when an XML documentation comment - a &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; tag - is specified for &lt;em&gt;some&lt;/em&gt; but &lt;em&gt;not all&lt;/em&gt; parameters on a method.
This warning would definitely be reported if you follow the convention described above of always having a &lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt; XML documentation tag for every parameter in a handler method that is model-bound (i.e. binds to some part of the incoming HTTP request) and always omitting it for every other parameter in the handler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I personally like to turn both of these off, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CS1573 arises by design. I might as well get rid of the noise by silencing these&lt;/li&gt;
&lt;li&gt;In API project, I do not always have XML documentation comments on every public type or member. I only provide these where they would go into the generated OpenAPI document. Therefore like to turn off CS1591 also.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If you would like to turn these warnings off, add a &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/errors-warnings" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;NoWarn&amp;gt;&lt;/code&gt;&lt;/a&gt; XML element&lt;/strong&gt; within the same &lt;code&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/code&gt; in which you added the &lt;code&gt;&amp;lt;GenerateDocumentationFile&amp;gt;true&amp;lt;/GenerateDocumentationFile&amp;gt;&lt;/code&gt; MS Build property above:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt; &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!--Turn on generation of .xml into build output folder that would contain all the XML documentation comments in the project--&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;GenerateDocumentationFile&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/GenerateDocumentationFile&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!--Switch off compiler warnings:

     CS1573 that arises due to missing XML documentation comments in handler parameters (I do this deliberately otherwise OpenAPI document spuriously contains documentation comments from non-model-bound handler parameters)

     and CS1591 because I do not always place XML documentation comments on all publicly visible types and members (except on handlers and on model-bound types in handler parameters) --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NoWarn&amp;gt;&lt;/span&gt;$(NoWarn);1591;1573&lt;span class="nt"&gt;&amp;lt;/NoWarn&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add Scalar UI:&lt;/strong&gt; Add a reference to package &lt;code&gt;Scalar.AspNetCore&lt;/code&gt; and add line &lt;code&gt;app.MapScalarApiReference();&lt;/code&gt; to &lt;code&gt;Program.cs&lt;/code&gt; (as &lt;a href="https://blog.scalar.com/p/how-net-9-and-scalar-solve-the-problem" rel="noopener noreferrer"&gt;described here&lt;/a&gt;).&lt;br&gt;
You can place it right after the &lt;code&gt;app.MapOpenApi();&lt;/code&gt; call you add to &lt;code&gt;Program.cs&lt;/code&gt; above.&lt;/p&gt;

&lt;p&gt;Adding this line is what would serve the Scalar UI to browse the raw JSON of the OpenAPI document in an interactive web app (the Scalar UI) where you can also test your API.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have completed the steps above, you can now run the API and navigate to &lt;code&gt;http://localhost:xxxx/scalar&lt;/code&gt; to browse the API's OpenAPI documentation and test it (as shown in the screenshot in intro section above).&lt;/p&gt;

&lt;p&gt;You can also see the raw JSON OpenAPI document at &lt;code&gt;http://localhost:xxxx/openapi/v1.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Note: Other ways of adding OpenAPI metadata to your project
&lt;/h3&gt;

&lt;p&gt;In addition to these ways of adding OpenAPI metadata that you exercised above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extension methods to provide metadata on handler and &lt;code&gt;RouteGroupBuilder&lt;/code&gt; registrations&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-10.0#openapi" rel="noopener noreferrer"&gt;Starting in .NET 10&lt;/a&gt;, XML documentation comments on the handlers and on the types used on those handlers can also be picked up by OpenAPI document generator. 
This is more powerful now than in the previous incarnation of this facility that existed before .NET 7.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;you can use the following to tweak OpenAPI metadata in your API project:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/include-metadata?view=aspnetcore-10.0&amp;amp;tabs=minimal-apis" rel="noopener noreferrer"&gt;C# attributes&lt;/a&gt; provided by the same package (&lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If other techniques - extension methods, C# attributes and XML documentation comments - fall short, OpenAPI document generation can be customized using &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi?view=aspnetcore-10.0" rel="noopener noreferrer"&gt;custom transformers&lt;/a&gt; provided to &lt;code&gt;builder.Services.AddOpenApi()&lt;/code&gt; call in &lt;code&gt;Program.cs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Set up Build-time Generation of OpenAPI JSON document
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem with Runtime-Only Generation
&lt;/h3&gt;

&lt;p&gt;The reason the OpenAPI document is only generated at runtime is that the services need to examine OpenAPI metadata explicitly configured in code in addition to other reflection-based sources. However, relying solely on runtime generation creates a gap in the development lifecycle. The document is missing at build time, where it is needed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linting:&lt;/strong&gt; Automatically checking the document for issues such as errors and OWASP compliance using a tool like Spectral.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review of Changes to the Contract:&lt;/strong&gt; Inclusion in your Git commit so that any changes to it are visible during the Pull Request (PR) review of the commit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second point is vital. Because the document is synthesized from numerous sources of metadata — XML documentation comments, C# attributes, extension method calls and custom configuration code — that are dispersed all over the API code, a small change to one file can quietly alter the OpenAPI document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But this document is your API contract:&lt;/strong&gt; &lt;em&gt;any&lt;/em&gt; change to it should be reviewed before the corresponding code reaches production.&lt;/p&gt;

&lt;p&gt;A natural place to do this review is as part of the PR review for the commit that led to those changes. However, this is only possible if the OpenAPI document (e.g. &lt;code&gt;openapi.json&lt;/code&gt;) is generated during a pre-commit build (e.g. to run tests), then checked into source control along with the code changes.&lt;/p&gt;

&lt;p&gt;In the section below, I show you how to set up build-time generation for your API's OpenAPI document, so that it is generated whenever the API project is built and subsequently checked into source control along with the code changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up Build-time Generation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Set up build time generation of OpenAPI document so it can be checked in to source control:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;In the project folder on the terminal, run:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Microsoft.Extensions.ApiDescription.Server
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This package contributes build assets to the &lt;code&gt;.csproj&lt;/code&gt; that execute during build of the project. They run the API during the build process, call the &lt;code&gt;/openapi/v1.json&lt;/code&gt; endpoint to fetch the dynamically generated OpenAPI document and save it as &lt;code&gt;bin/&amp;lt;configuration such as Debug&amp;gt;/netX.0/&amp;lt;Project Name&amp;gt;.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Clearly the path doesn't work if we are going to check it into source control (&lt;code&gt;bin&lt;/code&gt; folder is rightly excluded from source control in .NET &lt;code&gt;.gitignore&lt;/code&gt; files). We will fix this in the next step.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modify the output path and filename:&lt;/strong&gt; To put the generated OpenAPI document into project root, place the following in your &lt;code&gt;.csproj&lt;/code&gt; file. &lt;br&gt;
 You have to pass &lt;code&gt;--file-name&lt;/code&gt; in the XML tag &lt;code&gt;&amp;lt;OpenApiGenerateDocumentsOptions&amp;gt;&amp;lt;/OpenApiGenerateDocumentsOptions&amp;gt;&lt;/code&gt; in order to change the path. Therefore it is an opportunity to change the name of the document from &lt;code&gt;&amp;lt;ProjectName&amp;gt;.json&lt;/code&gt; to something else. I have changed it to &lt;code&gt;openapi.json&lt;/code&gt; below which I prefer to the default as it is more stable (does not depend on project name).&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;OpenApiDocumentsDirectory&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/OpenApiDocumentsDirectory&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;OpenApiGenerateDocumentsOptions&amp;gt;&lt;/span&gt;--file-name openapi&lt;span class="nt"&gt;&amp;lt;/OpenApiGenerateDocumentsOptions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modify &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/strong&gt; The API is executed during build when configuration information such as database connection strings and API keys would likely not be available (especially in CI/CD pipeline where build is cleanly separated from the execution/deployment jobs in which configuration information is sourced securely). &lt;/p&gt;

&lt;p&gt;Therefore we want to modify &lt;code&gt;Program.cs&lt;/code&gt; so that if it is executed during build - by the OpenAPI document generation step in the build - then services and middlewares that require configuration information are not added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: the sample code repo does not include this modification to &lt;code&gt;Program.cs&lt;/code&gt;&lt;/strong&gt;. However, I regularly use the following technique - from &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-10.0&amp;amp;tabs=net-cli%2Cnetcore-cli#customize-runtime-behavior-during-build-time-document-generation" rel="noopener noreferrer"&gt;here in MS Docs&lt;/a&gt; - to exclude &lt;code&gt;.AddXXX()&lt;/code&gt; statements to add those services and &lt;code&gt;.UseXXX&lt;/code&gt; statements to add those middlewares that require configuration information to be available. &lt;/p&gt;

&lt;p&gt;To understand the condition in the conditional execution (&lt;code&gt;if&lt;/code&gt;) blocks, note that the .NET assembly for command line tool that launches the API during build to fetch the OpenAPI document has assembly name &lt;code&gt;GetDocument.Insider&lt;/code&gt;. Therefore we check for the fact that the API project has been launched from this assembly to deduce that this run is happening at build time.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs file&lt;/span&gt;

&lt;span class="c1"&gt;// We check the entry assembly name to detect if the app is being executed by the OpenAPI build tool (whose assembly is named "GetDocument.Insider") &lt;/span&gt;
&lt;span class="c1"&gt;// See MS Docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-10.0&amp;amp;tabs=net-cli%2Cnetcore-cli#customize-runtime-behavior-during-build-time-document-generation&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;isBuildTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEntryAssembly&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nf"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"GetDocument.Insider"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;isBuildTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="c1"&gt;// exclude OpenTelemetry initialization as connection information for telemetry backend is not going to be available at build time when the app is run to generate Open API document&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// other statements for adding services to DI container...&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;isBuildTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="c1"&gt;//exclude adding app's DbContext to DI container as database connection string is not going to be available at build time when the app is run to generate Open API document&lt;/span&gt;
    &lt;span class="n"&gt;connString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CloudCartDB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connection string 'CloudCartDB' is not configured."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CloudCartDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseNpgsql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableSensitiveDataLogging&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// add middlewares and endpoints...&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;If you have completed the above steps, then whenever your project is built, the OpenAPI document - &lt;code&gt;openapi.json&lt;/code&gt; in the API project folder - would be regenerated&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Other things you can do with your OpenAPI document
&lt;/h2&gt;

&lt;p&gt;Now that you have set up OpenAPI document generation in your API, here are some pointers to other things you can do with the document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lint it with Spectral:&lt;/strong&gt; Spectral is an amazing tool for linting OpenAPI documents for a host of issues such as correctness, compliance with your organizational best practices and &lt;a href="https://apisyouwonthate.com/blog/securing-apis-with-spectral-owasp-ruleset/" rel="noopener noreferrer"&gt;any OWASP issues in API design that may be detectable from its OpenAPI document&lt;/a&gt;.&lt;br&gt;
I have not set up Spectral in the sample code as it is a Node.js tool and its setup would rather detract from the .NET focus of this article.&lt;br&gt;
However, if you are interested in setting it up, here are some links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See &lt;a href="https://dev.to/mnlth/linting-openapi-specs-using-spectral-37b"&gt;this post&lt;/a&gt; for setting up Spectral. 
The instructions should work fine run from the solution root - some NPM and Node.js files will get created in that folder beside the &lt;code&gt;sln&lt;/code&gt;/&lt;code&gt;slnx&lt;/code&gt; file but it should not interfere with the .NET solution. I haven't tested this specific configuration myself as my own spectral setups have been in polyglot solutions containing .NET APIs where I have installed Spectral one level above the .NET solution folder.&lt;/li&gt;
&lt;li&gt;Build project and run Spectral on it at every Git commit - so that the commit is aborted if linting fails - using &lt;a href="https://alirezanet.github.io/Husky.Net/" rel="noopener noreferrer"&gt;Husky.NET&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Check out Spectral's &lt;a href="https://docs.stoplight.io/docs/spectral/a630feff97e3a-concepts" rel="noopener noreferrer"&gt;own documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Return Problem Details responses and include them in your OpenAPI document:&lt;/strong&gt; &lt;a href="https://www.rfc-editor.org/rfc/rfc9457.html" rel="noopener noreferrer"&gt;IETF Problem Details RFC 9457&lt;/a&gt; is a superb, really lightweight standard for returning problems and errors from your APIs. It also has pretty good support in .NET Core.&lt;br&gt;&lt;br&gt;
I have an upcoming dev.to post on how to return Problem Details responses from a .NET Core minimal API and include these in the OpenAPI document. &lt;strong&gt;If you &lt;a href="https://dev.to/nausaf"&gt;follow&lt;/a&gt; me, you would get notified&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;OpenAPI document generation in a .NET minimal API is a matter of assembling the right pieces — the &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt; package, a handful of extension methods and XML documentation comments, Scalar UI for interactive browsing and testing, and build-time document generation.&lt;/p&gt;

&lt;p&gt;The last of these items is particularly important: once you set up your OpenAPI document to be produced at build time, it can be committed alongside your code, contract changes become visible in PR diffs and linting can run automatically — your API's public contract is transparent throughout the DevOps pipeline and no longer something that only materializes at runtime.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>openapi</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Patterns for Routing in ASP.NET Minimal APIs</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Mon, 20 Apr 2026 08:10:06 +0000</pubDate>
      <link>https://forem.com/nausaf/patterns-for-routing-in-aspnet-minimal-apis-1e8c</link>
      <guid>https://forem.com/nausaf/patterns-for-routing-in-aspnet-minimal-apis-1e8c</guid>
      <description>&lt;p&gt;In ASP.NET Core, &lt;strong&gt;Routing&lt;/strong&gt; is the mechanism that connects an incoming HTTP request to the code responsible for handling it. It performs two basic functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routing:&lt;/strong&gt; maps URLs in incoming HTTP requests to endpoint handlers that would handle those requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link Generation:&lt;/strong&gt; given a handler, generate a URL (that may be used to invoke that handler in the future). This, in a sense is the opposite of Routing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Minimal APIs are great because they get rid of the boilerplate and give you a great deal of flexibility in how to construct your API. This flexibility is also their downside and needs to be harnessed with clear patterns for organising code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This post gives a few patterns for Routing that would help to keep the code of your ASP.NET Minimal API projects well-organised&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can skip the intro to routing and &lt;a href="https://dev.to/nausaf/patterns-for-routing-in-aspnet-minimal-apis-1e8c#sample-code"&gt;go straight to the patterns&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Routing Works in ASP.NET
&lt;/h2&gt;

&lt;p&gt;To understand routing, we have to look at two distinct phases in an ASP.NET application: &lt;strong&gt;Startup&lt;/strong&gt; and &lt;strong&gt;Runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Startup Phase: Registration
&lt;/h3&gt;

&lt;p&gt;During application startup (usually in &lt;code&gt;Program.cs&lt;/code&gt;), you register your endpoints, mapping a &lt;strong&gt;route template&lt;/strong&gt; (like &lt;code&gt;/products/{id}&lt;/code&gt;) to a &lt;strong&gt;handler&lt;/strong&gt; (a delegate or method).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// set up middleware pipeline with `app.UseXXX` calls&lt;/span&gt;

&lt;span class="c1"&gt;// register endpoint handlers with `app.MapXXX` calls&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/products/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// find and return product with given `productId`&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get-product"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateProductArgs&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// create product in database with details given in `product`&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create-product"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// start the app&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally, you can also register middlewares using &lt;code&gt;app.UseXXX()&lt;/code&gt; methods - these intercept your request and potentially process, or alter it, until it reaches a handler.&lt;/p&gt;

&lt;p&gt;Registering endpoint handlers like this makes &lt;code&gt;Program.cs&lt;/code&gt; large and messy. &lt;strong&gt;Controlling this "bloat" is what most of the patterns in this post are about&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Runtime Phase: The Middleware Pipeline
&lt;/h3&gt;

&lt;p&gt;When a request hits your server, it travels through the &lt;strong&gt;Middleware Pipeline&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftuzltr7dmz5w6i34t28k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftuzltr7dmz5w6i34t28k.png" alt=" " width="800" height="805"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two key middlewares involved in routing are the &lt;strong&gt;Routing Middleware&lt;/strong&gt; and the &lt;strong&gt;Endpoint Middleware&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RoutingMiddleware&lt;/strong&gt;: When a request comes in, this middleware matches the URL to a registered route template. It doesn't execute the code yet; it simply selects the "Endpoint" - the handler registered for the matched route template - and attaches it to the &lt;code&gt;HttpContext&lt;/code&gt;.&lt;br&gt;
 If you don't call &lt;code&gt;app.UseRouting()&lt;/code&gt; in &lt;code&gt;Program.cs&lt;/code&gt;, this would be &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-10.0#middleware-order:~:text=app.UseRouting()%3B%20//%20If%20not%20called%2C%20runs%20at%20the%20beginning%20of%20the%20pipeline%20by%20default" rel="noopener noreferrer"&gt;added automatically as the first middleware in the pipeline&lt;/a&gt; when you call &lt;code&gt;app.Run()&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The "In-Between"&lt;/strong&gt;: Any middleware placed between these two, like authorization and authentication middlewares, can now see &lt;em&gt;which&lt;/em&gt; handler is about to be called and make decisions based on that, including returning a response that short-circuits the rest of the pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EndpointMiddleware&lt;/strong&gt;: This is the final stop. It takes the handler stored in the &lt;code&gt;HttpContext&lt;/code&gt;, performs &lt;strong&gt;Model Binding&lt;/strong&gt; - mapping request contents such as URL parameters like &lt;code&gt;{id}&lt;/code&gt; and request body JSON data to handler method's arguments - and executes the handler.&lt;br&gt;
This is the last middleware in the pipeline and added automatically if you didn't call &lt;code&gt;app.UseEndpoints()&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The response then travels back through the middleware pipeline in reverse order.&lt;/p&gt;

&lt;p&gt;For more details, see the &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing" rel="noopener noreferrer"&gt;MS Docs on Routing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample Code
&lt;/h2&gt;

&lt;p&gt;Patterns 1 - 3 are illustrated in a .NET minimal API project in the &lt;a href="https://github.com/naveedausaf/routing-patterns-openapi" rel="noopener noreferrer"&gt;sample code repo on GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Pattern 4 is not part of the sample code but is quite straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: &lt;em&gt;Handlers&lt;/em&gt; class
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Move your route registrations and handlers out of &lt;code&gt;Program.cs&lt;/code&gt; and into dedicated service-specific &lt;code&gt;static&lt;/code&gt; classes each of which contains the endpoint handlers for a service or group and provides a public &lt;code&gt;MapRoutes&lt;/code&gt; method to register them with ASP.NET.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;In a REST API a group would consist of all handlers that map to the same route such as &lt;code&gt;/products&lt;/code&gt;  and provide operations for the resource at that route, e.g. Products, but at different HTTP verbs (&lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt; etc.)  and/or at different route segments (such as &lt;code&gt;/products/{id}&lt;/code&gt; or &lt;code&gt;/products&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We would move all handlers for &lt;code&gt;/product&lt;/code&gt; out of &lt;code&gt;Program.cs&lt;/code&gt; and into a &lt;em&gt;Handlers&lt;/em&gt; class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductHandlers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HandlerNames&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;GetProduct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get-product"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CreateProduct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"create-product"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;RoutePrefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;RouteGroupBuilder&lt;/span&gt; &lt;span class="nf"&gt;MapRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RouteGroupBuilder&lt;/span&gt; &lt;span class="n"&gt;baseRouteGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;routeBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseRouteGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RoutePrefix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;routeBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HandleCreateProduct&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandlerNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateProduct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;routeBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HandleGetProduct&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandlerNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProduct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;routeBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleGetProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="cm"&gt;/* handler logic */&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleCreateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateProductArgs&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LinkGenerator&lt;/span&gt; &lt;span class="n"&gt;linkGen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* handler logic */&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;Program.cs&lt;/code&gt;, create a &lt;code&gt;RouteGroupBuilder&lt;/code&gt; for the base URL of the API, then call the static &lt;code&gt;MapRoutes&lt;/code&gt; method in the &lt;em&gt;Handlers&lt;/em&gt; class to register all the handlers contained in the class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In Program.cs&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;RouteGroupBuilder&lt;/span&gt; &lt;span class="n"&gt;v1ApiRouteGroup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ProductHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v1ApiRouteGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This is what the &lt;em&gt;Handlers&lt;/em&gt; class contains:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;HandleXXX&lt;/code&gt; method for each endpoint handler&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A nested static class &lt;code&gt;HandlerNames&lt;/code&gt;&lt;br&gt;
This contains  string constants for handler names that are provided to &lt;code&gt;.WithName&lt;/code&gt; call that is chained to a &lt;code&gt;.MapXXX&lt;/code&gt; call for handler registration.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HandlerNames&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;GetProduct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get-product"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CreateProduct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"create-product"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Constant &lt;code&gt;RoutePrefix&lt;/code&gt;. This is the common prefix of URLs of handlers in the group:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;RoutePrefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Being &lt;code&gt;public&lt;/code&gt; allows it to be used in unit- and integration tests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;code&gt;MapRoutes&lt;/code&gt; static method that does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;takes a &lt;code&gt;RouteGroupBuilder baseRouteGroup&lt;/code&gt; argument that represents the base URL of the API - e.g. &lt;code&gt;/&lt;/code&gt; or, if your API versioning is based on base URL prefixes for major version numbers, then something like &lt;code&gt;/v1&lt;/code&gt; as used above&lt;/li&gt;
&lt;li&gt;Registers all handlers in the class at &lt;code&gt;RoutePrefix&lt;/code&gt; relative to the &lt;code&gt;baseRouteGroup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Declares a name for each handler that is unique among all handlers registered in the API app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the example above, &lt;code&gt;MapRoutes&lt;/code&gt; registers two handler methods, &lt;code&gt;HandleCreateProduct&lt;/code&gt; and &lt;code&gt;HandleGetProduct&lt;/code&gt; at routes &lt;code&gt;/v1/products&lt;/code&gt; and &lt;code&gt;/v1/products/{id}&lt;/code&gt;, with respective names &lt;code&gt;create-product&lt;/code&gt; and &lt;code&gt;get-product&lt;/code&gt; that are unique among all handlers registered in the API application.&lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clean Program.cs:&lt;/strong&gt; As your app grows, &lt;code&gt;Program.cs&lt;/code&gt; can quickly become a 1,000-line "god file." This keeps it focused on high-level configuration by moving both the handlers themselves and the code for their registration out of it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cohesion:&lt;/strong&gt; All handlers for a service or a REST are now to be found in the one place together with their registration details (including their names and the routes at which they map).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open API metadata:&lt;/strong&gt; &lt;code&gt;MapRoutes&lt;/code&gt; in the Handlers class is the perfect place to chain &lt;code&gt;.WithTags()&lt;/code&gt;, &lt;code&gt;.WithSummary()&lt;/code&gt; and &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/include-metadata?view=aspnetcore-10.0&amp;amp;tabs=minimal-apis" rel="noopener noreferrer"&gt;other extension methods&lt;/a&gt; to provide Open API metadata on &lt;code&gt;RouteGroupBuilder&lt;/code&gt; for the route prefix for the whole group of handlers and on handler registrations themselves.&lt;br&gt;
&lt;em&gt;see example of this in the sample repo.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The &lt;em&gt;Clean Architecture&lt;/em&gt; Advantage:&lt;/strong&gt; If your API follows Clean Architecture, in particular if you separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;protocol specific &lt;em&gt;Interface Adapters&lt;/em&gt; such as the two HTTP/REST endpoint handlers above&lt;/li&gt;
&lt;li&gt;from the business logic of the operations which the code above hints is contained in the injected &lt;code&gt;IProductService&lt;/code&gt; implementation. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then your endpoints handlers would only be thin HTTP/REST wrappers over the more substantial operation logic contained in the actual business logic classes. They only handle concerns like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identifying routes at which requests are to be received (a REST concern)&lt;/li&gt;
&lt;li&gt;returning appropriate HTTP codes such as 2xx in case of success and 4xx or 5xx in case of errors (an HTTP concern) &lt;/li&gt;
&lt;li&gt;sending back data in a format that API callers can understand e.g. JSON (arguably a REST concern)&lt;/li&gt;
&lt;li&gt;in case of errors, translating exceptions thrown by business logic services into &lt;a href="https://www.rfc-editor.org/rfc/rfc9457" rel="noopener noreferrer"&gt;IETF Problem Details&lt;/a&gt; responses to send back to the API caller (arguably a REST concern)&lt;/li&gt;
&lt;li&gt;describing themselves for OpenAPI spec generation (see the section below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case it may make more sense for you to collect all of the endpoint handlers for a particular functionality or REST resource - such as all the handlers that invoke business logic that pertains to Products or Customers - in a single class (e.g. called &lt;code&gt;ProductHandlers&lt;/code&gt; or &lt;code&gt;CustomerHandlers&lt;/code&gt;), &lt;strong&gt;rather than have a separate file for each endpoint handler as per the &lt;a href="https://thecodeman.net/posts/repr-pattern" rel="noopener noreferrer"&gt;REPR pattern&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Of course if your endpoint handlers get too big or too numerous, you can still move them out into individual files&lt;/strong&gt; as per the REPR pattern. &lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pattern 2: Name Handlers for Reverse Routing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Never hardcode URLs when returning "Created" or "Redirect" results. Instead, use the &lt;code&gt;LinkGenerator&lt;/code&gt; to resolve URLs by the handler's name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleCreateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateProductArgs&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LinkGenerator&lt;/span&gt; &lt;span class="n"&gt;linkGen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Path-based generation is safer than URI-based&lt;/span&gt;
    &lt;span class="c1"&gt;// HandlerNames.GetProduct const has value "get-product"&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linkGen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPathByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HandlerNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TypedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose the id of the created product is &lt;code&gt;6f0ce3bd-cd86-425d-801a-d2c3e313cecf&lt;/code&gt;, then the returned URL would be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/products/6f0ce3bd-cd86-425d-801a-d2c3e313cecf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can see in &lt;code&gt;MapRoutes&lt;/code&gt; above how this handler is registered (in the usual way). &lt;/p&gt;

&lt;p&gt;The key here is the &lt;code&gt;LinkGenerator&lt;/code&gt; that is resolved from DI container by the model binder that is invoked by the &lt;code&gt;EndpointMiddleware&lt;/code&gt; and injected into the handler. &lt;/p&gt;

&lt;p&gt;Its &lt;code&gt;GetPathByName&lt;/code&gt; method computes a URL to the handler with unique name &lt;code&gt;get-product&lt;/code&gt;, with first route parameter (&lt;code&gt;{id}&lt;/code&gt;, see handler registration for &lt;code&gt;get-product&lt;/code&gt; handler in &lt;code&gt;MapRoutes&lt;/code&gt; above) set to the &lt;code&gt;id&lt;/code&gt; of the newly created Product returned by &lt;code&gt;IProductService.Create&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Refactor-Friendliness:&lt;/strong&gt; If you change your route template from &lt;code&gt;/product/{id}&lt;/code&gt; to &lt;code&gt;/catalog/{id}&lt;/code&gt;, you don’t have to hunt through your code to update string URLs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decoupling:&lt;/strong&gt; Handlers don't need to know the structure of the rest of the app's URL space.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt; By using &lt;code&gt;GetPathByName&lt;/code&gt; instead of &lt;code&gt;GetUriByName&lt;/code&gt;, you prevent &lt;a href="https://www.fastly.com/learning/security/what-are-http-host-header-attacks" rel="noopener noreferrer"&gt;&lt;strong&gt;Host Header attacks&lt;/strong&gt;&lt;/a&gt;. &lt;code&gt;GetPath&lt;/code&gt; returns a relative path (e.g., &lt;code&gt;/product/12&lt;/code&gt;), whereas &lt;code&gt;GetUri&lt;/code&gt; includes the domain, which can be manipulated if your server isn't strictly configured to filter host headers.&lt;br&gt;
In fact, not relying on the Host header value anywhere in your application code - the base URL at which the client originally sent the request - is &lt;a href="https://www.fastly.com/learning/security/what-are-http-host-header-attacks#avoid-using-the-host-header-value-and-its-variants-in-application-code" rel="noopener noreferrer"&gt;one of the easiest ways&lt;/a&gt; of preventing Host Header attacks.&lt;br&gt;
This is also the reason why I do not use &lt;code&gt;TypedResults.CreatedAtRoute()&lt;/code&gt; convenience method which does the same thing as &lt;code&gt;TypedResults.Created()&lt;/code&gt; but does not required you to first have &lt;code&gt;LinkGenerator&lt;/code&gt; injected and call some &lt;code&gt;GetPathXXX()&lt;/code&gt; method on it yourself; it uses the &lt;code&gt;LinkGenerator&lt;/code&gt; behind the scenes so you don't have to. The problem is it returns an absolute URI e.g. &lt;code&gt;https://www.example.com/v1/products/6f0ce3bd-cd86-425d-801a-d2c3e313cecf&lt;/code&gt; rather than the root-relative URI that the method shown above returns which is safer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pattern 3: Routing without Validation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Keep route constraints (e.g., &lt;code&gt;{id:int}&lt;/code&gt;) to a minimum and avoid using them for business logic validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;Route constraints are for &lt;strong&gt;discovering&lt;/strong&gt; the correct endpoint. If a constraint fails, ASP.NET Core returns a &lt;code&gt;404 Not Found&lt;/code&gt; instead of running your handler. &lt;/p&gt;

&lt;p&gt;However, if the ID exists but is simply invalid (e.g., a negative number), you still want your endpoint handler to run when it could send back a helpful &lt;code&gt;400 Bad Request&lt;/code&gt; or &lt;code&gt;422 Unprocessable Content&lt;/code&gt; response with a descriptive message or JSON to describe the error in more detail.&lt;/p&gt;

&lt;p&gt;Better yet, validate the request with a library such as &lt;a href="https://docs.fluentvalidation.net/en/latest/index.html" rel="noopener noreferrer"&gt;Fluent Validation&lt;/a&gt; or .NET's built-in validation facilities (&lt;a href="https://ivangechev.com/blog/minimal-apis/validate-incoming-requests-net-10-minimal-apis" rel="noopener noreferrer"&gt;made automatic in .NET 10 minimal APIs&lt;/a&gt;) return the validation error in &lt;a href="https://www.rfc-editor.org/rfc/rfc9457.html" rel="noopener noreferrer"&gt;IETF Problem Details&lt;/a&gt; format. I have an upcoming post on the topic. &lt;strong&gt;If you &lt;a href="https://dev.to/nausaf"&gt;follow&lt;/a&gt; me, you would get notified!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Prefer the "Double Star" Catch-all
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; When capturing a file path or a multi-segment URL, use the &lt;code&gt;**&lt;/code&gt; prefix (e.g., &lt;code&gt;/{**slug}&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;While both &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;**&lt;/code&gt; capture the remainder of a URL in a route template, they handle "Reverse Routing" differently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A single &lt;code&gt;*&lt;/code&gt; will &lt;strong&gt;URL-encode&lt;/strong&gt; forward slashes (turning &lt;code&gt;/&lt;/code&gt; into &lt;code&gt;%2F&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A double &lt;code&gt;**&lt;/code&gt; keeps the slashes as they are. If you are generating a link to a file path, you almost certainly want the latter.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Routing in Minimal APIs is deceptively simple, but by moving logic out of &lt;code&gt;Program.cs&lt;/code&gt; and leaning on &lt;code&gt;LinkGenerator&lt;/code&gt;, you create an API that is both easier to maintain and more secure by default.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Use .env files for .NET local development configuration with VS Code</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Mon, 14 Jul 2025 21:33:51 +0000</pubDate>
      <link>https://forem.com/nausaf/use-env-files-for-storing-development-secrets-and-configuration-for-net-core-projects-in-vs-code-1kbh</link>
      <guid>https://forem.com/nausaf/use-env-files-for-storing-development-secrets-and-configuration-for-net-core-projects-in-vs-code-1kbh</guid>
      <description>&lt;h2&gt;
  
  
  The Problem with .NET Development Configuration
&lt;/h2&gt;

&lt;p&gt;Most ecosystems I use - Node.js, Docker/Compose, Terraform - expect configuration to come from environment variables.&lt;/p&gt;

&lt;p&gt;In production, and other non-local environments, the hosting service loads data from configuration stores (e.g. &lt;a href="https://learn.microsoft.com/en-us/azure/azure-app-configuration/overview" rel="noopener noreferrer"&gt;Azure App Configuration&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html" rel="noopener noreferrer"&gt;AWS AppConfig&lt;/a&gt;) and secret managers (like &lt;a href="https://azure.microsoft.com/en-us/products/key-vault/?msockid=1b11c87398d96fb62df5dc7999fe6ee8" rel="noopener noreferrer"&gt;Azure Key Vault&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/secretsmanager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;) and sets these as environment variables before launching code. &lt;/p&gt;

&lt;p&gt;For local development, config lives in &lt;code&gt;.env&lt;/code&gt; files and is loaded automatically into environment variables by the framework or the launcher. Such &lt;code&gt;.env&lt;/code&gt; files also typically contain secrets; this is mitigated by excluding them from the repo in &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Either way - and in any environment - the app code only ever looks for its configuration in environment variables,&lt;/strong&gt; which is in keeping with the philosophy of &lt;a href="https://12factor.net/config" rel="noopener noreferrer"&gt;12-factor apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Except in .NET!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In .NET, production configuration would still &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers#environment-variable-configuration-provider" rel="noopener noreferrer"&gt;get loaded from environment variables&lt;/a&gt; that are injected by the hosting platform (e.g. from &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/app-service-configuration-references" rel="noopener noreferrer"&gt;Azure App Configuration references&lt;/a&gt; or &lt;a href="https://learn.microsoft.com/en-us/azure/azure-app-configuration/use-key-vault-references-dotnet-core" rel="noopener noreferrer"&gt;Key Vault references&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;However, in local development, a .NET project loads configuration from an assortment of sources. &lt;strong&gt;This makes local .NET config stick out like a sore thumb when working in polyglot workspaces, where everything else just uses &lt;code&gt;.env&lt;/code&gt; files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, in a workspace in which I have a .NET Minimal API alongside a Next.js frontend and a Docker Compose &lt;code&gt;compose.yaml&lt;/code&gt;, both Next.js and Docker Compose can take in config data in &lt;code&gt;.env&lt;/code&gt; files. &lt;strong&gt;Here, I would like to use an &lt;code&gt;.env&lt;/code&gt; file such as the following to configure the .NET API also&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;ASPNETCORE_ENVIRONMENT&lt;/span&gt;=&lt;span class="n"&gt;Development&lt;/span&gt;
&lt;span class="n"&gt;ASPNETCORE_URLS&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;3022&lt;/span&gt;
&lt;span class="n"&gt;ALLOWED_CORS_ORIGINS&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;3020&lt;/span&gt;
&lt;span class="n"&gt;ConnectionStrings__EShop&lt;/span&gt;=&amp;lt;&lt;span class="n"&gt;redacted&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yet, &lt;strong&gt;instead of an &lt;code&gt;.env&lt;/code&gt; file, we have the following sources&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;appSettings.*.json&lt;/code&gt; configuration files. &lt;strong&gt;There are usually several of these, generated as part of boilerplate code&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-9.0&amp;amp;tabs=windows#secret-manager" rel="noopener noreferrer"&gt;.NET User Secrets Manager&lt;/a&gt; for secrets. &lt;strong&gt;This tool has its own &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-10.0&amp;amp;tabs=windows#how-the-secret-manager-tool-works" rel="noopener noreferrer"&gt;quirks&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;To complicate matters further,&lt;/strong&gt; .NET projects also contain a scaffolded &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-9.0#lsj" rel="noopener noreferrer"&gt;&lt;code&gt;Properties/launchSettings.json&lt;/code&gt;&lt;/a&gt;. While originally meant for use in Visual Studio (the older product that predates VS Code), it still gets loaded and used when you launch your app through a Debug/Launch configuration in VS Code or on the command line using &lt;code&gt;dotnet run&lt;/code&gt;. This too can provide config key/value pairs, sometimes without you realising it.&lt;/p&gt;

&lt;p&gt;All these sources of development-time configuration obscure the effective source of a config value (there is a precedence order among the sources). &lt;/p&gt;

&lt;p&gt;They are also discordant with how everything else is configured: via a single &lt;code&gt;.env&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For these reasons, I configure .NET projects in my polyglot workspaces to use &lt;code&gt;.env&lt;/code&gt; files for local development,&lt;/strong&gt; rather than the default sources above.&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The solution given is primarily for VS Code&lt;/strong&gt;. You may be able to adapt it for other IDEs such as JetBrains Rider or Visual Studio but those are outside the scope of this post. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To use a &lt;code&gt;.env&lt;/code&gt; file with a .NET project, an alternative approach is to use &lt;a href="https://github.com/bolorundurowb/dotenv.net" rel="noopener noreferrer"&gt;&lt;code&gt;dotenv-net&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;. I explain at the end of this post why I don't like using it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create &lt;code&gt;.env&lt;/code&gt; files for VS Code launch configurations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Store all configuration, secret and non-secret, for a .NET project in a VS Code launch configuration in an &lt;code&gt;.env&lt;/code&gt; file&lt;/strong&gt;. I keep this next to &lt;code&gt;launch.json&lt;/code&gt; in the &lt;code&gt;.vscode&lt;/code&gt; folder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs22yu2cm7mqih07j50h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs22yu2cm7mqih07j50h.png" alt=" " width="288" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I provide the path of such an &lt;code&gt;.env&lt;/code&gt; file in &lt;code&gt;envFile&lt;/code&gt; attribute in the launch configuration&lt;/strong&gt;. For example, consider the compound launch configuration in &lt;code&gt;launch.json&lt;/code&gt; given below. Note the &lt;code&gt;envFile&lt;/code&gt; attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compounds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Frontend/Backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Next.js: debug in full stack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;".NET: debug in full stack"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stopAll"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".NET: debug in full stack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coreclr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"preLaunchTask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"build-backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"program"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/flowmazonbackend/flowmazonapi/bin/Debug/net9.0/flowmazonapi.dll"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/flowmazonbackend/flowmazonapi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stopAtEntry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"envFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/.vscode/flowmazonapi.env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sourceFileMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"/Views"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/Views"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"requireExactSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Next.js: debug in full stack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/flowmazonfrontend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"program"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/flowmazonfrontend/node_modules/next/dist/bin/next"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3020"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"console"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integratedTerminal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"envFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/.vscode/flowmazonfrontend.env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"serverReadyAction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"- Local:.+(https?://.+)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"uriFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"debugWithChrome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"webRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/flowmazonfrontend"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;flowmazonapi.env&lt;/code&gt; configures the .NET minimal API and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;Logging__LogLevel__Default&lt;/span&gt;=&lt;span class="n"&gt;Information&lt;/span&gt;
&lt;span class="n"&gt;Logging__LogLevel__Microsoft&lt;/span&gt;.&lt;span class="n"&gt;AspNetCore&lt;/span&gt;=&lt;span class="n"&gt;Warning&lt;/span&gt;
&lt;span class="n"&gt;ASPNETCORE_ENVIRONMENT&lt;/span&gt;=&lt;span class="n"&gt;Development&lt;/span&gt;
&lt;span class="n"&gt;ASPNETCORE_URLS&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;3022&lt;/span&gt;
&lt;span class="n"&gt;ALLOWED_CORS_ORIGINS&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;3020&lt;/span&gt;
&lt;span class="n"&gt;ConnectionStrings__FlowmazonDB&lt;/span&gt;=&amp;lt;&lt;span class="n"&gt;redacted&lt;/span&gt;&amp;gt;
&lt;span class="n"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;=&lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;otlp&lt;/span&gt;-&lt;span class="n"&gt;gateway&lt;/span&gt;-&lt;span class="n"&gt;prod&lt;/span&gt;-&lt;span class="n"&gt;eu&lt;/span&gt;-&lt;span class="n"&gt;west&lt;/span&gt;-&lt;span class="m"&gt;2&lt;/span&gt;.&lt;span class="n"&gt;grafana&lt;/span&gt;.&lt;span class="n"&gt;net&lt;/span&gt;/&lt;span class="n"&gt;otlp&lt;/span&gt;
&lt;span class="n"&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;/&lt;span class="n"&gt;protobuf&lt;/span&gt;
&lt;span class="n"&gt;OTEL_RESOURCE_ATTRIBUTES&lt;/span&gt;=&lt;span class="n"&gt;deployment&lt;/span&gt;.&lt;span class="n"&gt;environment&lt;/span&gt;.&lt;span class="n"&gt;name&lt;/span&gt;=&lt;span class="n"&gt;vscode_launch&lt;/span&gt;
&lt;span class="n"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;=&amp;lt;&lt;span class="n"&gt;redacted&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;VS Code takes all the key value pairs in the specified &lt;code&gt;.env&lt;/code&gt; file and sets them as environment variables&lt;/strong&gt;. In ASP.NET Core, these are read by the environment variable config source in the &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-10.0" rel="noopener noreferrer"&gt;Generic Host&lt;/a&gt;. This is the last and therefore the highest priority config source in ASP.NET Core host's default sequence, so it overrides any other source providing the same keys.&lt;/p&gt;

&lt;p&gt;So having an &lt;code&gt;.env&lt;/code&gt; file to configure a .NET project in a VS Code launch configuration means it replaces, for local development, the canonical combination of &lt;code&gt;appSettings.*.json&lt;/code&gt; files and .NET User Secrets Manager. For example, the &lt;code&gt;ConnectionStrings__FlowmazonDB=&amp;lt;redacted&amp;gt;&lt;/code&gt; line in the &lt;code&gt;.env&lt;/code&gt; file shown above equates to the following in &lt;code&gt;appSettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ConnectionStrings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"FlowmazonDB"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;connection string redacted&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and these two lines in the &lt;code&gt;.env&lt;/code&gt; file above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;Logging__LogLevel__Default&lt;/span&gt;=&lt;span class="n"&gt;Information&lt;/span&gt;
&lt;span class="n"&gt;Logging__LogLevel__Microsoft&lt;/span&gt;.&lt;span class="n"&gt;AspNetCore&lt;/span&gt;=&lt;span class="n"&gt;Warning&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;emulate the default log levels in the boilerplate &lt;code&gt;appSettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Logging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"LogLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Information"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Microsoft.AspNetCore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warning"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that once you have created a &lt;code&gt;.env&lt;/code&gt; file like above, you can delete any &lt;code&gt;appSettings*&lt;/code&gt; files in the project's folder, which we will do shortly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SECURITY NOTE:&lt;/strong&gt; Please make sure that you have a &lt;code&gt;.gitignore&lt;/code&gt; file in the workspace root and that it contains the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would stop &lt;code&gt;.env&lt;/code&gt; files, which may contain secrets such as connection strings for databases you use for local development, from getting checked in. Note that this, together with any secret scanning you might have in your online repo (such as &lt;a href="https://docs.github.com/en/code-security/concepts/secret-security/about-secret-scanning" rel="noopener noreferrer"&gt;GitHub Secret Scanning&lt;/a&gt;), guards against your local development secrets from leaking into your repo. &lt;strong&gt;It is important to be aware that, as with .NET User Secrets Manager, any secrets are still stored locally in plaintext&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All of your local development-time secrets and other configuration - for all projects and not just .NET if you have multiple ecosystems - are now in the &lt;code&gt;.env&lt;/code&gt; files located in a single folder: in &lt;code&gt;.vscode&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Moreover, all these colocated &lt;code&gt;.env&lt;/code&gt; files are explicitly referenced in a single place: in launch configurations in &lt;code&gt;launch.json&lt;/code&gt; in the &lt;code&gt;.vscode&lt;/code&gt; folder. &lt;/p&gt;

&lt;p&gt;If you have multiple launch configurations that need to be configured differently, you can define separate &lt;code&gt;.env&lt;/code&gt; files for each of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Provide an &lt;code&gt;.env&lt;/code&gt; file to &lt;code&gt;dotnet&lt;/code&gt; CLI commands
&lt;/h2&gt;

&lt;p&gt;Even when you have one or more VS Code launch configuration to run a .NET project, you would likely still need to run commands on the project in its on the command line, e.g. to generate or apply EF Core migrations from the DbContext in an API project, or just to build run &lt;code&gt;dotnet build&lt;/code&gt; on the .NET project or solution to check that it builds. &lt;/p&gt;

&lt;p&gt;My solution for that is to use &lt;a href="https://direnv.net/" rel="noopener noreferrer"&gt;direnv&lt;/a&gt; to load the &lt;code&gt;.env&lt;/code&gt; file for the API from &lt;code&gt;.vscode&lt;/code&gt; folder, as environment variables specifically when you are in the .NET project's folder on the command line. Therefore when I run  any &lt;code&gt;dotnet&lt;/code&gt; commands in this folder, those environment variables are available as config key/value pairs for the project.&lt;/p&gt;

&lt;p&gt;You can set up direnv for this purpose like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href="https://direnv.net/#basic-installation" rel="noopener noreferrer"&gt;direnv&lt;/a&gt; using your operating system's package manager using the installation instructions &lt;a href="https://direnv.net/docs/installation.html" rel="noopener noreferrer"&gt;given here&lt;/a&gt;.&lt;br&gt;
&lt;strong&gt;On Windows&lt;/strong&gt; you can do this by &lt;a href="https://learn.microsoft.com/en-us/windows/package-manager/winget/" rel="noopener noreferrer"&gt;installing WinGet&lt;/a&gt; if you don't have it already. Then &lt;strong&gt;open a shell with Admininistrator permissions&lt;/strong&gt;, one way of doing which is to find PowerShell, then right click it to select &lt;strong&gt;Run as administrator&lt;/strong&gt;:   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dl7shtp0klgsyhi1hfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dl7shtp0klgsyhi1hfy.png" alt=" " width="800" height="731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then run command &lt;code&gt;winget install direnv&lt;/code&gt; in this shell.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hook direnv into your shell, &lt;a href="https://direnv.net/docs/hook.html" rel="noopener noreferrer"&gt;as described here&lt;/a&gt;. &lt;br&gt;
To do this for Bash on Windows, I did this by adding line &lt;code&gt;eval "$(direnv hook bash)"&lt;/code&gt; at the end of the file &lt;code&gt;~/.bashrc&lt;/code&gt; where &lt;code&gt;~&lt;/code&gt; is my user profile folder &lt;code&gt;C:\Users\&amp;lt;my windows login name&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file named &lt;code&gt;.envrc&lt;/code&gt; in the .NET project's folder with the following content:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotenv &amp;lt;relative path to folder containing &lt;span class="nb"&gt;env &lt;/span&gt;file&amp;gt;/&amp;lt;&lt;span class="nb"&gt;env &lt;/span&gt;file name&amp;gt;.env
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Normally you would have statements like &lt;code&gt;export MY_VAR=&amp;lt;value&amp;gt;&lt;/code&gt; in a &lt;code&gt;.envrc&lt;/code&gt; file to create environment variables. However, in this instance, the &lt;code&gt;dotenv&lt;/code&gt; command in the &lt;code&gt;.envrc&lt;/code&gt; file above is telling direnv to go and load the contents of the specified &lt;code&gt;.env&lt;/code&gt; file as environment variables.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the shell in your project's folder, run:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;direnv allow
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In general, an &lt;code&gt;.envrc&lt;/code&gt; file for direnv should not be checked in. This is because it can directly contain environment variables and their values, some of which may be secrets. Even though this is not the case with the &lt;code&gt;.envrc&lt;/code&gt; above, as a matter of good practice, I would add the following line to the &lt;code&gt;.gitignore&lt;/code&gt; file in workspace root:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.envrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Now whenever you &lt;code&gt;cd&lt;/code&gt; into your .NET project folder in your shell,&lt;/strong&gt; direnv would run automatically, run the &lt;code&gt;.envrc&lt;/code&gt; file in the folder, and load your &lt;code&gt;.env&lt;/code&gt; file as environment variables. &lt;/p&gt;

&lt;p&gt;This means that &lt;strong&gt;while you are in that folder (or a subfolder within it) in your shell&lt;/strong&gt;, you can run commands like &lt;code&gt;dotnet run&lt;/code&gt; or &lt;code&gt;dotnet ef migrations add&lt;/code&gt; or &lt;code&gt;dotnet ef database update&lt;/code&gt; which require these configuration settings to run.&lt;/p&gt;

&lt;p&gt;Also, when you move out of the folder (e.g. &lt;code&gt;cd ..&lt;/code&gt;) or close your terminal instance, the environment variables get unloaded by direnv.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;*.env.template&lt;/code&gt; files shown in the screenshot above are pre-filled versions of the respective &lt;code&gt;.env&lt;/code&gt; files with secrets redacted (much like the snippets shown above). I do check these in and, together with setup instructions in the project's wiki, they make it easy to recreate the configuration. The &lt;code&gt;*.env&lt;/code&gt; files on the other hand obviously DO NOT get checked in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you want to use this technique, please make sure to delete the &lt;code&gt;&amp;lt;UserSecretsId&amp;gt;a-guid-here&amp;lt;/UserSecretsId&amp;gt;&lt;/code&gt;&lt;/strong&gt; element that would be present in your &lt;code&gt;csproj&lt;/code&gt; if you have previously used .NET User Secrets Manager with the project. While &lt;code&gt;.env&lt;/code&gt; would be the highest-precedence source anyway, deleting &lt;code&gt;&amp;lt;UserSecretsId&amp;gt;&lt;/code&gt; would avoid surprises down the line such as when you realise that you had forgotten to set certain secrets in &lt;code&gt;.env&lt;/code&gt; and they had been coming from User Secret Manager all along.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;By the same token, it is worth deleting &lt;code&gt;appSettings.json&lt;/code&gt; and any other &lt;code&gt;appSettings.*.json&lt;/code&gt; files (such as &lt;code&gt;appSettings.Development.json&lt;/code&gt;) in the .NET project folder (&lt;strong&gt;BUT&lt;/strong&gt; make sure you have backed them up somewhere before deleting them).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I don't use &lt;code&gt;dotenv-net&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bolorundurowb/dotenv.net" rel="noopener noreferrer"&gt;&lt;code&gt;dotenv-net&lt;/code&gt;&lt;/a&gt; Nuget package can be used to load &lt;code&gt;.env&lt;/code&gt; files into environment variables in code. &lt;a href="https://medium.com/@vosarat1995/env-in-net-aa0a8dc4b68c" rel="noopener noreferrer"&gt;For example&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;dotenv.net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;DotEnv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;//then rest of Program.cs&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//...       &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DotEnv.Load()&lt;/code&gt; loads contents of &lt;code&gt;.env&lt;/code&gt; file present in the directory of the running process as environment variables (you can also provide an alternate path as argument).  &lt;/p&gt;

&lt;p&gt;Next, when &lt;code&gt;WebApplicaton.CreateBuilder(args)&lt;/code&gt; is called, the .NET configuration system loads config data from the default (or other configured) sources. This is when &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers#environment-variable-configuration-provider" rel="noopener noreferrer"&gt;environment variables configuration provider&lt;/a&gt; loads as configuration the environment variables created by &lt;code&gt;DotEnv.Load()&lt;/code&gt; earlier.&lt;/p&gt;

&lt;p&gt;You can also wrap DotEnv into a configuration provider &lt;a href="https://dev.to/rmaurodev/load-env-file-into-iconfiguration-provider-4fk0"&gt;as this post shows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem with this approach is essentially the mechanics&lt;/strong&gt;: in ecosystems that are mainly configured using environment variables, &lt;code&gt;.env&lt;/code&gt; files in local development are loaded by the launchers/orchestrators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When Docker Compose launches apps/services for local testing, it reads key/value pairs from a &lt;code&gt;.env&lt;/code&gt; file, then sets it as environment variables for every Docker container it launches. This is how your .NET app's container would receive its configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When VS Code launches processes in a launch configuration - e.g. a .NET minimal API and a Next.js frontend - you can specify a separate &lt;code&gt;.env&lt;/code&gt; file for each process. Thus each launch configuration can have its own set of &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the app is launched in a non-local environment, the host - Azure App Service, Kubernetes, ECS etc. - load configuration data from a configuration store and set it as environment variables before launching the process.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way the processes launched for debugging or testing locally are completely oblivious to the source of configuration data and who loads/provides it. &lt;strong&gt;They don't know and don't care:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which &lt;code&gt;.env&lt;/code&gt; files were used&lt;/li&gt;
&lt;li&gt;whether &lt;code&gt;.env&lt;/code&gt; files or some other configuration store (e.g. in Production) was used to retrieve configuration data&lt;/li&gt;
&lt;li&gt;who loaded the config data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;They just get the right set of environment variables to read at run time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This makes processes really easy to configure, hence the popularity of this aspect of the 12-factor approach. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardcoding sources of configuration such as specific files - whether &lt;code&gt;appsettings.*.json&lt;/code&gt; or &lt;code&gt;.env&lt;/code&gt; - adds unnecessary complexity in my view&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It also again adds an exception for .NET behaviour in development&lt;/strong&gt; - that the code is looking for specific files at startup if &lt;code&gt;app.Environment.IsDevelopment()&lt;/code&gt; - when every other type of project in the polyglot workspace is probably only looking for environment variables when it runs.&lt;/p&gt;

&lt;p&gt;All of this is not to say that there aren't situations where &lt;code&gt;dotenv-net&lt;/code&gt; is the right solution. It's just that in general I prefer not to use it, for reasons given.&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>aspnet</category>
      <category>dotnetcore</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Why I moved from AutoFixture to Bogus for test data generation in C#/xUnit tests</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Sat, 05 Jul 2025 16:03:39 +0000</pubDate>
      <link>https://forem.com/nausaf/why-i-moved-from-autofixture-to-bogus-for-test-data-generation-for-cxunit-test-49kg</link>
      <guid>https://forem.com/nausaf/why-i-moved-from-autofixture-to-bogus-for-test-data-generation-for-cxunit-test-49kg</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/AutoFixture/AutoFixture" rel="noopener noreferrer"&gt;AutoFixture&lt;/a&gt; and &lt;a href="https://github.com/bchavez/Bogus" rel="noopener noreferrer"&gt;Bogus&lt;/a&gt; are both well-known libraries for generating test data in C# tests. &lt;strong&gt;AutoFixture is dated, whereas Bogus is state-of-the-art&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I moved to &lt;a href="https://github.com/bchavez/Bogus" rel="noopener noreferrer"&gt;Bogus&lt;/a&gt; from &lt;a href="https://github.com/AutoFixture/AutoFixture" rel="noopener noreferrer"&gt;AutoFixture&lt;/a&gt; because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AutoFixture is dated and no longer under active development&lt;/strong&gt;. Releases are infrequent (last one was 8 months before the date of this writing). &lt;a href="https://github.com/AutoFixture/AutoFixture?tab=readme-ov-file#documentation" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt; was updated in 2021 and many of the links mentioned in it contain very old posts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AutoFixture is too basic&lt;/strong&gt;. I was quite surprised to discover that despite how long it's been around, there &lt;a href="https://autofixture.github.io/docs/quick-start/" rel="noopener noreferrer"&gt;seems to be no out of the box way&lt;/a&gt; of generating a number in a specified range. &lt;/p&gt;

&lt;p&gt;This makes it particularly difficult to use with &lt;code&gt;Price&lt;/code&gt; for example which is bounded by zero below and would typically have an upper limit also.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bogus is a .NET port of &lt;a href="https://fakerjs.dev/" rel="noopener noreferrer"&gt;Faker&lt;/a&gt;&lt;/strong&gt;, a JavaScript test data generation library, and it does not have the problems above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is &lt;strong&gt;actively developed&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;has &lt;strong&gt;brilliant documentation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;has a &lt;strong&gt;flexible and powerful API&lt;/strong&gt; which allows you to generate (semi-)meaningful test data within specified constraints really easily. Also, the code you write to do so would be a pleasure to read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given a &lt;code&gt;Product&lt;/code&gt; class that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ImageUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this is what a &lt;code&gt;Faker&lt;/code&gt; for the class looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductFaker&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProductFaker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Commerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProductName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lorem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PicsumUrl&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Finance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;ProductFaker&lt;/code&gt;, you can generate endless amounts of random, yet semi-meaningful &lt;code&gt;Product&lt;/code&gt; objects to use in your tests. &lt;/p&gt;

&lt;p&gt;And that &lt;strong&gt;&lt;code&gt;Image.PicsumUrl()&lt;/code&gt; call generates URLs to actual images on &lt;a href="https://picsum.photos" rel="noopener noreferrer"&gt;&lt;code&gt;https://picsum.photos&lt;/code&gt;&lt;/a&gt; that you can navigate to in your Playwright or Selenium tests!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What's really great is that &lt;strong&gt;GitHub Copilot generated (almost all of) the &lt;code&gt;ProductFaker&lt;/code&gt; for me,&lt;/strong&gt; as soon as I typed &lt;code&gt;: Faker&amp;lt;Product&amp;gt;&lt;/code&gt;. On the other hand when I was wrestling with AutoFixture, it was quiet. This may have something to do with how much Bogus-based (and possibly Faker-based; Bogus is a port of Faker.js) test code there is out there that LLMs have been trained on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nick Chapsas has an excellent video, &lt;a href="https://www.youtube.com/watch?v=T9pwE1GAr_U" rel="noopener noreferrer"&gt;Generating realistic fake data in .NET using Bogus&lt;/a&gt;, demonstrating the use of Bogus&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>dotnetcore</category>
      <category>testing</category>
    </item>
    <item>
      <title>Intercepting HTTP Traffic from the Console with Fiddler</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Tue, 01 Jul 2025 00:48:13 +0000</pubDate>
      <link>https://forem.com/nausaf/intercepting-terminal-traffic-with-fiddler-5ci4</link>
      <guid>https://forem.com/nausaf/intercepting-terminal-traffic-with-fiddler-5ci4</guid>
      <description>&lt;p&gt;&lt;a href="https://www.telerik.com/fiddler/fiddler-classic" rel="noopener noreferrer"&gt;Fiddler&lt;/a&gt; doesn't work out of the box with HTTP traffic originating from commands run in the terminal (like &lt;code&gt;curl&lt;/code&gt;). Luckily, it can be configured to do so.&lt;/p&gt;

&lt;p&gt;The configuration given below would allow you to both intercept such traffic and decrypt SSL requests and responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Fiddler and your terminal
&lt;/h2&gt;

&lt;p&gt;I tested these steps on Fiddler Classic, which is quite old (but free!) but they should work on the newer, flashier &lt;a href="https://www.telerik.com/fiddler/fiddler-everywhere" rel="noopener noreferrer"&gt;Fiddler Everywhere&lt;/a&gt; also:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;Tools | Options&lt;/strong&gt; menu to bring up the &lt;strong&gt;Options&lt;/strong&gt; dialog. Go to &lt;strong&gt;Connections&lt;/strong&gt; tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49nkijnytlp78zhx1p7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49nkijnytlp78zhx1p7f.png" alt=" " width="706" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure the two checkboxes highlighted in red are checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Take note of the port on which Fiddler listens&lt;/strong&gt; (&lt;code&gt;8888&lt;/code&gt; in my case).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to your terminal and run the following commands (I am using &lt;code&gt;export&lt;/code&gt; keyword for Bash, you might need to use &lt;code&gt;set&lt;/code&gt; for Windows shells):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;http_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1:8888
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;https_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1:8888
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;This needs to be done every time you open a terminal from which you want HTTP traffic to be intercepted&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run a command on the terminal that executes an HTTP request e.g.:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--ssl-no-revoke&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: text/plain"&lt;/span&gt; https://icanhazdadjoke.com/ 
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;With curl you have to use the &lt;code&gt;--ssl-no-revoke&lt;/code&gt; option, for reasons explained shortly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You should now see traffic to the site accessed by curl, but it would be encrypted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4xt9439agz4l7d2hz49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4xt9439agz4l7d2hz49.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To descrypt SSL traffic, you need can click the yellow button shown in the snapshot above. This would bring up &lt;strong&gt;Tools | Options&lt;/strong&gt; dialog again, this time at the &lt;strong&gt;HTTPS&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure it as follows&lt;/strong&gt; (you can play around with the settings):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0dm0psrlbs1g58h5rtn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0dm0psrlbs1g58h5rtn.png" alt=" " width="706" height="458"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try out the setup
&lt;/h2&gt;

&lt;p&gt;Try the curl command given above again. You should now be able to see the decrypted SSL traffic of further requests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ve4twa5s4gzo4gp83p5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ve4twa5s4gzo4gp83p5.png" alt=" " width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we need to use &lt;code&gt;--ssl-no-revoke&lt;/code&gt; with cURL
&lt;/h2&gt;

&lt;p&gt;Fiddler is a man-in-the middle. This is what allows it to sniff HTTP traffic and show it to us. To decrypt SSL/TLS traffic Fiddler, issues and uses its own SSL certificate to interface with the client (e.g. the curl command in the example above). &lt;/p&gt;

&lt;p&gt;This works fine with many terminal commands that make HTTP calls, for example &lt;code&gt;terraform apply&lt;/code&gt; which makes API calls. However, curl throws an error, which I believe is because it actually checks if the certificate that Fiddler is using to establish the TLS tunnel is legit (which it is not, in the sense that it has not been issued by a well-known certificate authority such as VeriSign). &lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;--ssl-no-revoke&lt;/code&gt; command line parameter with curl gets around the error.&lt;/p&gt;

</description>
      <category>fiddler</category>
      <category>webdev</category>
      <category>restapi</category>
    </item>
    <item>
      <title>An attempt to return meaningful Problem Details responses for model binding errors in an ASP.NET Core Minimal API</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Tue, 19 Nov 2024 18:04:55 +0000</pubDate>
      <link>https://forem.com/nausaf/aborted-attempt-to-return-meaningful-problem-details-response-from-model-binding-errors-in-an-353g</link>
      <guid>https://forem.com/nausaf/aborted-attempt-to-return-meaningful-problem-details-response-from-model-binding-errors-in-an-353g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Model Binding is the process of binding parameters of the route handler to values provided in the request (to route segments, query string parameters, cookies, request headers or request body) or to services in the DI container.&lt;/p&gt;

&lt;p&gt;The automatically added &lt;code&gt;EndpointMiddleware&lt;/code&gt;, which is the last middleware in the request pipeline, performs model binding, then invokes the handler for the requested route with the parameter values extracted during model binding.&lt;/p&gt;

&lt;p&gt;If an error occurs during model binding, then in &lt;code&gt;Production&lt;/code&gt; environment, &lt;code&gt;EndpointMiddleware&lt;/code&gt; returns a 400 or a 500 response with an empty response body whereas in &lt;code&gt;Development&lt;/code&gt; environment it throws a &lt;code&gt;BadHttpRequestException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I like to return error conditions from my minimal API handlers as responses that are compliant with the &lt;a href="https://www.rfc-editor.org/rfc/rfc9457.html#name-detail" rel="noopener noreferrer"&gt;IETF Problem Details&lt;/a&gt; specification. I do this because it is a well-crafted yet really lightweight standard for returning errors from REST APIs. It is also mature (now in its second iteration) and ASP.NET Core has &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling-api?view=aspnetcore-9.0&amp;amp;tabs=minimal-apis#problem-details" rel="noopener noreferrer"&gt;good support for it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wanted to do the same with model-binding errors also: &lt;strong&gt;to return helpful and detailed ProblemDetails responses that would help the client fix an error that had occurred during model binding&lt;/strong&gt;, instead of the &lt;code&gt;EndpointMiddleware&lt;/code&gt; default of a 400 response with empty body in &lt;code&gt;Production&lt;/code&gt; environment.&lt;/p&gt;

&lt;p&gt;In order to do this I needed to understand exactly how &lt;code&gt;EndpointMiddleware&lt;/code&gt; returns an error that it encountered during model binding.&lt;/p&gt;

&lt;h2&gt;
  
  
  How EndpointMiddleware returns Model Binding Errors
&lt;/h2&gt;

&lt;p&gt;I have verified the following behaviour:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If a model binding error occurred but NOT due to an issue with contents of the HTTP request&lt;/strong&gt;, then the &lt;code&gt;EndpointMiddleware&lt;/code&gt; would return a 500 status code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This would happen in both &lt;code&gt;Production&lt;/code&gt; and &lt;code&gt;Development&lt;/code&gt; environments&lt;/strong&gt; i.e. regardless of whether &lt;code&gt;app.Environment.IsProduction()&lt;/code&gt; or &lt;code&gt;app.Environment.IsDevelopment()&lt;/code&gt; is true in &lt;code&gt;Program.cs&lt;/code&gt; at runtime.&lt;/p&gt;

&lt;p&gt;An example is when a service that was meant to be resolved from the DI container and passed in as an argument to the handler could not be resolved. No exception would be throw, only &lt;code&gt;500&lt;/code&gt; would be set as the status code of &lt;code&gt;HttpContext.Request&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the other hand, if the error occurred due to an issue with contents of the request,&lt;/strong&gt; for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a required field or value is missing in the request&lt;/li&gt;
&lt;li&gt;a request parameter has a value that is invalid/malformed or of an incorrect data type&lt;/li&gt;
&lt;li&gt;there is an issue with JSON request body (e.g. it is malformed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;then the response from &lt;code&gt;EndpointMiddleware&lt;/code&gt; varies by environment:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In &lt;code&gt;Development&lt;/code&gt; environment (and possibly in any non-&lt;code&gt;Production&lt;/code&gt; environment), a &lt;code&gt;BadHttpRequestException&lt;/code&gt; is thrown by the middleware. &lt;br&gt;
This has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StatusCode&lt;/code&gt; property set to &lt;code&gt;400&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a &lt;code&gt;Message&lt;/code&gt; property that is informative such as:  &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Failed to read parameter "CreateProductArgs createProductArgs" from the request body as JSON.&lt;/code&gt;  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;InnerException&lt;/code&gt; property &lt;strong&gt;which, if there was problem deserializing the JSON request body, would be&lt;/strong&gt; &lt;code&gt;System.Text.Json.JsonException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Again, this has an informative &lt;code&gt;Message&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JSON deserialization for type 
'problemdetailstestapi.CreateProductArgs' was 
missing required properties, 
including the following: price
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;But in &lt;code&gt;Production&lt;/code&gt; Environment, the &lt;code&gt;EndpointMiddleware&lt;/code&gt; sets status code &lt;code&gt;400&lt;/code&gt; in the outgoing response with a &lt;strong&gt;blank response body&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However we can enable throwing of &lt;code&gt;BadHttpRequestException&lt;/code&gt; in &lt;code&gt;Production&lt;/code&gt; environment by adding the following line in &lt;code&gt;Program.cs&lt;/code&gt; before &lt;a href="http://builder.Build" rel="noopener noreferrer"&gt;&lt;code&gt;builder.Build&lt;/code&gt;&lt;/a&gt;&lt;code&gt;()&lt;/code&gt; is called (from &lt;a href="https://github.com/dotnet/aspnetcore/issues/48355#issuecomment-1557650377" rel="noopener noreferrer"&gt;this issue&lt;/a&gt; in dotnet/aspnetcpore repo) to get our hands on the information that this exception would contain in &lt;code&gt;Development&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RouteHandlerOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThrowOnBadRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Now, even in &lt;code&gt;Production&lt;/code&gt;, if a model binding error occurred due to an issue with contents of the request, then a &lt;code&gt;BadHttpRequestException&lt;/code&gt; would be thrown by &lt;code&gt;EndpointMiddleware&lt;/code&gt;. This is useful as the exception can be quite informative.    &lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Clearly, the messages above in &lt;code&gt;BadHttpRequestException&lt;/code&gt; are useful but cannot be sent back to the client verbatim as doing so would reveal internal execution and implementation details.&lt;/p&gt;

&lt;p&gt;I also do not want to parse them to extract information as for the same exception, the structure of the error message can be different in different sitautions. Also, exception messages may change in the future.&lt;/p&gt;

&lt;p&gt;However, the messages are very useful for logging, and that alone is a good reason to turn on throwing of &lt;code&gt;BadHttpRequestException&lt;/code&gt; in &lt;code&gt;Production&lt;/code&gt; environment (i.e. to catch and log the exception).&lt;/p&gt;

&lt;p&gt;I have verified that an error does not get logged at level &lt;code&gt;Information&lt;/code&gt; or above (using .NET logging) from within &lt;code&gt;EndpointMiddleware&lt;/code&gt; if there is a model binding exception when binding the request. Now, even if it does log at level &lt;code&gt;Debug&lt;/code&gt; or below, I don't want to turn on such verbose logging in &lt;code&gt;Production&lt;/code&gt; for &lt;em&gt;any&lt;/em&gt; part of the request pipeline on an permanent basis.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;we need to log this exception ourselves&lt;/strong&gt;. This can be done in one of at least two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;via request logging at the reverse proxy (e.g. in a Web App in Azure App Service) to log all requests that resulted in a 400 response being returned. 
Here, bear mind that even though the &lt;code&gt;EndpointMiddleware&lt;/code&gt; would throw a &lt;code&gt;BadHttpRequestException&lt;/code&gt; up the the middleware chain, what gets returned to the client - what exits the pipeline as response - is a plain 400 with empty body. &lt;/li&gt;
&lt;li&gt;by writing a middleware that will catch and log the &lt;code&gt;BadHttpRequestException&lt;/code&gt; exception thrown by &lt;code&gt;EndpointMiddleware&lt;/code&gt; once throwing of this exception has been turned on in &lt;code&gt;Production&lt;/code&gt; environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Returning model binding errors as Problem Details responses
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Based on the above behaviour,&lt;/strong&gt; I thought I could create and return informative Problem Details responses in the event of a model binding error by creating a middleware that would also log these exceptions using .NET logging, as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Enable throwing &lt;code&gt;HttpBadRequestException&lt;/code&gt; in &lt;code&gt;Production&lt;/code&gt; environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a middleware just before &lt;code&gt;RoutingMiddleware&lt;/code&gt;. This would catch catch a &lt;code&gt;BadHttpRequestException&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If a &lt;code&gt;BadHttpRequestException&lt;/code&gt; exception is caught in our middleware which was thrown by the &lt;code&gt;EndpointMiddleware&lt;/code&gt;&lt;/strong&gt; rather than by an endpoint filter or by the invoked router handler then &lt;strong&gt;we have one of these two error situations&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
A. If &lt;code&gt;InnerException&lt;/code&gt; is &lt;code&gt;System.Text.Json.JsonException&lt;/code&gt; then the request body is invalid in the specific sense that either a provided value is of an incorrect type, or a required value is missing. Report this with a ProblemDetails response.&lt;br&gt;&lt;br&gt;
B. Otherwise the issue is with some other part of the request. Examples include the following: the JSON body is missing altogether, a required route segment is missing or has an invalid value. Report this with a ProblemDetails response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If a &lt;code&gt;BadHttpRequestException&lt;/code&gt; was NOT caught in the middleware that was thrown by the EndpointMiddleware,&lt;/strong&gt; i.e. no exception was caught, or an exception was caught but it was not &lt;code&gt;BadHtpRequestException&lt;/code&gt; or a &lt;code&gt;BadHttpRequestException&lt;/code&gt; was caught but it hadn’t been thrown by the EndpointMiddleware (i.e. had ben thrown by an endpoint filter or from somewhere in the invoked route handler), then &lt;strong&gt;we do not have the information to create a meaningful ProblemDetails response&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So we just let it - the response or the exception - propagate up the request pipeline. Eventually it would be caught and, in &lt;code&gt;Production&lt;/code&gt; environment, returned as a 400 response with blank body.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;I did create the middleware to do the above (given at the end of this post) but the results were underwhelming.&lt;/p&gt;

&lt;p&gt;It is at points 3A and 3B above that we detect model binding errors and where we could formulate and return meaningful Problem Details. &lt;/p&gt;

&lt;p&gt;However at these points it is not possible to reliably parse the &lt;code&gt;BadHttpRequestException&lt;/code&gt; thrown by &lt;code&gt;EndpointMiddleware&lt;/code&gt; to extract details about where in the request the model binding errors occurred or why. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The issue is that the &lt;code&gt;BadHttpRequestException&lt;/code&gt; thrown by &lt;code&gt;EndpointMiddleware&lt;/code&gt; on model binding errors is not very machine readable&lt;/strong&gt;. Therefore I could not formulate and return meaningful Problem Details responses that could pinpoint the error and provide detail to help the client fix it. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instead, all I can return are two very broad errors&lt;/strong&gt;, one at 3A and another at 3B (shown in middleware code below). These cover, between them, pretty much everything that could go wrong with a request (headers, route segments, query string, request body, cookies). This is to say that the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400" rel="noopener noreferrer"&gt;semantics of a 400 (Bad Request) status code&lt;/a&gt; are almost the same as these two errors (returned as Problem Details responses) taken together. &lt;/p&gt;

&lt;p&gt;This begs the question: &lt;strong&gt;Is there any value in returning two very generic Problem Details responses, over returning a plain 400 with an empty response body?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem Details specification offers an answer. To quote my &lt;a href="https://hyp.is/H1kXXqWyEe-F3K_AOmdBVQ/www.rfc-editor.org/rfc/rfc9457" rel="noopener noreferrer"&gt;favourite part of the spec&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;truly generic problems -- i.e., conditions that might apply to any resource on the Web -- are usually better expressed as plain status codes. For example, a "write access disallowed" problem is probably unnecessary, since a 403 Forbidden status code in response to a PUT request is self-explanatory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I don’t see what value distinguishing between these two broad errors would add over just sending back a plain 400 with an empty response body&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;And in &lt;code&gt;Production&lt;/code&gt; environment, a plain 400 with an empty response body is exactly what gets returned on a model binding error. &lt;strong&gt;So there's no need to do anything or change anything&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In particular, &lt;strong&gt;I will not implement the solution above.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In order to get more specific errors out of the opaque model-binding process, I would need to basically reverse-engineer (at least partially) &lt;code&gt;EndpointMiddleware&lt;/code&gt;'s model-binding logic&lt;/strong&gt; in the custom middleware. &lt;/p&gt;

&lt;p&gt;I am not particularly inclined to do this either because I cannot see how the value of this to the consumers of my current API project exceeds the investment in reverse-engineering the model-binding logic, and keeping it in sync with future ASP.NET Core releases.&lt;/p&gt;

&lt;p&gt;Isn't the whole point of model-binding in ASP.NET Core that it is free compared to something like Express/Node.js?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However I would always want to have logging of model-binding errors so I could inspect them later&lt;/strong&gt;. This might lead me to precise model-binding errors that are worth reporting from a middleware as ProblemDetails responses in the future. &lt;/p&gt;

&lt;p&gt;To get this logging there are two broad options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;turn on request logging in the reverse proxy to log all requests that led to 400 responses with empty bodies (these would include those that were returned by the &lt;code&gt;EndpointMiddleware&lt;/code&gt; on model binding errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;My Preferred Option:&lt;/strong&gt; Use a custom middleware to log &lt;code&gt;BadHttpRequestException&lt;/code&gt; thrown from &lt;code&gt;EndpointMiddleware&lt;/code&gt;. &lt;br&gt;
You can adapt the middleware shown below to do this and place it - by calling the &lt;code&gt;UseXXX&lt;/code&gt; method to register it - as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you do not have &lt;code&gt;app.UseRouting()&lt;/code&gt; or &lt;code&gt;app.UseEndpoints()&lt;/code&gt; in your &lt;code&gt;Program.cs&lt;/code&gt; then place it at the very end of middleware registrations, before the first &lt;code&gt;app.MapXXX&lt;/code&gt; call. This is because in minimal API, since at least .NET 9+, &lt;code&gt;app.Run()&lt;/code&gt; automatically add the Routing and Endpoints middlewares at the end if they haven't been registered explicitly.&lt;/li&gt;
&lt;li&gt;If you do have either &lt;code&gt;app.UseRouting&lt;/code&gt; or &lt;code&gt;app.UseEndpoints()&lt;/code&gt; in your &lt;code&gt;Program.cs&lt;/code&gt; then add the middleware just before both of those.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;The middleware was as follows. &lt;/p&gt;

&lt;p&gt;It doesn't include the check for the stack trace to see if the &lt;code&gt;BadHttpRequestException&lt;/code&gt; was thrown by the &lt;code&gt;EndpointMiddleware&lt;/code&gt; or not; I give a sketch of how to do this further down.&lt;/p&gt;

&lt;p&gt;You can try it out in your own ASP.NET Core minimal API app by calling &lt;code&gt;ProblemDetailsForBadRequestMiddlewareExtensions.UseProblemDetailsForBadRequest&lt;/code&gt; in your &lt;code&gt;Program.cs&lt;/code&gt;. I did this by adding line &lt;code&gt;app.UseProblemDetailsForBadRequest()&lt;/code&gt; after line &lt;code&gt;var app = builder.Build();&lt;/code&gt; in the boilderplate code but before &lt;code&gt;app.UseRouting();&lt;/code&gt;. If you do not have &lt;code&gt;app.UseRouting();&lt;/code&gt; of &lt;code&gt;app.UseEndpoints();&lt;/code&gt; (these are not necessary in .NET minimal APIs any more) then simply add it as the last middleware before the first &lt;code&gt;app.MapXXX()&lt;/code&gt; method call in your &lt;code&gt;Program.cs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The process of creating and using a custom middleware is described &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-9.0" rel="noopener noreferrer"&gt;here&lt;/a&gt; in ASP.NET Core documentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;problemdetailsmiddleware.Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProblemDetailsForBadRequestMiddleware&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;RequestDelegate&lt;/span&gt; &lt;span class="n"&gt;_next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProblemDetailsForBadRequestMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProblemDetailsForBadRequestMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RequestDelegate&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProblemDetailsForBadRequestMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_next&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BadHttpRequestException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StackTrace&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"BadRequestException occurred while processing HTTP request"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerException&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;JsonException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;//this would only happen if the &lt;/span&gt;
                &lt;span class="c1"&gt;// var validationProblem = TypedResults.ValidationProblem(new Dictionary&amp;lt;string, string[]&amp;gt; {&lt;/span&gt;
                &lt;span class="c1"&gt;//     {"request body json", new string[] {@"An error occurred when parsing the provided request body. One of three things is likely to be wrong:&lt;/span&gt;

                &lt;span class="c1"&gt;//     1. The provided request body is not well-formed JSON&lt;/span&gt;
                &lt;span class="c1"&gt;//     2. A required key is missing in the request body JSON&lt;/span&gt;
                &lt;span class="c1"&gt;//     3. A value of an incorrect type is provided for a key in the request body JSON"}}&lt;/span&gt;
                &lt;span class="c1"&gt;// });&lt;/span&gt;

                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TypedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Problem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status400BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"http://example.com/problems/invalid-request-body-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"\"An error occurred while parsing request body JSON\","&lt;/span&gt;
                    &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Request body was provided but an error occurred when parsing it. One of three things is likely to be wrong: 1. The provided request body is not well-formed JSON 2. A required property is missing in the request body JSON 3. A value of an incorrect or incompatible type was provided for a property in the request body JSON"&lt;/span&gt;
                    &lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TypedResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Problem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status400BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"http://example.com/problems/missing-body-or-invalid-request-parameter-values"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"\"Request body is missing or a request parameter value is missing or invalid\","&lt;/span&gt;
                    &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Either the request body is required but missing, or the value of a request parameter - in request headers, query string, route segments or cookies - is either missing ( in case of a required parameter) or invalid (e.g. of an incorrect type). Check your request against the OpenAPI description of the operation."&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProblemDetailsForBadRequestMiddlewareExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="nf"&gt;UseProblemDetailsForBadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProblemDetailsForBadRequestMiddleware&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Distinguishing between whether the &lt;code&gt;BadHtpRequestException&lt;/code&gt; was thrown from EndpointMiddleware of from further down the request processing pipeline:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If every frame in the stack (each is a new line) begins with &lt;code&gt;at Microsoft.AspNetCore.Http.RequestDelegateFactory&lt;/code&gt; until potentially a &lt;code&gt;--- End of stack trace from previous location ---&lt;/code&gt; line is ensountered, then the exception was thrown from EndpointMiddleware (I believe &lt;code&gt;RequestDelegateFactory&lt;/code&gt; create a &lt;code&gt;RequestDelegate&lt;/code&gt; out of every middleware in the pipeline that is to be invoked). For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   at Microsoft.AspNetCore.Http.RequestDelegateFactory.Log.InvalidJsonRequestBody(HttpContext httpContext, String parameterTypeName, String parameterName, Exception exception, Boolean shouldThrow)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.&amp;lt;HandleRequestBodyAndCompileRequestDelegateForJson&amp;gt;g__TryReadBodyAsync|102_0(HttpContext httpContext, Type bodyType, String parameterTypeName, String parameterName, Boolean allowEmptyRequestBody, Boolean throwOnBadRequest, JsonTypeInfo jsonTypeInfo)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.&amp;lt;&amp;gt;c__DisplayClass102_2.&amp;lt;&amp;lt;HandleRequestBodyAndCompileRequestDelegateForJson&amp;gt;b__2&amp;gt;d.MoveNext()
--- End of stack trace from previous location ---
   at problemdetailsmiddleware.Middleware.ProblemDetailsForBadRequestMiddleware.InvokeAsync(HttpContext context) in C:\MyWork\problemdetails\problemdetailsmiddleware\Middleware\ProblemDetailsForBadRequestMiddleware.cs:line 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are other frames, theses would come from an endpoint filter or from somewhere in the invoked route handler. In this case, not all frames, until the line &lt;code&gt;--- End of stack trace from previous location ---&lt;/code&gt; is encountered, would start with &lt;code&gt;at Microsoft.AspNetCore.Http.RequestDelegateFactory&lt;/code&gt;, as in this example from a Release build of a minimal API (for some reason the Release build also contained a &lt;code&gt;.pdb&lt;/code&gt;, hence the topmost frame even tells youthat the error occurred at line 80):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   at Program.&amp;lt;&amp;gt;c.&amp;lt;&amp;lt;Main&amp;gt;$&amp;gt;b__0_3(IList`1 products, LinkGenerator linkGen, CreateProductArgs createProductArgs) in C:\MyWork\problemdetails\problemdetailsmiddleware\Program.cs:line 80
   at lambda_method4(Closure, EndpointFilterInvocationContext)
   at FluentValidation.AspNetCore.Http.FluentValidationEndpointFilter.InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.&amp;lt;ExecuteValueTaskOfObject&amp;gt;g__ExecuteAwaited|129_0(ValueTask`1 valueTask, HttpContext httpContext, JsonTypeInfo`1 jsonTypeInfo)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.&amp;lt;&amp;gt;c__DisplayClass102_2.&amp;lt;&amp;lt;HandleRequestBodyAndCompileRequestDelegateForJson&amp;gt;b__2&amp;gt;d.MoveNext()
--- End of stack trace from previous location ---
   at problemdetailsmiddleware.Middleware.ProblemDetailsForBadRequestMiddleware.InvokeAsync(HttpContext context) in C:\MyWork\problemdetails\problemdetailsmiddleware\Middleware\ProblemDetailsForBadRequestMiddleware.cs:line 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>dotnet</category>
      <category>api</category>
      <category>problemdetails</category>
      <category>minimalapi</category>
    </item>
    <item>
      <title>Creating an NPM package that runs on command line</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Wed, 02 Oct 2024 16:59:10 +0000</pubDate>
      <link>https://forem.com/nausaf/creating-an-npm-package-that-runs-on-command-line-with-npx-9a0</link>
      <guid>https://forem.com/nausaf/creating-an-npm-package-that-runs-on-command-line-with-npx-9a0</guid>
      <description>&lt;p&gt;Every now and then I need to create an NPM package that runs on the command line, as a CLI (Command Line Interace). &lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;npx eslint&lt;/code&gt; runs and lints code in a directory and can accept command line arguments also. Similarly &lt;code&gt;npx create-next-app --eslint --tailwind&lt;/code&gt; would scaffold a Next.js app that has eslint and tailwind configured. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There are three things you need to do to turn an NPM package into one that can be run from the command line:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a file that contains the code that would run when the package is run using &lt;code&gt;npx&lt;/code&gt; from the command line, e.g. &lt;code&gt;cli.js&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In package.json, place a &lt;code&gt;"bin"&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"bin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./cli.js"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;code&gt;"bin": "./cli.js"&lt;/code&gt; tells &lt;code&gt;npx&lt;/code&gt; that when the package is run from the command line using &lt;code&gt;npx &amp;lt;package name&amp;gt;&lt;/code&gt;, then &lt;code&gt;./cli.js&lt;/code&gt; should be run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ASIDE:&lt;/strong&gt; &lt;code&gt;"type": "module"&lt;/code&gt; tells NPM utilities that the type of modules which would be imported in the code files in this package would be ES6 (using &lt;code&gt;import&lt;/code&gt; statement). Otherwise you would only be able to import Common JS modules (using &lt;code&gt;require&lt;/code&gt;) which, this being the tail-end of 2024, you probably don't want to do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On top of the file declared in &lt;code&gt;"bin"&lt;/code&gt; in package.json, place the line &lt;code&gt;#!/usr/bin/env node&lt;/code&gt;. This allows the &lt;code&gt;cli.js&lt;/code&gt; to run using the Node.js executable when it is launched by &lt;code&gt;npx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, my &lt;code&gt;cli.js&lt;/code&gt; in package root would look like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!/usr/bin/env node
&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRequire&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRequire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;packageJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./package.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Version number of the package is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;packageJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If I publish the package to NPM by running &lt;code&gt;npm publish&lt;/code&gt; on the terminal in the project folder, then go to a completely different folder on the terminal and execute &lt;code&gt;npx show-version-number&lt;/code&gt; (where &lt;code&gt;show-version-number&lt;/code&gt; is the name of the package in &lt;code&gt;package.json&lt;/code&gt; and therefore in NPM registry), it would still run:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwh87kjzrb3x2zv6lwcur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwh87kjzrb3x2zv6lwcur.png" alt=" " width="478" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I checked that &lt;code&gt;npx&lt;/code&gt; downloaded and stored the package in &lt;code&gt;C:\Users\{My User Name}\AppData\Local\npm-cache\_npx\&lt;/code&gt; on my Windows machine.&lt;/p&gt;

&lt;p&gt;Code is in &lt;a href="https://github.com/naveedausaf/print-version-number" rel="noopener noreferrer"&gt;this GitHub repo&lt;/a&gt;. If you want to publish it to NPM, please change &lt;code&gt;"name"&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; to a different value as I have already published a package with the name &lt;code&gt;"show-version-number"&lt;/code&gt;. I use &lt;a href="https://remarkablemark.org/npm-package-name-checker/" rel="noopener noreferrer"&gt;this tool&lt;/a&gt; to check if a package name is available in NPM registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can also create named commands&lt;/strong&gt; by creating named values in &lt;code&gt;"bin"&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"bin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"showver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./cli.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you publish the package to NPM, and run it as &lt;code&gt;npx show-version-number&lt;/code&gt; on the command line, &lt;code&gt;cli.js&lt;/code&gt; would still run as before. I believe &lt;code&gt;npx &amp;lt;package name&amp;gt;&lt;/code&gt; picks up the first entry in &lt;code&gt;"bin"&lt;/code&gt; (in this case the key &lt;code&gt;"showver"&lt;/code&gt;) and runs the code file defined there (in this case &lt;code&gt;./cli.js&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;However, you can also install the package on your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; show-version-number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;npm install --global&lt;/code&gt; command would register a command named &lt;code&gt;showversion&lt;/code&gt; with the operating system (as a cmd file on Windows and as a symlink on Unix-based system such as Linux, as &lt;a href="https://docs.npmjs.com/cli/v10/configuring-npm/package-json#bin" rel="noopener noreferrer"&gt;described here&lt;/a&gt;) that aps to &lt;code&gt;cli.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Now, you can say the following on the terminal in any folder on your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;showver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would run &lt;code&gt;cli.js&lt;/code&gt; with Node executable and you would have the same output as when you ran &lt;code&gt;npx show-version-number&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course, &lt;strong&gt;a single package may provide multiple named commands&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;ASIDE:&lt;/strong&gt; I had to import &lt;code&gt;package.json&lt;/code&gt; using the following lines in &lt;code&gt;cli.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRequire&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRequire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;packageJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./package.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of &lt;code&gt;import packageJson from "package.json"&lt;/code&gt; because in my version of Node (v20+), this latter import throws a &lt;code&gt;ERR_IMPORT_ASSERTION_TYPE_MISSING&lt;/code&gt; error when I try to run the package using &lt;code&gt;npx .&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;To fix the error I had to either rewrite the import as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import packageJson from "./package.json" with { type: "json" };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or using the &lt;code&gt;assert&lt;/code&gt; keyword as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import packageJson from "./package.json" assert { type: "json" };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In either case, I got the following warning when I ran the code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1jfpfd011goo08pwwby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1jfpfd011goo08pwwby.png" alt=" " width="651" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, the three lines I use to import &lt;code&gt;package.json&lt;/code&gt; instead, clunky as they are, get rid of the warning. See &lt;a href="https://stackoverflow.com/questions/70106880/err-import-assertion-type-missing-for-import-of-json-file" rel="noopener noreferrer"&gt;this StackOverflow thread&lt;/a&gt; for more details.&lt;/p&gt;

</description>
      <category>npm</category>
      <category>node</category>
      <category>cli</category>
    </item>
    <item>
      <title>Environments in GitHub (with example of deploying a Next.js app to Vercel)</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Mon, 30 Sep 2024 00:09:32 +0000</pubDate>
      <link>https://forem.com/nausaf/environments-in-github-with-example-of-nextjs-deployment-to-vercel-3hmm</link>
      <guid>https://forem.com/nausaf/environments-in-github-with-example-of-nextjs-deployment-to-vercel-3hmm</guid>
      <description>&lt;p&gt;An Environment in GitHub is basically a set of three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secrets&lt;/li&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;li&gt;Protection rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you &lt;a href="https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment#next-steps" rel="noopener noreferrer"&gt;define an environment&lt;/a&gt;, you provide a name and can configure any of the above.&lt;/p&gt;

&lt;p&gt;For example, when defining an environment named &lt;strong&gt;UAT&lt;/strong&gt; (in &lt;strong&gt;Environments&lt;/strong&gt; tab in repo &lt;strong&gt;Settings&lt;/strong&gt;), the environment definition page would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkelq7b1d736t7yokqp6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkelq7b1d736t7yokqp6n.png" alt=" " width="734" height="898"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What is interesting is that you can reference an environment in a job in a GitHub Actions workflow using an &lt;a href="https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment" rel="noopener noreferrer"&gt;environment block&lt;/a&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-to-vercel-pr-preview-env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-24.04&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy-artifacts.outputs.previewUrl }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to the fact that such a job is allowed to access Variables and Secrets defined within the environment (as opposed to at the repo level) and Protection Rules such as delayed execution, manual approval, and deployment only from branches meeting specified criteria (e.g. with matching names and/or with branch protection rules) apply, &lt;strong&gt;when a job references an &lt;code&gt;environment&lt;/code&gt;, this enables a number of other interesting behaviours&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There is a really nice sticky comment on the PR (so it updates with every push to the source branch if that triggers the CI workflow) which shows the value of the &lt;code&gt;url&lt;/code&gt; property for the &lt;code&gt;environment&lt;/code&gt; once the job has completed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadoxc7757lfy70vw29cr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadoxc7757lfy70vw29cr.png" alt=" " width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can use check &lt;strong&gt;&lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-deployments-to-succeed-before-merging" rel="noopener noreferrer"&gt;Require deployments to succeed before merging&lt;/a&gt;&lt;/strong&gt; in a branch protection rule on the base branch (say &lt;code&gt;main&lt;/code&gt;). This would ensure that any head branch (source branch) in a PR has successfully deployed to the the specified environment. &lt;/p&gt;

&lt;p&gt;Deployment of the source branch to an environment is indicated by there being a job in a workflow that runs on the source branch which references the specified and provides a deployment URL. An example can be seen in the snippet above in which job &lt;code&gt;deploy-to-vercel-pr-preview-env&lt;/code&gt; has an &lt;code&gt;envionment&lt;/code&gt; object that specifies &lt;code&gt;name: Preview&lt;/code&gt; and also sets the value of the &lt;code&gt;url&lt;/code&gt; property which would be the URL to which the job deployed.&lt;/p&gt;

&lt;p&gt;I am not sure of the value of this check in simple branching workflows as you would already have a check in branch protections rules on &lt;code&gt;main&lt;/code&gt; (or other base branch) to say that a certain job ran successfully. One such job would be the one that deploys to a UAT or other pre-release environment successfully (e.g. &lt;code&gt;deploy-to-vercel-pr-preview-env&lt;/code&gt; in the snippet above).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;url&lt;/code&gt; property of &lt;code&gt;environment&lt;/code&gt; can be static but it can also reference a &lt;a href="https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-output-parameter" rel="noopener noreferrer"&gt;step output parameter&lt;/a&gt; of the same job. In the latter case, the &lt;code&gt;url&lt;/code&gt; property of the &lt;code&gt;environment&lt;/code&gt; block in the job will be evaluated after the step which outputs that parameter value has executed. &lt;/p&gt;

&lt;p&gt;In the job snippet shown above, the step that produces the referenced step output parameter is:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-artifacts&lt;/span&gt;
    &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;previewUrl=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})&lt;/span&gt;
      &lt;span class="s"&gt;echo "previewUrl=$previewUrl" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Once the job has completed (or perhaps when the step above has completed), value of step output parameter &lt;code&gt;previewUrl&lt;/code&gt; would be available. This would be assigned to &lt;code&gt;url&lt;/code&gt; proeprty of the &lt;code&gt;environment&lt;/code&gt;. This is what gets displayed on the sticky comment for the environment as the deployment URL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If a job referencing an environment runs on multiple source branches (in multiple PRs), they don't seem to wait for each other i.e. they seem to run in parallel (I might be wrong on this). &lt;/p&gt;

&lt;p&gt;In this case each would post a different URL in its sticky comment as long as the underlying deployment logic generated a different URL on every deployment. For example, every deployment of Next.js project to Preview environment in your Vercel project would generate a new URL.&lt;/p&gt;

&lt;p&gt;In fact, since Vercel has such a generous allowance of deployment to an environment that would keep active, all deployments of the same branch, within the same pull request are active and accessible. If your deployment job references a GitHub environment (e.g. "Preview" in above example) then all of these deployment URLs are accessible on the pull request, not just the latest one:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1j9p13cf0qtgffjyr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1j9p13cf0qtgffjyr4.png" alt=" " width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All deployments to an environment, with their respective URLs if provided, can also be seen on repo main page in a section on right hand side named &lt;strong&gt;Deployments&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmu6dvo9owas1mx7sm0nq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmu6dvo9owas1mx7sm0nq.png" alt=" " width="423" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can click an environment name and see a list of all deployments to it, including link to each. The link to the most recent deployment is shown right at the top:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldtyzt600sr40esbgxhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldtyzt600sr40esbgxhm.png" alt=" " width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;To use GitHub Environments in my Next.js project which I deploy to Vercel, I do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create two GitHub Environments, named Production and Preview.&lt;/p&gt;

&lt;p&gt;A Vercel project to which the repo is deployed also has evironments with the same names (these are Vercel environments though, not GitHub environments). So its good to have matching names in both GitHub repo and Vercel project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My CI workflow deploys a pull request's source branch to Vercel's Preview environment and references GitHub Environment named "Preview":&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci-${{ github.ref }}&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;checks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;VERCEL_ORG_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_ORG_ID }}&lt;/span&gt;
  &lt;span class="na"&gt;VERCEL_PROJECT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_PROJECT_ID }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-to-vercel-preview-env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Vercel Preview environment&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-24.04&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy-artifacts.outputs.previewUrl }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Vercel CLI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install --global vercel@latest&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pull Vercel Environment Information&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Project Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel build --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Project Artifacts to Vercel&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-artifacts&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;previewUrl=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})&lt;/span&gt;
          &lt;span class="s"&gt;echo "previewUrl=$previewUrl" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Release/CD workflow that deploys &lt;code&gt;main&lt;/code&gt; to Vercel Production environment references the GitHub Environment named "Production":&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release to Production&lt;/span&gt;
&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-to-prod-pipeline&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Vercel&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-24.04&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Production&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy-artifacts.outputs.previewUrl }}&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VERCEL_ORG_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_ORG_ID }}&lt;/span&gt;
      &lt;span class="na"&gt;VERCEL_PROJECT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_PROJECT_ID }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Vercel CLI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install --global vercel@latest&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pull Vercel Environment Information&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Project Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Project Artifacts to Vercel&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-artifacts&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;previewUrl=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})&lt;/span&gt;
          &lt;span class="s"&gt;echo "previewUrl=$previewUrl" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>github</category>
      <category>githubactions</category>
      <category>nextjs</category>
      <category>vercel</category>
    </item>
    <item>
      <title>Set up linting and formatting for code and (S)CSS in Next.js with ESLint, Stylelint, Prettier and lint-staged</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Wed, 26 Apr 2023 21:20:21 +0000</pubDate>
      <link>https://forem.com/nausaf/set-up-linting-and-formatting-for-code-and-scss-files-in-a-nextjs-project-43fb</link>
      <guid>https://forem.com/nausaf/set-up-linting-and-formatting-for-code-and-scss-files-in-a-nextjs-project-43fb</guid>
      <description>&lt;p&gt;UPDATE: Please find an up to date version of this article on &lt;a href="https://www.freecodecamp.org/news/how-to-set-up-eslint-prettier-stylelint-and-lint-staged-in-nextjs" rel="noopener noreferrer"&gt;FreeCodeCamp&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>eslint</category>
      <category>stylelint</category>
      <category>prettier</category>
    </item>
    <item>
      <title>Replacement for retired VS Code Jest codelens</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Fri, 30 Dec 2022 23:32:54 +0000</pubDate>
      <link>https://forem.com/nausaf/replacement-for-retired-vs-code-jest-codelens-36j4</link>
      <guid>https://forem.com/nausaf/replacement-for-retired-vs-code-jest-codelens-36j4</guid>
      <description>&lt;p&gt;The Codelens provided by VS Code Jest extension on tests in the editor &lt;a href="https://github.com/jest-community/vscode-jest/pull/950" rel="noopener noreferrer"&gt;has been retired&lt;/a&gt; (hence &lt;a href="https://dev.to/nausaf/vs-code-jest-extension-tackling-debug-codelens-disappearing-act-1l98"&gt;this post on Jest CodeLens&lt;/a&gt; I wrote a while back is also outdated).&lt;/p&gt;

&lt;p&gt;The fix is really simple:&lt;/p&gt;

&lt;p&gt;In your User Settings file (&lt;code&gt;setttings.json&lt;/code&gt;), put this line in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"testing.defaultGutterClickAction": "contextMenu"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be able to access Debug test etc. from the Play button in the gutter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqad3y247g2f1zw3nme1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqad3y247g2f1zw3nme1.png" alt=" " width="551" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>rest</category>
      <category>api</category>
      <category>discuss</category>
    </item>
    <item>
      <title>VS Code Jest extension: tackling Debug codelens disappearing act</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Tue, 18 Oct 2022 16:24:03 +0000</pubDate>
      <link>https://forem.com/nausaf/vs-code-jest-extension-tackling-debug-codelens-disappearing-act-1l98</link>
      <guid>https://forem.com/nausaf/vs-code-jest-extension-tackling-debug-codelens-disappearing-act-1l98</guid>
      <description>&lt;p&gt;This is the section of my VS Code User Preferences &lt;code&gt;settings.json&lt;/code&gt; (to access, &lt;code&gt;Ctrl +P&lt;/code&gt; then select &lt;code&gt;Preferences: Open User Settings&lt;/code&gt;) for Jest VS Code extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    ...
    "jest.coverageFormatter": "GutterFormatter",  
    "jest.debugCodeLens.showWhenTestStateIn": [
        "fail",
        "unknown",
        "pass",
        "skip"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I found that the Jest extension's Debug CodeLens wouldn't always appear on tests. A lot of the time I had to go to &lt;strong&gt;Testing&lt;/strong&gt; window in VS Code sidebar to start debugging on a test because the Debug codelens wasn't available on the test in situ in the code file. &lt;/p&gt;

&lt;p&gt;Turns out this was because &lt;code&gt;jest.debugCodeLens.showWhenTestStateIn&lt;/code&gt; has default value of &lt;code&gt;["fail", "unknown"]&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I added the other two possible statuses to ensure that the Debug codelens is always available.&lt;/p&gt;




&lt;p&gt;The other setting here is &lt;code&gt;jest.coverageFormatter&lt;/code&gt;. I find seeing coverage in the gutter on the left of a file unobtrusive compared to the extension's default behaviour of colour-coding regions of code in the file.&lt;/p&gt;




&lt;p&gt;You can of course set the above on a per project basis by putting the snippet above in &lt;code&gt;.vscode\settings.json&lt;/code&gt; in your workspace (this file can be created/navigated to using the command &lt;code&gt;Ctrl + P&lt;/code&gt; then &lt;code&gt;Preferences: Open Workspace Settings&lt;/code&gt;).&lt;/p&gt;

</description>
      <category>jest</category>
      <category>javascript</category>
      <category>testing</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Configure different Jest timeouts for unit and integration tests in the same project</title>
      <dc:creator>Naveed Ausaf</dc:creator>
      <pubDate>Mon, 17 Oct 2022 21:54:21 +0000</pubDate>
      <link>https://forem.com/nausaf/configuring-different-timeouts-for-integration-and-unit-tests-in-jest-3eoh</link>
      <guid>https://forem.com/nausaf/configuring-different-timeouts-for-integration-and-unit-tests-in-jest-3eoh</guid>
      <description>&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You want to configure &lt;strong&gt;different test execution timeouts&lt;/strong&gt; for &lt;strong&gt;Jest tests in different folders&lt;/strong&gt; in the same project e.g. &lt;strong&gt;1 second&lt;/strong&gt; for tests in &lt;code&gt;./tests/unitTests/&lt;/code&gt; and &lt;strong&gt;60 seconds&lt;/strong&gt; for tests in &lt;code&gt;./tests/integrationTests&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;During debugging&lt;/strong&gt;, the timeout for tests in any folder should be quite large, say &lt;strong&gt;10 minutes&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Otherwise Jest could throw timeout errors like &lt;code&gt;thrown: "Exceeded timeout of 5000 ms for a test.&lt;/code&gt; even if a test completes successfully. This happens when debugging a test takes longer than the (default or explicitly configured) timeout for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pocjkvs6vvv6krd1nom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pocjkvs6vvv6krd1nom.png" alt=" " width="800" height="273"&gt;&lt;/a&gt;    &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;p&gt;Given the folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
│   jest.config.js
│   package-lock.json
│   package.json
│
└───tests
    ├───integrationTests
    │       integrationTestSuite.test.js
    │
    └───unitTests
            unitTestSuite.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a &lt;strong&gt;solution is as follows&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm install --save-dev debugger-is-attached&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;jestSetup.js&lt;/code&gt; file in each of the test folders and set the desired timeout via a call to &lt;code&gt;jest.setTimeout()&lt;/code&gt; method.&lt;br&gt;
So we have a &lt;code&gt;tests/unitTests/jestSetup.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//timeout of 1 second&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;and a &lt;code&gt;tests/integrationTests/jestSetup.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//timeout of 1 minute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a id="complete-config"&gt;&lt;/a&gt;Create &lt;code&gt;jest.config.js&lt;/code&gt; in project root as follows&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;debuggerIsAttached&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debugger-is-attached&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDebuggerAttached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;debuggerIsAttached&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unitTestFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/unitTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;integrationTestFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/integrationTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getSetupFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;isDebuggerAttached&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jestSetup.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseProjectConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//Here put any properties that are the same for&lt;/span&gt;
    &lt;span class="c1"&gt;//all folders and can be specified at level&lt;/span&gt;
    &lt;span class="c1"&gt;//of the project object (all such properties&lt;/span&gt;
    &lt;span class="c1"&gt;//are declared in type ProjectConfig in&lt;/span&gt;
    &lt;span class="c1"&gt;//Config.ts in Jest repo)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//any config key/values in configuration (except those&lt;/span&gt;
    &lt;span class="c1"&gt;//that are to specified in ProjectConfig)&lt;/span&gt;
    &lt;span class="c1"&gt;//e.g. &lt;/span&gt;
    &lt;span class="c1"&gt;//collectCoverage: true,&lt;/span&gt;

    &lt;span class="c1"&gt;//project config&lt;/span&gt;
    &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseProjectConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UnitTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unitTestFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.test.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="na"&gt;slowTestThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;//1 second&lt;/span&gt;
        &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getSetupFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unitTestFolder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseProjectConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IntegrationTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;integrationTestFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.test.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="na"&gt;slowTestThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;//1 minute&lt;/span&gt;
        &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getSetupFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;integrationTestFolder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;//any other Jest config goes here&lt;/span&gt;
    &lt;span class="c1"&gt;//(these are any properties declared in type&lt;/span&gt;
    &lt;span class="c1"&gt;//InitialConfig but not in type ProjectConfig&lt;/span&gt;
    &lt;span class="c1"&gt;//in Config.ts in Jest repo)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDebuggerAttached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;testTimeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;600000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//ten minutes&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In User Settings (&lt;code&gt;settings.json&lt;/code&gt; which appears when from the Ctrl + P command pallette you select &lt;strong&gt;Preferences: Open User Setting (JSON)&lt;/strong&gt;), set &lt;code&gt;"jest.monitorLongRun"&lt;/code&gt; to a value that is equal to or greater than the largest of the folder-specific timeouts declared in &lt;code&gt;jest.config.js&lt;/code&gt; above. For example,&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"jest.monitorLongRun": 60000, //1 minute
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Explanation
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Configuring different timeouts for different test folders
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;testTimeout&lt;/code&gt; property can be set in &lt;code&gt;jest.config.js&lt;/code&gt; in project root to set a timeout other than the default of 5s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;testTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//60 seconds&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to specify &lt;code&gt;testTimeout&lt;/code&gt; separately for different subfolders?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The canonical way to assign different configurations to tests in different subfolders is to use Jest's &lt;em&gt;monorepo configuration&lt;/em&gt;. We pretend, as far as Jest is concerned, that folders &lt;code&gt;tests/integrationTests&lt;/code&gt; and &lt;code&gt;tests/unitTests&lt;/code&gt; are two separate projects in a &lt;a href="https://www.atlassian.com/git/tutorials/monorepos" rel="noopener noreferrer"&gt;monorepo&lt;/a&gt; (a collection of related projects stored in a single repository). &lt;/p&gt;

&lt;p&gt;Using this approach we can configure separate timeouts for our two subfolders as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;jest.config.js&lt;/code&gt; in each subfolder. In this set &lt;code&gt;testTimeout&lt;/code&gt; property as shown in the snippet above.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Declare the different folder-specific config files as &lt;em&gt;projects&lt;/em&gt; in the &lt;strong&gt;top-level &lt;code&gt;jest.config.js&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/unitTests/jest.config.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/integrationTests/jest.config.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point the folder structure would be as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
│   jest.config.js
│   package-lock.json
│   package.json
│
└───tests
    ├───integrationTests
    │       integrationTestSuite.test.js
    │       jest.config.js
    │
    └───unitTests
            jest.config.js
            unitTestSuite.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt; is that if we configure &lt;code&gt;testTimeout&lt;/code&gt; property in a folder-specific config file &lt;strong&gt;it will not have any effect&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is because &lt;code&gt;testTimeout&lt;/code&gt; is not defined in type &lt;code&gt;ProjectConfig&lt;/code&gt; in &lt;a href="https://github.com/facebook/jest/blob/main/packages/jest-types/src/Config.ts" rel="noopener noreferrer"&gt;&lt;code&gt;Config.ts&lt;/code&gt;&lt;/a&gt; which specifies the config schema of &lt;code&gt;project&lt;/code&gt; (which for use are the two subfolders of tests).&lt;/p&gt;

&lt;p&gt;Even though the top-level as well as folder-specific config files are all called &lt;code&gt;jest.config.js&lt;/code&gt;, the properties that are allowed in folder level config are not all the same as the properties allowed in top level config. &lt;/p&gt;

&lt;p&gt;Folder-specific config files need to have be those defined in type &lt;code&gt;ProjectConfig&lt;/code&gt; whereas top-level &lt;code&gt;jest.config.js&lt;/code&gt; specifies properties that are &lt;a href="https://github.com/facebook/jest/issues/9529" rel="noopener noreferrer"&gt;probably declared in type &lt;code&gt;InitialConfig&lt;/code&gt; in &lt;code&gt;Config.ts&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Many keys are defined in both types but not &lt;code&gt;testTimeout&lt;/code&gt;: it is only contained in &lt;code&gt;InitialConfig&lt;/code&gt; and therefore only has effect if declared in the top-level config file. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Therefore &lt;code&gt;testTimeout&lt;/code&gt; property cannot be use to override test timeouts in &lt;code&gt;jest.config.js&lt;/code&gt; files in subfolders&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, &lt;strong&gt;to set timeout at subfolder level&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We can call &lt;code&gt;jest.setTimeout(TIMEOUT_IN_MS)&lt;/code&gt; in a &lt;code&gt;.js&lt;/code&gt; file in the subfolder, conventionally named &lt;code&gt;jestSetup.js&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Declare &lt;code&gt;jestSetup.js&lt;/code&gt; in the folder-level &lt;code&gt;jest.config.js&lt;/code&gt; so that &lt;a href="https://jestjs.io/docs/configuration#setupfilesafterenv-array" rel="noopener noreferrer"&gt;it would be run by Jest before any tests in that folder are executed&lt;/a&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;For example&lt;/strong&gt; create &lt;code&gt;tests/integrationTests/jestSetup.js&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//timeout of 1 minute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a &lt;code&gt;tests/integrationTests/jest.config.js&lt;/code&gt; to go with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IntegrationTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;rootDir&amp;gt;/jestSetup.js`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For unit tests, &lt;code&gt;./tests/unitTests/jest.config.js&lt;/code&gt; would be the same as the config file for integration tests folder shown above (because &lt;code&gt;&amp;lt;rootDir&amp;gt;&lt;/code&gt; always resolves to the containing folder). However &lt;code&gt;./tests/integrationTests/jestSetup.js&lt;/code&gt; would specify a different timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//timeout of 1 second&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The folder structure of this &lt;strong&gt;working solution&lt;/strong&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
│   jest.config.js
│   package-lock.json
│   package.json
│
└───tests
    ├───integrationTests
    │       integrationTestSuite.test.js
    │       jest.config.js
    |       jestSetup.js
    │
    └───unitTests                 
            jest.config.js
            jestSetup.js
            unitTestSuite.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;We can eliminate folder-specific &lt;code&gt;jest.config.js&lt;/code&gt; files by pulling the info in the top level config&lt;/strong&gt; so the &lt;code&gt;./jest.config.js&lt;/code&gt; in the root now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UnitTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      
      &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/unitTests/**/*.test.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/unitTests/jestSetup.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IntegrationTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/integrationTests/**/*.test.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/integrationTests/jestSetup.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The folder structure of this &lt;strong&gt;more compact solution&lt;/strong&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
│   jest.config.js
│   package-lock.json
│   package.json
│
└───tests
    ├───integrationTests
    │       integrationTestSuite.test.js
    │       jestSetup.js
    │
    └───unitTests
            jestSetup.js
            unitTestSuite.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd like to point out &lt;strong&gt;three improvements that can be made&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It is worth adding a &lt;code&gt;slowTestThreshold&lt;/code&gt; property in every &lt;code&gt;project&lt;/code&gt; object and set it equal to or somewhat greater than the timeout declared in the corresponding &lt;code&gt;jestSetup.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A longer timeout prevents Jest from throwing an error on longer running tests. But it would still show the execution time of such a tests with a red background:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsd0xi7z77bf8abyi4hrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsd0xi7z77bf8abyi4hrv.png" alt=" " width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;slowTestThreshold: 60000&lt;/code&gt; to the folder's config in &lt;code&gt;jest.config.js&lt;/code&gt; tells Jest that this is how long you expect tests in that folder to take so it doesn't show execution times in warning colour.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If a property is exported both &lt;a href="https://github.com/facebook/jest/blob/main/packages/jest-types/src/Config.ts#L424" rel="noopener noreferrer"&gt;&lt;code&gt;ProjectConfig&lt;/code&gt; and &lt;code&gt;InitialConfig&lt;/code&gt; types&lt;/a&gt; then, if you configure it in top level &lt;code&gt;jest.config.js&lt;/code&gt; but &lt;strong&gt;not&lt;/strong&gt; in folder-level config, it would be overridden by project config to its default value which would probably be &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;For example given a &lt;code&gt;jest.config.js&lt;/code&gt; that looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setupFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./topLevelSetupFile.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IntegrationTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/integrationTests/**/*.test.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/tests/integrationTests/jestSetup.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In the effective folder-specific configuration for folder &lt;code&gt;./tests/integrationTests/&lt;/code&gt;, &lt;code&gt;setupFiles&lt;/code&gt; property would be set to nothing (&lt;code&gt;null&lt;/code&gt; I think).&lt;/p&gt;

&lt;p&gt;A nice solution (from &lt;a href="https://orlandobayo.com/blog/monorepo-testing-using-jest/" rel="noopener noreferrer"&gt;Orlando Bayo's post&lt;/a&gt;) is to set the &lt;em&gt;shared config&lt;/em&gt; - properties that are shared across all folders - in a &lt;code&gt;baseProjectconfig&lt;/code&gt; object and spread this into every &lt;code&gt;project&lt;/code&gt; object.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Incorporating both of these improvements into our solution, we have the following &lt;code&gt;jest.config.js&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseProjectConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//Here put any properties that are the same for&lt;/span&gt;
    &lt;span class="c1"&gt;//all folders and can be specified at level&lt;/span&gt;
    &lt;span class="c1"&gt;//of the project object (all such properties&lt;/span&gt;
    &lt;span class="c1"&gt;//are declared in type ProjectConfig in&lt;/span&gt;
    &lt;span class="c1"&gt;//Config.ts in Jest repo)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//any config key/values in configuration (except those&lt;/span&gt;
    &lt;span class="c1"&gt;//that are specified in ProjectConfig)&lt;/span&gt;
    &lt;span class="c1"&gt;//e.g. &lt;/span&gt;
    &lt;span class="c1"&gt;//collectCoverage: true,&lt;/span&gt;

    &lt;span class="c1"&gt;//project config&lt;/span&gt;
    &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseProjectConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UnitTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unitTestFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.test.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="na"&gt;slowTestThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;//1 second&lt;/span&gt;
        &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getSetupFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unitTestFolder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseProjectConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IntegrationTests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;integrationTestFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.test.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="na"&gt;slowTestThreshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;//1 minute&lt;/span&gt;
        &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getSetupFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;integrationTestFolder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;        
    &lt;span class="c1"&gt;//any other Jest config goes here&lt;/span&gt;
    &lt;span class="c1"&gt;//(these are any properties declared in type&lt;/span&gt;
    &lt;span class="c1"&gt;//InitialConfig but not in type ProjectConfig&lt;/span&gt;
    &lt;span class="c1"&gt;//in Config.ts in Jest repo)&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Jest VS Code extension still shows a popup if a test takes too long to execute (although, because of the configuration above, the test won't fail on a timeout):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5crv5o8pzxb9ztejo6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5crv5o8pzxb9ztejo6k.png" alt=" " width="538" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To prevent this warning window from popping up, set &lt;code&gt;"jest.monitorLongRun"&lt;/code&gt; in User Setting file (to edit it select &lt;strong&gt;Preferences: Open User Settings&lt;/strong&gt; from Ctrl + P command pallette) to the value of the longest of your folder-specific timeouts e.g.:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"jest.monitorLongRun": 60000, //1 minute
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting a long timeout when debugging
&lt;/h3&gt;

&lt;p&gt;When debugging, if you take longer to step through a test than the (default or explicitly configured) timeout, Jest would throw a timeout error at the end even if the test completed successfully. I find that for a test to fail like this is confusing when you're debugging a test that you expect to pass.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pocjkvs6vvv6krd1nom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pocjkvs6vvv6krd1nom.png" alt=" " width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To address this issue we can use the &lt;a href="https://www.npmjs.com/package/debugger-is-attached" rel="noopener noreferrer"&gt;&lt;code&gt;debugger-is-attached&lt;/code&gt;&lt;/a&gt; package. This exports an async function that returns &lt;code&gt;true&lt;/code&gt; if debugger is attached and &lt;code&gt;false&lt;/code&gt; otherwise.&lt;/p&gt;

&lt;p&gt;It can be integrated into &lt;code&gt;jest.config.js&lt;/code&gt; by exporting an &lt;code&gt;async&lt;/code&gt; function that returns the config object instead of directly returning the object in question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;debuggerIsAttached&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debugger-is-attached&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//do async things&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//the config object&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the function, if the debugger is not attached then everything should be the same as before but if it is attached, then we make two changes to the returned object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;not&lt;/strong&gt; configure &lt;code&gt;jestSetup.js&lt;/code&gt;, the file that declares the timeout for tests in its containing folder, to run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;set &lt;code&gt;testTimeout&lt;/code&gt; property to 10 minutes (&lt;code&gt;600000&lt;/code&gt; ms) to give us plenty of time to debug a test.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After these changes, the final top-level &lt;code&gt;jest.config.js&lt;/code&gt; is as shown in TL;DR at the top. &lt;/p&gt;

&lt;p&gt;Thanks for reading. Any comments or suggestions for improvement would be greatly appreciated.&lt;/p&gt;

</description>
      <category>jest</category>
      <category>node</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
