<?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>DEV Community: nausaf</title>
    <description>The latest articles on DEV Community by nausaf (@nausaf).</description>
    <link>https://dev.to/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>DEV Community: nausaf</title>
      <link>https://dev.to/nausaf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nausaf"/>
    <language>en</language>
    <item>
      <title>Use .env files for .NET local development configuration with VS Code</title>
      <dc:creator>nausaf</dc:creator>
      <pubDate>Mon, 14 Jul 2025 21:33:51 +0000</pubDate>
      <link>https://dev.to/nausaf/use-env-files-for-storing-development-secrets-and-configuration-for-net-core-projects-in-vs-code-1kbh</link>
      <guid>https://dev.to/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="730"&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>nausaf</dc:creator>
      <pubDate>Sat, 05 Jul 2025 16:03:39 +0000</pubDate>
      <link>https://dev.to/nausaf/why-i-moved-from-autofixture-to-bogus-for-test-data-generation-for-cxunit-test-49kg</link>
      <guid>https://dev.to/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>nausaf</dc:creator>
      <pubDate>Tue, 01 Jul 2025 00:48:13 +0000</pubDate>
      <link>https://dev.to/nausaf/intercepting-terminal-traffic-with-fiddler-5ci4</link>
      <guid>https://dev.to/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="496"&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>nausaf</dc:creator>
      <pubDate>Tue, 19 Nov 2024 18:04:55 +0000</pubDate>
      <link>https://dev.to/nausaf/aborted-attempt-to-return-meaningful-problem-details-response-from-model-binding-errors-in-an-353g</link>
      <guid>https://dev.to/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>Patterns for Routing in ASP.NET Core minimal APIs</title>
      <dc:creator>nausaf</dc:creator>
      <pubDate>Sun, 27 Oct 2024 12:59:32 +0000</pubDate>
      <link>https://dev.to/nausaf/patterns-for-routing-in-aspnet-core-4ghm</link>
      <guid>https://dev.to/nausaf/patterns-for-routing-in-aspnet-core-4ghm</guid>
      <description>&lt;h1&gt;
  
  
  Introduction to Routing in ASP.NET Core
&lt;/h1&gt;

&lt;p&gt;In ASP.NET Core, Routing is the process of mapping a requested URL to a handler. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, during startup in Program.cs, you have to register a handler against a &lt;strong&gt;route template&lt;/strong&gt; by calling &lt;code&gt;MapXXX&lt;/code&gt; method on &lt;code&gt;app&lt;/code&gt; or on a &lt;code&gt;RouteGroupBuilder&lt;/code&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in my apps, I do not call &lt;code&gt;MapXXX&lt;/code&gt; directly on &lt;code&gt;app&lt;/code&gt;. Instead I always call these on a &lt;code&gt;RouteGroupBuilder&lt;/code&gt; object obtained from &lt;code&gt;app&lt;/code&gt; for a certain URL prefix e.g. the &lt;code&gt;routeBuilder&lt;/code&gt; used above was obtained from app by saying &lt;code&gt;app.MapGroup("/product")&lt;/code&gt;. Therefore the route template that was mapped to the handler function &lt;code&gt;HandleCreateProduct&lt;/code&gt; is &lt;code&gt;/product/{id}&lt;/code&gt;. It would be obtained by concatenating segment with which the called &lt;code&gt;RouteGroupBuilder&lt;/code&gt; was initialised with the segment that passed to &lt;code&gt;MapXXX&lt;/code&gt; method when registering the handler.&lt;/p&gt;

&lt;p&gt;All registered route templates are stored in a dictionary, each against its registered handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next,&lt;/strong&gt; when an HTTP request for a URL is received, routing is done by the &lt;code&gt;RoutingMiddleware&lt;/code&gt;: it first maps the incoming URL to one of the route templates stored as keys in the dictionary, then it maps that route template to its registered handler stored in the dictionary. It sets this handler in HttpContext so that it is available to every subsequent middleware until the request reaches the &lt;code&gt;EndpointMiddleware&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;EndpointMiddleware&lt;/code&gt; would invoke the handler function, passing to it any parameters using its model binding logic, sourcing them from request and from the DI container. There is one route parameter in the example route template, &lt;code&gt;{id}&lt;/code&gt;. If, given the example route template &lt;code&gt;/product/{id}&lt;/code&gt;, the URL requested in an HTP request was &lt;code&gt;/product/12&lt;/code&gt;, then &lt;code&gt;id&lt;/code&gt; parameter would be assigned value &lt;code&gt;12&lt;/code&gt; during model binding. This would be passed value of an &lt;code&gt;int id&lt;/code&gt; parameter to the handler whose signature could be something 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;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;Results&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Ok&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;&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="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//code to retrieve product from database and return it&lt;/span&gt;
    &lt;span class="c1"&gt;//in JSON body of response&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;In addition to &lt;em&gt;routing&lt;/em&gt;, i.e. mapping an incoming URL to a handler, the Routing system in ASP.NET Core allows us to perform the reverse process:&lt;/strong&gt; it allows generation of URL for a given handler.&lt;/p&gt;

&lt;p&gt;In a handler, we can call &lt;code&gt;LinkGenerator.GetPathByName&lt;/code&gt; (a instance of &lt;code&gt;LinkGenerator&lt;/code&gt; - this class is part of the Routing system - is injected from DI if declared as a parameter in a handler) and provide a handler's name to it:&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;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;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="n"&gt;result&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 name would have have been declared for a handler by chaining &lt;code&gt;.WithName&lt;/code&gt; to the &lt;code&gt;MapXXX&lt;/code&gt; call when registering the handle with the Routing system. For example in the snippet shown above for registering the function &lt;code&gt;HandleGetProduct&lt;/code&gt; as handler, we chained &lt;code&gt;.WithName(HandlerNames.GetProduct)&lt;/code&gt; where &lt;code&gt;HandlerNames.GetProduct&lt;/code&gt; is a const:&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;private&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;/div&gt;



&lt;p&gt;The declared name for a handler should be unique among all registered handlers in the app. The naming convention I use - &lt;code&gt;&amp;lt;operation&amp;gt;-&amp;lt;microservice&amp;gt;&lt;/code&gt; ensures that.&lt;/p&gt;

&lt;p&gt;Referencing a handler by name rather than by name rather than by passing in a delegate to the handler function makes sense: the handler delegate may be private and may not be available throughout the app where it need to be referenced when a URL to it needs to be generated.&lt;/p&gt;

&lt;p&gt;If the route template to which the referenced handler was mapped includes route parameters, you can provide values for these in a dictionary passed as second parameter of &lt;code&gt;LinkGenerator.GetPathByName&lt;/code&gt; as shown above.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LinkGenerator.GetPathByName&lt;/code&gt; generates a absolute URL to the handler but without a host name. &lt;code&gt;LinkGenerator&lt;/code&gt; has other methods such as &lt;code&gt;GetUriByName&lt;/code&gt; which prefixes includes the hostname/domain name at the beginning also. I avoid this as this can lead to security vulnerabilities such as to a &lt;a href="https://andrewlock.net/adding-host-filtering-to-kestrel-in-aspnetcore/" rel="noopener noreferrer"&gt;host name spoofing attack&lt;/a&gt; if the &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/host-filtering?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;HostFilteringMiddleware&lt;/a&gt; is not properly configured (it is added to the middleware pipeline by default but in a disabled state).&lt;/p&gt;

&lt;p&gt;For further information, see MS Docs page &lt;a href="https://learn.microsoft.com/en-gb/aspnet/core/fundamentals/routing?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;Routing in ASP.NET Core&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Patterns and Guidelines I Use to Implement Routing
&lt;/h1&gt;

&lt;p&gt;These are the patterns and guidelines that I use in my minimal API projects to implement Routing:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Mapping Route Templates to Handlers
&lt;/h2&gt;

&lt;p&gt;Create a class which collects together all handlers for a cohesive area of business logic, say for a (synchronous) microservice, and registers these in a &lt;code&gt;static MapRoutes&lt;/code&gt; method. This method is called from Program.cs. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The advantage of this pattern is that:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a microservice is responsible for registering all of its operation handlers with the names and metadata that it sees fit to declare. In particular names, route details and metadata for all of the operations in a cohesive group of handlers or a microservice do not pollute &lt;code&gt;Program.cs&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the same time, &lt;code&gt;Program.cs&lt;/code&gt; can choose a route prefix and metadata for the whole group, which can depend on any other groups that &lt;code&gt;Program.cs&lt;/code&gt; chooses to register handlers for.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;To implement this pattern:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Handlers class
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In &lt;code&gt;Handlers&lt;/code&gt; folder, create a class named &lt;code&gt;&amp;lt;service name&amp;gt;Handlers&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In this class, create each handler as a &lt;code&gt;private static&lt;/code&gt; method named &lt;code&gt;Handle&amp;lt;operation&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a nested class &lt;code&gt;private static class HandlerNames&lt;/code&gt; to keep string constants for handler names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create method &lt;code&gt;public static RouteGroupBuilder MapRoutes(RouteGroupBuilder routeBuilder)&lt;/code&gt; registers each handler with its name from &lt;code&gt;HandlerNames&lt;/code&gt; by calling &lt;code&gt;routeBuilde.MapXXX&lt;/code&gt; method for the appropriate HTTP verb, then returns the passed in &lt;code&gt;RouteGroupBuilder&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&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;flowmazonapi.Domain&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;flowmazonapi.BusinessLogic&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;flowmazonapi.BusinessLogic.ProductService&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;Microsoft.AspNetCore.Http.HttpResults&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;ProductHandlers&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;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;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;Results&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Ok&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="n"&gt;ValidationProblem&lt;/span&gt;&lt;span class="p"&gt;&amp;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="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//throw new NotImplementedException();&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;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;Results&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;,&lt;/span&gt; &lt;span class="n"&gt;ValidationProblem&lt;/span&gt;&lt;span class="p"&gt;&amp;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="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;httpContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//throw new NotImplementedException();&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;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;routeBuilder&lt;/span&gt;&lt;span class="p"&gt;)&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="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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Call &lt;code&gt;MapRoutes&lt;/code&gt; on a Handlers class from &lt;code&gt;Program.cs&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;Program.cs&lt;/code&gt;, once all middleware have been added (just before &lt;code&gt;app.Run()&lt;/code&gt; is called, call &lt;code&gt;MapRoutes&lt;/code&gt; on the handlers class, passing in a &lt;code&gt;RouteGroupBuilder&lt;/code&gt; taht has been initialised with the prefix that would be prefixed to route templates that will be registered.&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="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;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;"/product"&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;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;Here &lt;code&gt;WithTags&lt;/code&gt; is an estension method provided by &lt;code&gt;OpenApiRouteHandlerBuilderExtensions&lt;/code&gt; and attaches the tag &lt;code&gt;product Operations&lt;/code&gt; to the whole group of routes that was registered by the called &lt;code&gt;ProductHandlers.MapRoutes&lt;/code&gt; method. Being able to do this using a &lt;em&gt;fluent&lt;/em&gt; syntax shows the value of returning the same &lt;code&gt;RouteGroupBuidler&lt;/code&gt; that was passed in to the &lt;code&gt;MapRoutes&lt;/code&gt; method. &lt;/p&gt;

&lt;p&gt;The nullability checks in modern C# make it very difficult for &lt;code&gt;ProductHandlers.MapRoutes&lt;/code&gt; &lt;em&gt;not&lt;/em&gt; to return the &lt;code&gt;RouteGroupBuilder&lt;/code&gt; that was passed in to it. Therefore I am happy with this convention to achieve a fluent interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Mapping Handlers to URLs
&lt;/h2&gt;

&lt;p&gt;In a handler that needs to return a URL, e.g. to a resource that was created in the handler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Declare &lt;code&gt;LinkGenerator linkGen&lt;/code&gt; parameter in the handler (this would be resolved from DI container when the handler is called by &lt;code&gt;EndpointMiddleware&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the handler, call &lt;code&gt;linkGen.GetPathByName(HandlersName.&amp;lt;const for handler name&amp;gt;, &amp;lt;any route parameters&amp;gt;)&lt;/code&gt; to generate a URL to the handler.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always use &lt;code&gt;LinkGenerator.GetPathByName&lt;/code&gt;, &lt;strong&gt;never use &lt;code&gt;LinkGenerator.GetUriByName&lt;/code&gt;&lt;/strong&gt; to avoid security issues such as host name spoofing mentioned in the intro section above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&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;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;Results&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;,&lt;/span&gt; &lt;span class="n"&gt;ValidationProblem&lt;/span&gt;&lt;span class="p"&gt;&amp;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="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;httpContext&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;result&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;CreateProduct&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;);&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;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="n"&gt;result&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;h3&gt;
  
  
  3. Go easy on route constraints
&lt;/h3&gt;

&lt;p&gt;Route templates should be as simple as possible. While ASP.NET Core provides a fairly &lt;a href="https://learn.microsoft.com/en-gb/aspnet/core/fundamentals/routing?view=aspnetcore-8.0#route-constraints" rel="noopener noreferrer"&gt;rich set of constraints&lt;/a&gt; that may be placed on route parameters (such as &lt;code&gt;{id}&lt;/code&gt; in code shown above), these should be avoided as much as possible.&lt;/p&gt;

&lt;p&gt;In particular, as the documentation advises, &lt;strong&gt;route constraints should not be used for validation of route parameter values&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use &lt;code&gt;**&lt;/code&gt; for catchall parameter rather than &lt;code&gt;*&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When using &lt;a href="https://learn.microsoft.com/en-gb/aspnet/core/fundamentals/routing?view=aspnetcore-8.0#route-templates" rel="noopener noreferrer"&gt;catchall parameters&lt;/a&gt;, I only use &lt;code&gt;**&lt;/code&gt; rather than single &lt;code&gt;*&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Both have the same behaviour in mapping URL to a route template and to parameter value during model binding, but in the reverse process, when we use LinkGenerator&lt;code&gt;class to generate a URL from a handler's name and route parameter values,&lt;/code&gt;*&lt;em&gt;&lt;code&gt;output&lt;/code&gt;/&lt;code&gt;as a&lt;/code&gt;/&lt;code&gt;in the generated URL whereas the singel asterisk (&lt;/code&gt;&lt;/em&gt;&lt;code&gt;) outputs&lt;/code&gt;%2F` which I almost never want.&lt;/p&gt;

</description>
      <category>dotnetcore</category>
      <category>aspdotnet</category>
      <category>api</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Creating an NPM package that runs on command line</title>
      <dc:creator>nausaf</dc:creator>
      <pubDate>Wed, 02 Oct 2024 16:59:10 +0000</pubDate>
      <link>https://dev.to/nausaf/creating-an-npm-package-that-runs-on-command-line-with-npx-9a0</link>
      <guid>https://dev.to/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="Image description" 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="Image description" 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>nausaf</dc:creator>
      <pubDate>Mon, 30 Sep 2024 00:09:32 +0000</pubDate>
      <link>https://dev.to/nausaf/environments-in-github-with-example-of-nextjs-deployment-to-vercel-3hmm</link>
      <guid>https://dev.to/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="Image description" 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="Image description" 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="Image description" width="800" height="544"&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="Image description" 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="Image description" width="800" height="696"&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>nausaf</dc:creator>
      <pubDate>Wed, 26 Apr 2023 21:20:21 +0000</pubDate>
      <link>https://dev.to/nausaf/set-up-linting-and-formatting-for-code-and-scss-files-in-a-nextjs-project-43fb</link>
      <guid>https://dev.to/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>nausaf</dc:creator>
      <pubDate>Fri, 30 Dec 2022 23:32:54 +0000</pubDate>
      <link>https://dev.to/nausaf/replacement-for-retired-vs-code-jest-codelens-36j4</link>
      <guid>https://dev.to/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="Image description" 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>nausaf</dc:creator>
      <pubDate>Tue, 18 Oct 2022 16:24:03 +0000</pubDate>
      <link>https://dev.to/nausaf/vs-code-jest-extension-tackling-debug-codelens-disappearing-act-1l98</link>
      <guid>https://dev.to/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>nausaf</dc:creator>
      <pubDate>Mon, 17 Oct 2022 21:54:21 +0000</pubDate>
      <link>https://dev.to/nausaf/configuring-different-timeouts-for-integration-and-unit-tests-in-jest-3eoh</link>
      <guid>https://dev.to/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="Image description" 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="Image description" width="800" height="132"&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="Image description" 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="Image description" 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>
