<?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: Avisto</title>
    <description>The latest articles on DEV Community by Avisto (@avisto).</description>
    <link>https://dev.to/avisto</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3976145%2F7ba96ea2-0777-4a66-91ce-02d81934dcca.png</url>
      <title>DEV Community: Avisto</title>
      <link>https://dev.to/avisto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avisto"/>
    <language>en</language>
    <item>
      <title>Bruno from A to Z: Variables, Scripting, and E2E Tests</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Wed, 17 Jun 2026 07:17:32 +0000</pubDate>
      <link>https://dev.to/avisto/bruno-from-a-to-z-variables-scripting-and-e2e-tests-49ml</link>
      <guid>https://dev.to/avisto/bruno-from-a-to-z-variables-scripting-and-e2e-tests-49ml</guid>
      <description>&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%2F2q28iztbfn68al1czol6.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%2F2q28iztbfn68al1czol6.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the previous article, we compared four API clients and concluded that Bruno was the most coherent choice for a development team in 2026. Now that we know why, let’s see how.&lt;/p&gt;

&lt;p&gt;This article covers the essential features: organizing collections, managing variables, scripting, and e2e tests. It’s aimed at developers just starting with Bruno or who want to understand what it really offers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importing or Organizing a New Collection
&lt;/h3&gt;

&lt;p&gt;Bruno supports importing from Postman, Insomnia, and OpenAPI. This is particularly useful when migrating from another tool. Existing collections aren’t lost. In this article, we’re starting from a blank collection. The .bru format developed by Bruno won’t be covered here since the team now recommends the YAML format implementing the OpenAPI Specification.&lt;/p&gt;

&lt;p&gt;Just like in an IDE, the collection tree shown in Bruno represents the actual file tree on disk. Each request is represented by an independent file. With a clear structure and a readable format, Bruno lets you track file differences with Git naturally and without friction.&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%2Fasnn3389x4j636jtv7d1.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%2Fasnn3389x4j636jtv7d1.png" width="800" height="803"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;bruno file system&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The file tree matches the request tree in Bruno. With a simpler format and a clear structure, Bruno makes it easier to track file differences with Git.&lt;/p&gt;
&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;

&lt;p&gt;Before talking about scripting and tests, you need to understand how Bruno handles variables. It’s the foundation everything else rests on.&lt;/p&gt;

&lt;p&gt;Like all API clients, Bruno offers different variable types:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt variables:&lt;/strong&gt; They force the user to enter a value before the request is sent. Useful for sensitive parameters or parameters that vary by context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request / Folder / Collection variables:&lt;/strong&gt; These are equivalent to traditional variables found in all API clients. They’re defined statically and persist in files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment variables:&lt;/strong&gt; Just like in a regular project, Bruno supports variables described in a .env file that’s easily swappable depending on the target environment (local, staging, production).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime variables:&lt;/strong&gt; Variables assigned using scripts at execution time. They’re not directly modifiable by the user in the interface. They’re useful for automating tasks between requests, like propagating an authentication token. We’ll cover this in detail in the next section.&lt;/p&gt;

&lt;p&gt;Here’s an image showing the variable usage priorities.&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%2Fv8us64pl1n2h6h1l40cx.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%2Fv8us64pl1n2h6h1l40cx.png" width="571" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bruno applies the same principle to variables as it does to requests: they’re stored in .bru or .env files, which keeps the tool Git-friendly. Nothing lives solely in memory or in an opaque cloud state.&lt;/p&gt;
&lt;h3&gt;
  
  
  Automating with Pre-request and Post-response Scripts
&lt;/h3&gt;

&lt;p&gt;Once variables are in place, scripting allows you to manipulate them automatically between requests. This is where Bruno really distinguishes itself from Postman.&lt;/p&gt;

&lt;p&gt;Bruno lets you write scripts to modify variables or requests using JavaScript syntax. We’ll look at two concrete cases: an authentication request and a project creation request.&lt;/p&gt;
&lt;h3&gt;
  
  
  Storing the jwt_token Using a Post-script in a Variable
&lt;/h3&gt;

&lt;p&gt;For this example, we have the following request.&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%2Fv3ujbg0k4toq7nkj5l4v.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%2Fv3ujbg0k4toq7nkj5l4v.png" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Postman:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here, you overwrite the jwt_token variable value in the collection. The previous value, which might have been set manually by a developer, is lost without warning.&lt;/p&gt;

&lt;p&gt;javascript&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;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jwt_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&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 Bruno:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here, you only modify the runtime value of the jwt_token variable. The old static value is preserved in the file. The runtime value is lost when you close the application, which is precisely the expected behavior for a temporary token.&lt;/p&gt;

&lt;p&gt;javascript&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;bru&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jwt_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction isn’t trivial. In Postman, a script can silently overwrite a value someone had set for their tests. In Bruno, the two coexist without stepping on each other.&lt;/p&gt;

&lt;p&gt;Bruno also offers a way to simplify this assignment using a visual post-response variables interface, which lets you replace the script above with a declarative configuration directly in the editor without writing a single line of 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%2F8qzp5bworwgd51r6t5x6.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%2F8qzp5bworwgd51r6t5x6.png" width="800" height="586"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Variable Post Response to replace script&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Post Response Variables to replace a script. Next, you just need to use this variable in your authentication system after executing the previous request.&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%2F94xt6cxh8krym3i37xt4.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%2F94xt6cxh8krym3i37xt4.png" width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Using a Runtime Variable as a Default Value for a Request
&lt;/h3&gt;

&lt;p&gt;Here’s a more advanced use case with two requests: a POST and a GET on a project resource.&lt;/p&gt;

&lt;p&gt;Here’s the project POST that returns the newly generated project ID.&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%2Fqk1siv7jsv5y5s3xuqpu.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%2Fqk1siv7jsv5y5s3xuqpu.png" width="799" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Project POST Request. The POST creates a new project and returns its identifier. In post-response, we store this identifier in the autoProjectId runtime variable. This variable can then serve as the default value for the GET project by identifier: a pre-request script checks if a projectId variable exists in the collection. If not, it automatically injects autoProjectId as a fallback value.&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%2Flkmm34w1mx69ysxgdpol.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%2Flkmm34w1mx69ysxgdpol.png" width="798" height="198"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;GET project with pre-request&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Project GET Request with pre-request to assign the projectId. This mechanism is particularly useful when creating entities that require many parameters. It lets a developer run a sequence of requests without manually entering each generated identifier. Bruno takes care of it between steps. Here’s an example of a pre-request for project creation.&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%2Fadbdh6po058w84cg3e47.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%2Fadbdh6po058w84cg3e47.png" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see pre-request variables, and if they don’t have a value, we build a default value.&lt;/p&gt;
&lt;h3&gt;
  
  
  E2E Tests
&lt;/h3&gt;

&lt;p&gt;Scripting covers variable automation. Tests, meanwhile, serve to validate that endpoints behave as expected.&lt;/p&gt;

&lt;p&gt;Like Postman, Bruno lets you set up collections of endpoint tests. During execution, test reports can be generated to keep a record of results.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to Write Them?
&lt;/h3&gt;

&lt;p&gt;Bruno offers two approaches for writing tests, depending on the complexity of the case you’re covering.&lt;/p&gt;

&lt;p&gt;The assertions table for simple, declarative checks without code. JavaScript scripts via Chai.js for more complex cases that require logic, loops, or conditions.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Test Script
&lt;/h3&gt;

&lt;p&gt;Bruno sets up test scripts in JavaScript, accessible in a dedicated “Tests” section. The assertion system is based on Chai.js, which makes tests readable by the widest audience, including developers unfamiliar with testing tools.&lt;/p&gt;

&lt;p&gt;javascript&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should have 'bonjour' as response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bonjour&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more complex cases involving loops or conditions:&lt;/p&gt;

&lt;p&gt;javascript&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should validate user items by name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Benjamin&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Foo&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;h3&gt;
  
  
  The Assertions Table
&lt;/h3&gt;

&lt;p&gt;To simplify writing the most common cases, Bruno provides an “Asserts” section with a declarative table interface that doesn’t require writing JavaScript. The first test above can be transcribed in just a few clicks.&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%2Faqsiivu5bkqxgbsrwz0d.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%2Faqsiivu5bkqxgbsrwz0d.png" width="798" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, the second test involving a forEach loop isn’t achievable through assertions. It’s a good indicator for knowing when to switch to a full script: as soon as you need to iterate or condition checks, the script becomes necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: Response Query
&lt;/h3&gt;

&lt;p&gt;A complementary tool deserves mention. It lets you extract elements from a response the same way you’d do with lodash. For example, to extract all values of the owner attribute in the following response:&lt;/p&gt;

&lt;p&gt;json&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"item_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Benjamin"&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;"item_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Olivier"&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;Just call res('..owner') to get ["Benjamin", "Olivier"], then verify that the expected value is in the list. This operator works in both assertions tables and test scripts.&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;res('..owner') = ["Benjamin", "Olivier"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, it’s just a matter of verifying what you want, here whether “Benjamin” is actually included in the list or not.&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%2Fsfmnsprj20mqzqaxyse1.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%2Fsfmnsprj20mqzqaxyse1.png" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating Test Reports
&lt;/h3&gt;

&lt;p&gt;Two options are available for generating reports: via the interface in Bruno’s paid version, or via the command line thanks to the Bruno CLI, accessible for free.&lt;/p&gt;

&lt;p&gt;To run them, just execute the following command:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bru run your-folder-who-contain-test &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;--reporter-html&lt;/span&gt; ./result.html &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./environments/YourEnvironmentIfNeeded.br
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated report is a standalone HTML file, readable in any browser, that summarizes all tests executed with their status. It’s a deliverable that can be directly shared with a QA team or client without additional infrastructure.&lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://docs.usebruno.com/bru-cli/overview" rel="noopener noreferrer"&gt;https://docs.usebruno.com/bru-cli/overview&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Bruno lives up to its promises. Behind a sober interface lies a tool that made clear technical choices and sticks to them.&lt;/p&gt;

&lt;p&gt;Git integration isn’t a marketing argument: it concretely changes how you work as a team. Being able to review a request like you review code, seeing exactly what changed between two versions of an endpoint, including collections in the same pull requests as the code that tests them. These are real gains that accumulate over a project’s lifetime.&lt;/p&gt;

&lt;p&gt;The assertions and response query lower the barrier to entry for writing endpoint tests. You don’t need to master Chai.js to cover 80 percent of common cases. And when cases become complex, the full JavaScript script steps in without friction.&lt;/p&gt;

&lt;p&gt;Finally, the free CLI and HTML reports are a major selling point. Generating a readable test report without paying for a subscription, integrating it into a CI/CD pipeline, sharing it with a team. That’s exactly what you expect from a developer-oriented tool.&lt;/p&gt;

&lt;p&gt;Bruno doesn’t revolutionize the field. But it cleanly solves frictions that many had accepted as normal.&lt;/p&gt;

</description>
      <category>restapi</category>
      <category>softwaredevelopment</category>
      <category>bruno</category>
    </item>
    <item>
      <title>Postman nous a perdus en route, voici ce qu’on utilise en 2026</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Tue, 16 Jun 2026 12:02:51 +0000</pubDate>
      <link>https://dev.to/avisto/postman-nous-a-perdus-en-route-voici-ce-quon-utilise-en-2026-9l6</link>
      <guid>https://dev.to/avisto/postman-nous-a-perdus-en-route-voici-ce-quon-utilise-en-2026-9l6</guid>
      <description>&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%2Fi9y7baprqrssvy9rxpbr.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%2Fi9y7baprqrssvy9rxpbr.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; L’API Client &lt;strong&gt;Bruno&lt;/strong&gt; est le plus pertinent.&lt;/p&gt;

&lt;p&gt;Dans cet article, nous allons rapidement comparer les 4 API Clients les plus “populaire”. L’idée n’est pas de faire un benchmark exhaustif mais plutôt de pointer les éléments qui ont motivé notre changements. Et dans la suite de cet article on exploras les fonctionnalités du meilleurs.&lt;/p&gt;

&lt;p&gt;Voici les 4 candidats ( dans leur ordre de création) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Postman&lt;/strong&gt; , l’usine la plus utilisé&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insomnia&lt;/strong&gt; , le concurrant de Postman&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bruno&lt;/strong&gt; , Open Source &amp;amp; Git Oriented&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yaak&lt;/strong&gt; , Open Source successeur de insomnia&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  L’usine, Postman
&lt;/h4&gt;

&lt;p&gt;Fondé en 2012 par Abhinav Asthana, Ankit Sobti et Abhijit Kane, d’abord comme extension Chrome avant de devenir une application standalone. Postman, c’est l’outil que tout le monde connaît, le précurseur, et pendant des années un allié incroyable sur les projets.&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%2Fe9mbusibaqs1wcnlpwbf.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%2Fe9mbusibaqs1wcnlpwbf.png" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;exemple d’interface postman&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On retrouve un écosystème particulièrement complet : une extension VSCode, un CLI via Newman, une large communauté et une documentation solide. La synchronisation cloud permet de travailler en équipe directement dans le projet, et les possibilités de scripting ajoutent une vraie flexibilité pour automatiser les tests.&lt;/p&gt;

&lt;p&gt;Côté points négatifs, l’outil montre quelques limites structurelles. Les WebSockets sont isolés dans une collection à part, ce qui nuit à la cohérence. Le CLI Postman nécessite une clé API, et Newman, bien que fonctionnel, reste moins bien intégré que le CLI natif de Postman.&lt;/p&gt;

&lt;p&gt;Mais l’éléphant dans la pièce, c’est le mode connecté obligatoire et tout ce qui vient avec. Le client est lent, la synchronisation n’est pas toujours correcte, et les performances se sont dégradées avec le temps. Bonus même sans abonnement payant, vos collections finissent sur le cloud mais sans synchronisation automatique avec votre dépôt Git.&lt;/p&gt;

&lt;h4&gt;
  
  
  Le challenger, Insomnia
&lt;/h4&gt;

&lt;p&gt;Créé en 2016 par Gregory Schier, puis racheté par Kong en 2019. L’objectif initial était de proposer une alternative plus simple et plus légère à Postman. Depuis le rachat, la philosophie a évolué vers l’intégration dans l’écosystème Kong, avec un focus sur le design et le test d’API dans des environnements plus orientés entreprise.&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%2Fe3by3btc8o0mkmktu1n1.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%2Fe3by3btc8o0mkmktu1n1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Insomnia dispose de plugins qui étendent ses fonctionnalités et d’un CLI intégré, ce qui couvre les besoins essentiels pour automatiser les tests.&lt;/p&gt;

&lt;p&gt;Les limites sont cependant plus nombreuses. L’interface est moins lisible que ses concurrents, et la documentation manque parfois de clarté. Comme Postman, les collections sont sauvegardées dans un seul fichier et c’est là que le problème Git se pose concrètement :&lt;/p&gt;

&lt;p&gt;Tant que les modifications se font en ajout pur, ça passe. Mais dès qu’on commence à avoir des conflits entre des ajouts et des modifications au milieu du fichier, ça devient vite ingérable. On parle d’un fichier JSON potentiellement de plusieurs milliers de lignes, sans structure lisible par un humain, où résoudre un conflit Git relève plus de la chirurgie que du merge classique. Le moindre accolade mal placée et la collection est corrompue.&lt;/p&gt;

&lt;h4&gt;
  
  
  L’outsider Bruno
&lt;/h4&gt;

&lt;p&gt;Lancé en 2022 par Anoop M D. La philosophie est explicitement anti-cloud et pro-local : les collections vivent dans le système de fichiers, en texte brut, versionnables nativement avec Git. L’objectif est de redonner le contrôle aux développeurs, sans compte obligatoire ni synchronisation cloud forcée.&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%2Fyivp8ovoo98ebnz6qztk.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%2Fyivp8ovoo98ebnz6qztk.png" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bruno se distingue nettement par son approche orientée fichiers : chaque requête est sauvegardée dans un fichier individuel nommé d’après elle, ce qui rend les diffs Git lisibles et le versioning naturel. Le Git diff est d’ailleurs disponible gratuitement, avec commit, push et pull en version payante. L’outil propose également un CLI, une extension VSCode, du scripting avec un système simplifié d’assertions et de variables post-requêtes, les WebSockets dans la même collection, et des rapports de tests en HTML accessibles gratuitement via le CLI.&lt;/p&gt;

&lt;p&gt;Le principal point de friction est la courbe d’apprentissage du scripting, moins immédiate que sur des outils plus établis. Les variables runtime ne sont par ailleurs pas overridables, ce qui peut bloquer certains workflows avancés.&lt;/p&gt;

&lt;h4&gt;
  
  
  Le petit nouveau, Yaak
&lt;/h4&gt;

&lt;p&gt;Créé aux alentours de 2023 par Gregory Schier, le même créateur qu’ &lt;strong&gt;Insomnia&lt;/strong&gt; , qu’il a quitté après le rachat par Kong. Yaak ressemble à une réponse directe à ce qu’Insomnia est devenu. La philosophie affichée est le minimalisme et la simplicité, avec un stockage local et une intégration Git native, dans la même veine que Bruno mais avec une approche encore plus épurée.&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%2Fnp32xv3sgxs6zzd9pbf0.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%2Fnp32xv3sgxs6zzd9pbf0.png" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yaak mise sur une interface minimaliste et un affichage des requêtes ligne par ligne, ce qui le rend agréable à utiliser au quotidien. Comme Bruno, il sauvegarde chaque requête dans un fichier individuel et intègre nativement Git diff, commit, push et pull. Les WebSockets sont dans la même collection, et des plugins sont disponibles pour étendre les fonctionnalités.&lt;/p&gt;

&lt;p&gt;Les limites sont cependant significatives. L’outil est encore jeune et ça se ressent : pas de scripting, pas de tests, pas de CLI pour exécuter les requêtes. Les noms de fichiers sont générés aléatoirement, ce qui nuit à la lisibilité sur un dépôt Git. Et le prix peut freiner, surtout face à des alternatives plus matures qui offrent davantage de fonctionnalités pour un coût similaire.&lt;/p&gt;

&lt;h4&gt;
  
  
  Postman on est sur qu’on abandonne ?
&lt;/h4&gt;

&lt;p&gt;Il faut en reparler un peu. Pendant longtemps, c’était la référence incontestée. L’outil que tout le monde installait sans se poser de questions. Mais Postman est un exemple assez emblématique d’enshittification : un produit qui commence par servir ses utilisateurs, puis qui les utilise pour servir ses investisseurs. La connexion obligatoire, le cloud forcé, les fonctionnalités clés progressivement glissées derrière un abonnement, une interface qui grossit à chaque version sans que l’expérience de base s’améliore. Ce n’est pas un hasard si Bruno et Yaak ont émergé exactement dans cette période. Ils sont en partie une réponse directe à cette trajectoire.&lt;/p&gt;

&lt;h4&gt;
  
  
  Le grand vainqueur en 2026
&lt;/h4&gt;

&lt;p&gt;Insomnia, de son côté, a suivi une trajectoire similaire à Postman après son rachat par Kong en 2019. Le fait que Gregory Schier son créateur original, soit reparti de zéro pour construire Yaak en dit long sur ce que le produit est devenu.&lt;/p&gt;

&lt;p&gt;Et enfin Yaak, partage la même philosophie que Bruno, mais reste trop jeune pour être recommandé en production. L’absence de scripting, de tests et de CLI sont des manques trop importants pour une équipe qui a besoin d’automatiser et de fiabiliser ses workflows.&lt;/p&gt;

&lt;p&gt;Il nous reste donc BRUNO !&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AYFnj98GLkhus4Luq" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AYFnj98GLkhus4Luq" width="1024" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ce qui fait la force de Bruno, c’est sa philosophie : un outil open source, local par défaut, qui respecte votre workflow plutôt que de vous imposer le sien. Pas de compte, pas de cloud obligatoire, pas de fonctionnalités retirées à la prochaine mise à jour payante. Juste un outil qui fait ce qu’on lui demande.&lt;/p&gt;

&lt;p&gt;Si Bruno vous intéresse, le prochain article entre dans le détail : configuration, scripting, intégration Git et workflow au quotidien.&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>restapi</category>
    </item>
    <item>
      <title>Postman Lost Us Along the Way. Here’s What We Use in 2026</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Fri, 12 Jun 2026 08:45:38 +0000</pubDate>
      <link>https://dev.to/avisto/postman-lost-us-along-the-way-heres-what-we-use-in-2026-lh1</link>
      <guid>https://dev.to/avisto/postman-lost-us-along-the-way-heres-what-we-use-in-2026-lh1</guid>
      <description>&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%2Fi9y7baprqrssvy9rxpbr.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%2Fi9y7baprqrssvy9rxpbr.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Bruno is the most relevant API client.&lt;/p&gt;

&lt;p&gt;We’re going to quickly compare the four most “popular” API clients. The goal isn’t an exhaustive benchmark but rather to highlight what drove our shift. And later in the article, we’ll explore the features that make the winner stand out.&lt;/p&gt;

&lt;p&gt;Here are the four candidates (in order of creation):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Postman, the factory everyone uses&lt;/li&gt;
&lt;li&gt;Insomnia, Postman’s challenger&lt;/li&gt;
&lt;li&gt;Bruno, open source and Git-oriented&lt;/li&gt;
&lt;li&gt;Yaak, open source successor to Insomnia&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Factory: Postman
&lt;/h3&gt;

&lt;p&gt;Founded in 2012 by Abhinav Asthana, Ankit Sobti, and Abhijit Kane, starting as a Chrome extension before becoming a standalone application. Postman is the tool everyone knows, the pioneer, and for years it was an incredible ally on projects.&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%2Fe9mbusibaqs1wcnlpwbf.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%2Fe9mbusibaqs1wcnlpwbf.png" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;postman interface&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You’ll find a particularly complete ecosystem: a VSCode extension, a CLI through Newman, a large community, and solid documentation. Cloud synchronization lets teams work directly in the project, and scripting capabilities add real flexibility for automating tests.&lt;/p&gt;

&lt;p&gt;On the negative side, the tool shows some structural limitations. WebSockets are isolated in a separate collection, which hurts consistency. The Postman CLI requires an API key, and Newman, while functional, is less well integrated than Postman’s native CLI.&lt;/p&gt;

&lt;p&gt;But the elephant in the room is the mandatory connected mode and everything that comes with it. The client is slow, synchronization isn’t always reliable, and performance has degraded over time. And even without a paid subscription, your collections end up in the cloud without automatic synchronization with your Git repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenger: Insomnia
&lt;/h3&gt;

&lt;p&gt;Created in 2016 by Gregory Schier, then acquired by Kong in 2019. The initial goal was to offer a simpler, lighter alternative to Postman. Since the acquisition, the philosophy has shifted toward integration into the Kong ecosystem, with a focus on API design and testing in more enterprise-oriented environments.&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%2Fe3by3btc8o0mkmktu1n1.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%2Fe3by3btc8o0mkmktu1n1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;insomnia interface&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Insomnia has plugins that extend its functionality and an integrated CLI, which covers the basic needs for automating tests.&lt;/p&gt;

&lt;p&gt;The limitations are more numerous, though. The interface is less readable than its competitors, and documentation sometimes lacks clarity. Like Postman, collections are saved in a single file, and this is where the Git problem becomes concrete:&lt;/p&gt;

&lt;p&gt;As long as changes are purely additive, it works. But once you start having conflicts between additions and modifications in the middle of the file, it quickly becomes unmanageable. We’re talking about a JSON file potentially thousands of lines long, with no human-readable structure, where resolving a Git conflict is more like surgery than a standard merge. A single misplaced brace and the collection is corrupted.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Outsider: Bruno
&lt;/h3&gt;

&lt;p&gt;Launched in 2022 by Anoop M D. The philosophy is explicitly anti-cloud and pro-local: collections live in the filesystem, in plain text, natively versionable with Git. The goal is to give control back to developers, without mandatory accounts or forced cloud synchronization.&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%2Fyivp8ovoo98ebnz6qztk.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%2Fyivp8ovoo98ebnz6qztk.png" width="800" height="521"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;bruno interface&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bruno stands out clearly with its file-oriented approach: each request is saved in an individual file named after it, making Git diffs readable and versioning natural. Git diff is available for free, with commit, push, and pull in the paid version. The tool also offers a CLI, a VSCode extension, scripting with a simplified system of assertions and post-request variables, WebSockets in the same collection, and HTML test reports accessible for free via the CLI.&lt;/p&gt;

&lt;p&gt;The main friction point is the learning curve for scripting, less immediate than on more established tools. Runtime variables aren’t overridable either, which can block certain advanced workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  The New Kid: Yaak
&lt;/h3&gt;

&lt;p&gt;Created around 2023 by Gregory Schier, the same creator as Insomnia, whom he left after the Kong acquisition. Yaak looks like a direct response to what Insomnia became. The stated philosophy is minimalism and simplicity, with local storage and native Git integration, in the same vein as Bruno but with an even more stripped-down approach.&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%2Fnp32xv3sgxs6zzd9pbf0.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%2Fnp32xv3sgxs6zzd9pbf0.png" width="800" height="469"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;yaak interface&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yaak focuses on a minimalist interface and line-by-line request display, which makes it pleasant to use daily. Like Bruno, it saves each request in an individual file and natively integrates Git diff, commit, push, and pull. WebSockets are in the same collection, and plugins are available to extend functionality.&lt;/p&gt;

&lt;p&gt;The limitations, however, are significant. The tool is still young and it shows: no scripting, no tests, no CLI for executing requests. Filenames are randomly generated, which hurts readability in a Git repository. And the pricing can be a barrier, especially against more mature alternatives that offer more features at a similar cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are We Really Abandoning Postman?
&lt;/h3&gt;

&lt;p&gt;We should revisit this for a moment. For a long time, it was the undisputed reference. The tool everyone installed without question. But Postman is a pretty emblematic example of enshittification: a product that starts by serving its users, then uses them to serve its investors. Mandatory connection, forced cloud, key features gradually locked behind a subscription, an interface that grows with each version without the baseline experience improving. It’s no accident that Bruno and Yaak emerged precisely during this period. They’re partly a direct response to this trajectory.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Big Winner in 2026
&lt;/h3&gt;

&lt;p&gt;Insomnia, for its part, has followed a similar trajectory to Postman after its Kong acquisition in 2019. The fact that Gregory Schier, its original creator, started from scratch to build Yaak says a lot about what the product became.&lt;/p&gt;

&lt;p&gt;And finally, Yaak shares the same philosophy as Bruno but remains too immature to recommend in production. The absence of scripting, tests, and CLI are too significant gaps for a team that needs to automate and secure its workflows.&lt;/p&gt;

&lt;p&gt;That leaves us with Bruno.&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AYFnj98GLkhus4Luq" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AYFnj98GLkhus4Luq" width="1024" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes Bruno Strong
&lt;/h3&gt;

&lt;p&gt;Bruno’s strength lies in its philosophy: an open source tool, local by default, that respects your workflow rather than imposing its own. No account, no forced cloud, no features stripped away in the next paid update. Just a tool that does what you ask of it.&lt;/p&gt;

&lt;p&gt;If Bruno interests you, the next article goes into the details: configuration, scripting, Git integration, and daily workflow.&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>restapi</category>
    </item>
    <item>
      <title>CSS: Why px and not cm?</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Wed, 10 Jun 2026 13:44:04 +0000</pubDate>
      <link>https://dev.to/avisto/css-why-px-and-not-cm-1f1p</link>
      <guid>https://dev.to/avisto/css-why-px-and-not-cm-1f1p</guid>
      <description>&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%2Fobakk91e20og7xemikir.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%2Fobakk91e20og7xemikir.png" alt="image illustration" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re wondering why a guide on CSS units is even worth your time, then you’re in the right place (AViSTO 🚀).&lt;/p&gt;

&lt;p&gt;When we think about CSS, we tend to imagine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That it’s unloved! Of the HTML / CSS / JavaScript trio, it’s the one that gets the least attention.&lt;/li&gt;
&lt;li&gt;That we refactor it (too) regularly — often blindly, with copy-paste without real understanding being an unfortunately common practice.&lt;/li&gt;
&lt;li&gt;That we underestimate it: its full capabilities are very rarely known, and even less often exploited.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll focus on practices drawn from the development of complex web applications (industrial and SaaS). Static sites often have logic or needs that are too simple, and don’t require the same level of mastery over CSS units.&lt;/p&gt;

&lt;p&gt;At the heart of this disinterest, units are probably one of the most underestimated issues today. On this topic, we frequently observe a mix of lack of reflection and outdated ideas that have been recycled for many years.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If it looks fine on my screen, it must be fine! FALSE&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So why not dust all of this off and take a tour of what’s out there, to understand why this seemingly basic topic is actually a genuine cross-disciplinary battleground involving designers, developers, product owners, and the associated costs?&lt;/p&gt;

&lt;h2&gt;
  
  
  📏 Overview of the Different Units
&lt;/h2&gt;

&lt;p&gt;In CSS, we find several families of length units [1]:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Absolute units.&lt;/li&gt;
&lt;li&gt;Relative units.&lt;/li&gt;
&lt;li&gt;Percentages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each family contains several units and serves a specific purpose.&lt;/p&gt;

&lt;p&gt;It’s important to understand that length units must be able to address the needs of multiple media — not just computer screens and smartphones/tablets: printing is also particularly concerned [4].&lt;/p&gt;

&lt;h3&gt;
  
  
  Absolute Units
&lt;/h3&gt;

&lt;p&gt;They are considered constants fixed values that, for a given screen, do not change.&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%2Ffsqaxhji9w99wh9kejwn.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%2Ffsqaxhji9w99wh9kejwn.png" alt="unit table" width="720" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Typically, reading that table, you might think that most of these don’t apply to the web. And you might also wonder: “So, why use pixels and not centimetres?”&lt;/p&gt;

&lt;p&gt;Part of the answer lies in the definition of a pixel, according to the W3C (World Wide Web Consortium):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pixel is a unit of length that corresponds approximately to the width or height of a single dot, which can be comfortably seen by the human eye without effort, while also being as small as possible.&lt;/p&gt;
&lt;/blockquote&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%2Fel6idabj0712md4l46st.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%2Fel6idabj0712md4l46st.png" alt="unit table" width="327" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The challenge is therefore to have a unit that is as precise as possible despite the infinite variety of devices on which it will be displayed. We won’t go into the technical solution described by the W3C here (it would be far too long for this document), but you’ll find relevant documents in the “Sources” section at the bottom of the page [3][7].&lt;/p&gt;

&lt;h4&gt;
  
  
  About “pixels versus centimètres”
&lt;/h4&gt;

&lt;p&gt;We therefore understand that even absolute units depend on the resolution of the screen on which they’re displayed. It is inevitable that there will be a difference between the value stated in your code and what you might be tempted to measure with a ruler, for example.&lt;/p&gt;

&lt;p&gt;To avoid confusion, the most obvious option for digital is to go with the pixel. The exact size of a pixel will depend on the resolution of the device on which it is displayed [15].&lt;/p&gt;

&lt;h3&gt;
  
  
  Font-Relative Units
&lt;/h3&gt;

&lt;p&gt;As the name suggests, these are relative to the font size or a character within a given font:&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%2Fllwy2nwypxdam5jsaa7f.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%2Fllwy2nwypxdam5jsaa7f.png" alt="unit table" width="720" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may have noticed that there’s no “Name” column in our table → The reason is simple: these units are not abbreviations.&lt;/p&gt;

&lt;p&gt;The unit ex refers to the space taken by an x, so we might expect em to work similarly, but that's not the case [12]: it's a typographic unit that defines a reference space for a given font [2].&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%2F2r0ecrkci3wr7wiaz793.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%2F2r0ecrkci3wr7wiaz793.png" alt="unit table" width="670" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A metal slug. The line height, c, is the predecessor of the em [10].&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewport-Relative Units
&lt;/h3&gt;

&lt;p&gt;These lengths are relative to the viewport. The viewport corresponds to the visible display area of a device. For a web browser, it’s the content of the window, not including the rest of the interface. This concept, supported by browsers since around 2012, has greatly facilitated responsive behaviour, particularly on mobile:&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%2F3o56sg3pygragn5tytu6.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%2F3o56sg3pygragn5tytu6.png" alt="unit table" width="720" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the time, we’ll use the dynamic viewport to design interfaces suited to both desktop and mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Percentages
&lt;/h3&gt;

&lt;p&gt;Percentages (%) are interpreted relative to the value of the same property defined on the parent element. width will depend on the parent's width, while font-size will depend on the parent's font-size [3].&lt;/p&gt;

&lt;h3&gt;
  
  
  TL;DR: 99% of Cases
&lt;/h3&gt;

&lt;p&gt;There are many units out there, but in practice we only use a small fraction of them in web development.&lt;/p&gt;

&lt;p&gt;Below is a summary of the most commonly used units [6]:&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%2F3alpt8uf72py6yrsdlyc.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%2F3alpt8uf72py6yrsdlyc.png" alt="unit table" width="720" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which, honestly, takes a weight off our shoulders, doesn’t it? We go from over twenty units down to just six!&lt;/p&gt;

&lt;p&gt;That said, it’s still essential to make the right choices among those six units for almost every CSS property you use. That choice will depend on the context and the stakes of your project.&lt;/p&gt;

&lt;p&gt;The foundations are now in place. In the next article, I’ll tackle concrete cases and perhaps even a touch of architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usefull resources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Unités CSS : em, px, pt, cm, in. . . . (s. d.). &lt;a href="https://www.w3.org/Style/Examples/007/units.fr.html" rel="noopener noreferrer"&gt;https://www.w3.org/Style/Examples/007/units.fr.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wikipedia contributors. (2025, 25 mai). Quad (typography). Wikipedia. &lt;a href="https://en.m.wikipedia.org/wiki/Quad_(typography)" rel="noopener noreferrer"&gt;https://en.m.wikipedia.org/wiki/Quad_(typography)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cascading Style Sheets, level 1. (s. d.). &lt;a href="https://www.w3.org/TR/CSS1/#percentage-units" rel="noopener noreferrer"&gt;https://www.w3.org/TR/CSS1/#percentage-units&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Media types. (s. d.). &lt;a href="https://www.w3.org/TR/CSS2/media.html#q7.0" rel="noopener noreferrer"&gt;https://www.w3.org/TR/CSS2/media.html#q7.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An Introduction to Cascading Style Sheets. (s. d.). &lt;a href="https://nwalsh.com/docs/articles/css/" rel="noopener noreferrer"&gt;https://nwalsh.com/docs/articles/css/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Valeurs et unités CSS — Apprendre le développement web | MDN. (1970, 1 janvier). MDN Web Docs. &lt;a href="https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Styling_basics/Values_and_units" rel="noopener noreferrer"&gt;https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Styling_basics/Values_and_units&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reichenstein, O. (2025, 14 mai). Responsive Typography : The Basics. iA. &lt;a href="https://ia.net/topics/responsive-typography-the-basics" rel="noopener noreferrer"&gt;https://ia.net/topics/responsive-typography-the-basics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wikipedia contributors. (2025a, mai 12). Em (typography). Wikipedia. &lt;a href="https://en.m.wikipedia.org/wiki/Em_(typography)" rel="noopener noreferrer"&gt;https://en.m.wikipedia.org/wiki/Em_(typography)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Syntax and basic data types. (s. d.). &lt;a href="https://www.w3.org/TR/CSS22/syndata.html#length-units" rel="noopener noreferrer"&gt;https://www.w3.org/TR/CSS22/syndata.html#length-units&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>CSS : L’architecture minimale pour réussir son projet 🇫🇷</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Wed, 10 Jun 2026 13:43:52 +0000</pubDate>
      <link>https://dev.to/avisto/css-larchitecture-minimale-pour-reussir-son-projet-51jl</link>
      <guid>https://dev.to/avisto/css-larchitecture-minimale-pour-reussir-son-projet-51jl</guid>
      <description>&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%2Fv2w1lsrwfhxwt8v2oq7l.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%2Fv2w1lsrwfhxwt8v2oq7l.png" alt="image illustration" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maintenant que les problématiques ont été évoquées, parlons réalité terrain et solutions. Pour rappeler le contexte, nous nous intéressons ici à un projet de taille moyenne, avec une équipe de trois à quatre développeurs et aucun UX/UI designer dédié. La question devient alors : que peut-on faire pour garantir la qualité du style dans notre future application ?&lt;/p&gt;

&lt;p&gt;La loi de Gall est particulièrement intéressante lorsque l’on entame l’architecture d’un projet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On constate invariablement qu’un système complexe qui fonctionne a évolué à partir d’un système simple qui fonctionnait. Un système complexe conçu de toutes pièces ne fonctionne jamais et ne peut être retouché pour le rendre opérationnel. Il faut repartir de zéro avec un système simple qui fonctionne.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le style de votre application finira forcément par devenir un système complexe : des règles générales sur lesquelles viendront se greffer des reworks, des règles intermédiaires, puis, inévitablement, des exceptions.&lt;/p&gt;

&lt;p&gt;Notre objectif est donc de réduire la complexité de ce système à un état suffisamment simple pour pouvoir, plus tard, faire le chemin inverse sans difficulté majeure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintenir la consistance au sein du projet
&lt;/h2&gt;

&lt;p&gt;C’est parti pour un minimum d’architecture en CSS. En trois étapes : gestion des layouts, composants de niveau atomique et propriétés générales.&lt;/p&gt;

&lt;p&gt;Concernant les layouts et les composants c’est facile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nous allons utiliser un système hiérarchique ou l’on séparera les composants “page” du “reste”.&lt;/li&gt;
&lt;li&gt;Une page est responsable du layout et de la coordination des informations entre les différents composants qu’elle intègre.&lt;/li&gt;
&lt;li&gt;Un composant, quant à lui, est responsable d’un ensemble cohérent de fonctionnalités, ayant une position définie dans l’interface et une responsabilité commune.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cette logique sera facile à coupler avec le pattern Container/Presentational.&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%2Fujbxshtdzkzhnkceu63i.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%2Fujbxshtdzkzhnkceu63i.png" alt="architecture" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensuite, les composants utilisés dans les pages sont répartis en deux catégories. Cette distinction concerne plus précisément ceux qui sont réutilisés à travers plusieurs pages. Nous appliquons ici les principes de l’atomic design en créant une couche abstraite de composants atomiques.&lt;br&gt;
Become a Medium member&lt;/p&gt;

&lt;p&gt;L’objectif est de concevoir des composants simples et réutilisables, destinés à être combinés au sein d’autres composants. Ces « atomes », une fois assemblés, permettent de former des « molécules ». Par exemple : un bouton, une carte ou encore des éléments de formulaire.&lt;/p&gt;

&lt;p&gt;Dernier point important, avoir des valeurs de propriétés réutilisable de manière transverse. Elles seront définies au niveau global de l’application. Il y en a 3 qui sont indispensables à un projet :&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%2Fzuuwhen4aq1e217kwsuh.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%2Fzuuwhen4aq1e217kwsuh.png" alt="propriété css" width="720" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ces trois éléments assurent une cohérence à travers l’application. On évite ainsi que chaque développeur ait à se demander quelle typologie est pertinente. Il devient également possible d’associer facilement du sens aux propriétés.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Point d’attention si vous utilisez Tailwind CSS : c’est un outil très puissant qui augmente considérablement la DX mais si son utilisation n’est pas bien encadrée, cette approche pourra nuire à la maintenabilité du projet sur le long terme.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Assurer l’avenir et les modifications
&lt;/h3&gt;

&lt;p&gt;Sans designer, nous pouvons d’ores et déjà oublier l’idée de gérer proprement un changement de taille de police effectué par l’utilisateur. ( Abordé dans l’article précédent si vous ne l’avez pas lu)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Malheureusement, notre application ne sera pas parfaitement accessible mais il est préférable de se concentrer sur ce qui peut être fait correctement, avec les moyens à notre disposition.&lt;/li&gt;
&lt;li&gt;Pas de modification de la font size par défaut, donc pas de rem : nous utiliserons principalement l’unité px pour toutes les tailles qui ne dépendent pas du layout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Concernant les unités à utiliser voici une petite synthèse qui pourrait vous être utile :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Width et height en %/auto, afin de s’adapter à la place que laisse le layout.&lt;/li&gt;
&lt;li&gt;Margin et padding en px, puisque la taille du contenu ne change pas. Cela présente un avantage majeur : des comportements simples, faciles à ajuster par la suite et surtout lisible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si ces deux points sont respectés et que l’intégration de votre interface est réalisée minutieusement, vous ne devriez pas avoir de problème à faire scale votre projet et vos ambitions. C’est la fin de ce petit tour d’horizon sur les unités en CSS et pourquoi les maitriser est un véritable enjeux. Deux mots-clés à avoir en tête : consistance et maintenabilité.&lt;/p&gt;

&lt;p&gt;Pour relativiser un peu, la majorité de l’environnement web utilise donc souvent sans le savoir une unité basée sur un multiple de 16 px : le rem. Pourtant, cela n’empêche personne de continuer à développer : c’est bien la preuve qu’il est tout à fait possible de concevoir d’excellents produits en utilisant uniquement des pixels.&lt;/p&gt;

&lt;p&gt;Et pour vous rassurer sur l’accessibilité, à titre d’exemple, l’outil de suivi de production Jira ne permet pas de modifier la taille de la police de son interface. Même les plus grandes plateformes ne se lancent pas toujours dans ce type de conception. En espérant vous avoir rendu le CSS un peu plus agréables.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Spoiler: le prochain article expliquera ce qu’est un composant et pourquoi c’est magique. À la semaine prochaine !&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>CSS : Pourquoi faut-il maîtriser ses unités ? 🇫🇷</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Wed, 10 Jun 2026 13:43:40 +0000</pubDate>
      <link>https://dev.to/avisto/css-pourquoi-faut-il-maitriser-ses-unites--2iaf</link>
      <guid>https://dev.to/avisto/css-pourquoi-faut-il-maitriser-ses-unites--2iaf</guid>
      <description>&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%2Ff8hw8kpk038702p13nms.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%2Ff8hw8kpk038702p13nms.png" alt="image illustration" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dans l’article précédent, nous avons passé en revue la liste des unités CSS et leurs particularités. Mais au-delà de la théorie, une question persiste : quels sont les véritables bénéfices ? Pourquoi se compliquer la vie avec des px, des em ou des pourcentages, quand on pourrait simplement utiliser des rem de manière transversale ?&lt;/p&gt;

&lt;h3&gt;
  
  
  ❔Raison #1 : Représenter une réalité de la manière la plus exacte possible
&lt;/h3&gt;

&lt;p&gt;Potentiellement le plus évident, mais aussi le plus difficile à observer : le travail des développeurs web ne peut pas s’apparenter uniquement à écrire frénétiquement des lignes de codes. Il s’agit surtout de décrire une réalité avec un langage. Chaque ligne de code représente un comportement de notre interface : dis autrement, chaque propriété utilisée, surtout en CSS, décrit un aspect de cette réalité.&lt;/p&gt;

&lt;p&gt;Voici un exemple pour illustrer notre propos. Il nous servira de fil rouge tout au long de cet article :&lt;/p&gt;

&lt;p&gt;Ci-dessous, deux boutons :&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%2Fq5xjdho90gljrtocpzfg.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%2Fq5xjdho90gljrtocpzfg.png" alt="buttons" width="545" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un bleu,&lt;/li&gt;
&lt;li&gt;Un rose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si vous mettez de côté leur différence de couleur, ce sont les mêmes.&lt;br&gt;
Pourtant, taille de l’écran mise à part, voici comment leur taille est expliquée :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bouton bleu a une hauteur de 48px et du padding latéral de 16px&lt;/li&gt;
&lt;li&gt;Bouton rose a une hauteur et une largeur de 100% celle du parent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;→ Même résultat, et pourtant les intentions sont très différentes.&lt;br&gt;
Pour le plaisir, ajoutons une autre implémentation :&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%2Fcbqnshzy8i5mu5vpag5o.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%2Fcbqnshzy8i5mu5vpag5o.png" alt="buttons" width="525" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vous voulez essayer de deviner cette proposition ? Voici ce que nous obtenons :&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bouton vert à une hauteur et une largeur ajustées à son contenu, avec un padding de 1rem&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Cette petite démonstration illustre bien la raison pour laquelle il existe de nombreuses unités.&lt;br&gt;
En effet, ces dernières permettent d’exprimer un besoin de différentes manières :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Le bouton bleu&lt;/strong&gt; convient pour un design simple, s’il n’est pas nécessaire de le réutiliser par la suite ou de le placer dans un layout complexe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Le bouton vert&lt;/strong&gt; répond à une tentative de gestion de changement de taille de police.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Le bouton rose&lt;/strong&gt;, quant à lui, est conçu pour être utilisé dans des grilles ou des conteneurs flexibles (grid ou flexbox), car c’est le layout qui déterminera sa taille finale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sur une maquette classique, l’apparence de ces boutons sera identique. Si leur taille varie selon la page consultée, la maquette indiquera simplement que la taille a changé, sans en préciser la raison.&lt;/p&gt;

&lt;p&gt;🧙‍♂️ Ce qu’il faut retenir, c’est que le choix des unités ne doit pas être guidé uniquement par des considérations techniques, mais aussi par des choix de design et de produit.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❔Raison #2 : Développer de (vraies) interfaces responsives
&lt;/h3&gt;

&lt;p&gt;Il y a-t-il une relation de gain direct entre utiliser des unités relatives à la police et réaliser une interface responsive et robuste ? → FAUX&lt;/p&gt;

&lt;p&gt;Confusions les plus courantes qui alimentent cette croyance :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La consonance forte entre les termes “responsive” et “relative”.&lt;/li&gt;
&lt;li&gt;Le coté “dynamique” : une “unité dynamique” qui pourrait faire penser qu’elle est intéressante pour construire des “pages dynamiques”.&lt;/li&gt;
&lt;li&gt;Le fait que terme “relatif” évoque l’adaptabilité et donc, la responsivité.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L’erreur est à la fois sémantique et technique : on confond souvent une unité proportionnelle avec une capacité de mise en page adaptative.&lt;/p&gt;

&lt;p&gt;On pourrait en imaginer beaucoup d’autres, mais cela montre bien que le sujet est un terrain fertile pour les fausses bonnes idées.&lt;/p&gt;

&lt;p&gt;Il est important de garder en tête que la responsivité d’une page se joue principalement sur ce qu’on appelle le layout [7]. À savoir, l’espace accordé à chaque élément sur une page. Une interface responsive est une interface qui possède des “adaptative layouts”.&lt;/p&gt;

&lt;h4&gt;
  
  
  Illustration des principes d’adaptative layout
&lt;/h4&gt;

&lt;p&gt;Voici un exemple avec une interface ‘classique’, affichée sur un écran d’ordinateur :&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%2Fb715v2ust90w1yj4r3xr.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%2Fb715v2ust90w1yj4r3xr.png" alt="Big layout - Résolution Desktop" width="720" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Avec une tablette, le layout change et la présentation des éléments n’est plus la même. Ce n’est pas qu’une question de réduction de taille des éléments :&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%2Fb715v2ust90w1yj4r3xr.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%2Fb715v2ust90w1yj4r3xr.png" alt="Medium layout - Résolution Tablette" width="720" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;C’est encore plus flagrant avec une interface mobile :&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%2Fy2yfb4xif61pmsbtz16c.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%2Fy2yfb4xif61pmsbtz16c.png" alt="Small layout - Résolution Mobile" width="720" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Que se passe-t-il quand on réduit la taille du navigateur ? La taille des textes est-elle réduite ? les espaces entre les cartes sont-ils plus petits ? L’aspect est-il conservé ?&lt;/p&gt;

&lt;p&gt;Vous l’avez compris, l’adaptative layout est pensé pour maximiser la qualité de l’expérience utilisateur sur chaque device.&lt;br&gt;
Write on Medium&lt;/p&gt;

&lt;p&gt;En conséquence :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L’agencement du layout et de ses composants va être ajusté.&lt;/li&gt;
&lt;li&gt;La disposition des cartes ne sera plus la même et la barre de navigation seras remplacée par un “burger menu”, plus adapté aux smartphones et tablettes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il n’est pas question de simplement “tout réduire”.&lt;/p&gt;

&lt;p&gt;Pour créer un adaptative layout, nous utilisons des breakpoints :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ce sont des largeurs personnalisables qui déterminent le comportement de notre mise en page en fonction de la largeur de l’appareil ou de la fenêtre &lt;/li&gt;
&lt;li&gt;La plupart des librairies proposent des valeurs de référence (comme par exemple sm, md, lg, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Un conseil : Ne jamais réduire la taille des textes pour gagner de l’espace ! Oui, sur mobile, la taille de police peut se permettre d’être un peu plus petite parce que la distance entre l’écran et l’œil est plus faible. En revanche, ce choix ne doit pas être motivé par un gain de place.&lt;/p&gt;

&lt;p&gt;🧙‍♂️ Vous l’aurez compris : ce n’est pas parce que vous utilisez des unités relatives que vous développez une interface responsive. Pour avoir un design responsive, le véritable enjeux de conception se trouve au niveau des layouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❔Raison #3 : Gérer l’accessibilité
&lt;/h3&gt;

&lt;p&gt;C’est une problématique essentielle et qui, pourtant, reste souvent mal comprise lorsqu’il s’agit des unités. En grande partie pour des raisons historiques.&lt;/p&gt;

&lt;p&gt;Avant 2005, les navigateurs étaient limités techniquement : lors d’un zoom, ils n’étaient pas capables d’agrandir la taille de la police comme le reste du contenu de la page. Cela signifie qu’à cette époque, la seule méthode pour grossir un texte sur un écran était de changer la taille de la police sur l’appareil. Cette taille était alors utilisée comme taille par défaut pour l’entièreté des pages web consultées.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nous n’entrerons pas ici dans le détail du “C” de CSS, donc nous utiliserons le terme “police par défaut” plutôt que “root”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ainsi, avant 2005, le seul moyen de rendre votre site accessible au public malvoyant était d’utiliser des rem, soit l’unité de longueur relative à la taille de la police par défaut. Les font-size de votre application étaient alors modifiées en fonction du choix de l’utilisateur.&lt;/p&gt;

&lt;p&gt;Aujourd’hui, les navigateurs ont beaucoup évolué et gèrent très bien le grossissement de la police, même en unité absolue lors d’un pinch-to-zoom.&lt;br&gt;
Press enter or click to view image in full size&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%2Fhtumugwahs4fflkqednq.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%2Fhtumugwahs4fflkqednq.png" alt="Un “pinch-to-zoom” sur un écran tactile" width="720" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ Le pinch-to-zoom peut dépanner, mais il ne remplace pas une bonne gestion des tailles de texte. Certains utilisateurs modifient uniquement la taille de la police, il faut donc penser à eux pour garantir une accessibilité complète.&lt;/p&gt;

&lt;p&gt;Maintenant, supposons que nous ayons exprimé tous nos textes en rem et que la phrase suivante nous semble correcte :&lt;/p&gt;

&lt;p&gt;Les textes de mon application sont des multiples de la taille de la police par défaut&lt;/p&gt;

&lt;p&gt;Observons le comportement sur nos boutons :&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%2Frr43jw35bdhon7x8qdio.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%2Frr43jw35bdhon7x8qdio.png" alt="3 buttons" width="525" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Les 3 boutons ont une font-size de 1.2rem. Si l’utilisateur n’a pas modifié la taille de sa police, cela correspond à 1.2 * 16px = 19.2px.&lt;/p&gt;

&lt;p&gt;Nous pouvons d’ores et déjà observer que le bouton vert prend plus de place que ses voisins et que le libellé du bouton rose approche dangereusement des limites de son conteneur.&lt;/p&gt;

&lt;p&gt;Maintenant, nous allons modifier la taille de la police de notre navigateur, afin d’observer l’impact sur nos boutons. Pour ce faire, avec Google Chrome, rendez-vous ici :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paramètres &amp;gt; Apparence &amp;gt; Taille de police&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ci-dessous, nos 3 boutons avec une taille de police « très grande » :&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%2Frptqacafm0a1rrwb1afb.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%2Frptqacafm0a1rrwb1afb.png" alt="Taille de police “Très grande”" width="522" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ci-dessous, nos 3 boutons avec une taille de police « très petite » :&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%2F9dqa6snv82m38watjg67.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%2F9dqa6snv82m38watjg67.png" alt="Taille de police “Très petite”" width="527" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le bouton vert s’en sort mieux que ses confrères : ce n’est pas parfait, mais les proportions sont respectées. La raison est simple : c’est le seul à utiliser les rem comme taille de texte et d’espace.&lt;/p&gt;

&lt;p&gt;Attention toutefois : ce n’est pas magique. S’appuyer sur les rem pour gérer les changements de taille de police par défaut peut rapidement devenir un terrain glissant, voire un véritable marécage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si le bouton vert était contenu dans un layout dont les dimensions ne permettaient pas d’accueillir un composant plus gros, alors l’interface aurait dérapé.&lt;/li&gt;
&lt;li&gt;À l’inverse, si les boutons avaient des tailles définies en pixels, ce problème ne se poserait pas car leur dimension ne dépendrait pas de la taille de la police.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour réussir à combiner la responsivité et les principes d’accessibilité sur la taille des polices, il est nécessaire de recourir à une conception minutieuse, qui concerne l’ensemble de l’équipe et pas uniquement les designers. C’est aussi une question de budget, qu’il s’agisse de la taille de l’équipe ou des moyens alloués.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❔Raison #4 : Se simplifier les calculs
&lt;/h3&gt;

&lt;p&gt;Vous commencez peut-être à sentir une certaine critique envers l’utilisation des em et des rem. Vous avez raison, ils augmentent très souvent la complexité déjà bien trop présente du CSS.&lt;/p&gt;

&lt;p&gt;En effet, si votre site ne bénéficie pas d’un design qui prend en compte la modification des tailles de police via les paramètres, &lt;strong&gt;alors il n’y a aucune raison d’écrire vos valeurs en multiple de 16&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En réalité, en dehors du côté “dessin”, il y a une autre raison qui pousse les gens à ne pas aimer le CSS : sa complexité de relecture.&lt;/p&gt;

&lt;p&gt;Il est très facile d’override une propriété par mégarde, sans s’en rendre compte. La fonctionnalité de cascade est à double tranchant. Il arrive aussi que le css soit écrit dans un fichier et s’applique à un autre endroit de l’application. C’est une complexité qui n’apporte aucun plaisir ni mérite. Vraiment, relire du CSS, c’est comme défaire un gros paquet de nœuds, c’est long, compliqué et surtout pas très stimulant.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cela explique en grande partie la popularité de l’utility first avec Tailwind.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alors, ce n’est donc vraiment pas l’endroit òu ajouter de la complexité sans raison.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;La marge extérieure droite mesure ?2.3 rem? et ma bordure ?0.2rem? d’épaisseur.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Dans ce cas la, on se concentre uniquement sur le résultat en espérant qu’il n’y ait aucun bug, puisque de toute manière, on ne comprend pas ce qui a été écrit.&lt;br&gt;
On obtient alors du code “mort”, que personne n’ose modifier et supprimer puisque personne ne sait concrètement ce qu’il fait. Sur des projets qui vivent pendant plusieurs années, cela amène forcément à tout reprendre un jour.&lt;/p&gt;

&lt;p&gt;De plus, sur un projet dénué de réflexion sur le sujet, on verra des rems et em imbriqués avec des tailles de police qui changent sans que personne ne l’ai prévu. Ajouter à cela la gestion des breakpoint et plus personne n’est en mesure de prévoir la tailles des éléments affichés à l’écran. Très vite, votre projet devient une école de magie : on ne code plus, on lance des sorts !&lt;/p&gt;

&lt;p&gt;Vous l’aurez compris, connaître les unités, c’est bien, mais savoir les choisir correctement, c’est une autre mission.&lt;/p&gt;

&lt;p&gt;Le prochain article ne parlera plus d’unité, vous maitrisez maintenant. Mais nous nous intéresserons à l’architecture CSS minimum pour des projets concrets.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reichenstein, O. (2025, 14 mai). Responsive Typography : The Basics. iA. &lt;a href="https://ia.net/topics/responsive-typography-the-basics" rel="noopener noreferrer"&gt;https://ia.net/topics/responsive-typography-the-basics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Responsive Templates (Community). (s. d.). Figma. &lt;a href="https://www.figma.com/design/arM6MlY2IGy9KwsSPUJQqo/Responsive-Templates--Community-?node-id=9-297" rel="noopener noreferrer"&gt;https://www.figma.com/design/arM6MlY2IGy9KwsSPUJQqo/Responsive-Templates--Community-?node-id=9-297&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>CSS : Pourquoi les px et pas les cm ? 🇫🇷</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Wed, 10 Jun 2026 13:43:23 +0000</pubDate>
      <link>https://dev.to/avisto/css-pourquoi-les-px-et-pas-les-cm--40pi</link>
      <guid>https://dev.to/avisto/css-pourquoi-les-px-et-pas-les-cm--40pi</guid>
      <description>&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%2Fobakk91e20og7xemikir.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%2Fobakk91e20og7xemikir.png" alt="image illustration" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si vous vous demandez pourquoi un guide sur les unités CSS a un intérêt, alors vous êtes au bon endroit (AViSTO 🚀).&lt;br&gt;
Quand on pense au CSS, on imagine surtout&amp;nbsp;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qu'Il est mal aimé&amp;nbsp;! Du trio HTML / CSS / JavaScript, c'est celui qui reçoit le moins d'attention.&lt;/li&gt;
&lt;li&gt;On le refactorise (trop) régulièrement. Souvent à tâtons, et où le copier-coller sans réelle compréhension est une pratique malheureusement courante.&lt;/li&gt;
&lt;li&gt;On le sous-estime&amp;nbsp;: ses capacités complètes sont très rarement connues et encore moins exploitées.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous allons nous concentrer sur les pratiques issues du développement d'applications web complexes (industriel et SaaS). Les sites statiques on des logiques ou besoins souvent trop simples, qui ne requièrent pas le même niveau de maîtrise des unités en CSS.&lt;/p&gt;

&lt;p&gt;Au cœur de ce désintérêt, les unités sont probablement l'une des problématiques les plus sous-estimées actuellement. On observe bien souvent sur ce sujet un mélange de manque de réflexion et d'idées obsolètes mobilisées depuis de longues années.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Si c'est ok sur mon écran, c'est que c'est bon&amp;nbsp;! FAUX&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pourquoi ne pas dépoussiérer tout ça en faisant un tour d'horizon de l'existant, afin de comprendre pourquoi ce sujet, en apparence basique, est un véritable champ de bataille inter-métiers où se retrouvent impliqués les designers, les développeurs, les product owners et les coût associés.&lt;/p&gt;

&lt;h2&gt;
  
  
  📏 Présentation des différentes unités
&lt;/h2&gt;

&lt;p&gt;En CSS, on retrouve plusieurs familles d'unités de longueur [1]&amp;nbsp;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Les unités absolues.&lt;/li&gt;
&lt;li&gt;Les unités relatives.&lt;/li&gt;
&lt;li&gt;Les pourcentages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chaque famille comporte plusieurs unités et remplit une fonction bien précise.&lt;/p&gt;

&lt;p&gt;Il faut comprendre que les unités de longueur doivent permettre de répondre aux besoins de plusieurs médias et pas uniquement des écrans d'ordinateurs et de smartphones / tablettes&amp;nbsp;: l'impression, elle aussi, est particulièrement concernée [4].&lt;/p&gt;

&lt;h3&gt;
  
  
  Les unités&amp;nbsp;absolues
&lt;/h3&gt;

&lt;p&gt;Elles sont considérées comme des constantes, soit des valeurs fixes qui, pour un écran donné, ne changent pas&amp;nbsp;:&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%2Flp7tg1t5y95uv42hxw3i.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%2Flp7tg1t5y95uv42hxw3i.png" alt="unit table" width="720" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Typiquement, en lisant ce tableau, vous pourriez vous dire que la plupart ne concernent pas le web. Et vous demander également&amp;nbsp;: "Finalement, pourquoi utiliser des pixels et pas des centimètres&amp;nbsp;?".&lt;/p&gt;

&lt;p&gt;Une partie de la réponse se trouve dans la définition d'un pixel, selon le W3C (World Wide Web Consortium)&amp;nbsp;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Le pixel est une unité de longueur qui correspond approximativement à la largeur ou à la hauteur d'un point unique, qui peut être vu confortablement par l'œil humain sans effort mais qui par ailleurs est aussi petit que possible.&lt;/p&gt;
&lt;/blockquote&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%2Fel6idabj0712md4l46st.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%2Fel6idabj0712md4l46st.png" alt="unit table" width="327" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La problématique est donc d'avoir une unité la plus précise possible malgré l'infinité des supports sur lesquels elle sera affichée. Nous ne détaillerons pas la solution technique évoquée par le W3C (ce serait beaucoup trop long pour ce document), cependant vous retrouverez des documents susceptibles de vous intéresser dans la section "Sources" en bas de page [3][7].&lt;/p&gt;

&lt;h4&gt;
  
  
  Concernant la question "pixels versus centimètres"
&lt;/h4&gt;

&lt;p&gt;On comprend donc que même les unités absolues dépendent de la résolution de l'écran sur lequel elles sont affichées. Il est inévitable qu'il y ait une différence entre la valeur indiquée dans votre code et celle que vous pourriez être tenté de mesurer, par exemple à l'aide d'une règle.&lt;/p&gt;

&lt;p&gt;Afin d'éviter les confusions, l'option la plus évidente pour le numérique est d'opter pour le pixel.&lt;/p&gt;

&lt;p&gt;La taille exacte d'un pixel dépendra de la résolution du périphérique sur lequel il est affiché [15].&lt;/p&gt;

&lt;h3&gt;
  
  
  Les unités relatives liées à la&amp;nbsp;police
&lt;/h3&gt;

&lt;p&gt;Comme leur nom l'indique, ces dernières sont relatives à la taille de la police ou d'un caractère dans une police donnée&amp;nbsp;:&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%2Fxusucy8scxotxmxi2wig.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%2Fxusucy8scxotxmxi2wig.png" alt="unit table" width="720" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vous aurez peut-être remarqué qu'il manque une colonne "Nom" à notre tableau → La raison est simple&amp;nbsp;: ces unités ne sont pas des abréviations.&lt;/p&gt;

&lt;p&gt;L'unité 'ex' fait référence à l'espace pris par un 'x'&amp;nbsp;: nous pourrions donc nous attendre à ce que 'em' fonctionne de manière similaire, mais ce n'est pas le cas [12]&amp;nbsp;: c'est une unité en typographie qui détermine un espace de référence pour une police donnée [2].&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%2F2r0ecrkci3wr7wiaz793.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%2F2r0ecrkci3wr7wiaz793.png" alt="Une chasse en métal" width="670" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une chasse en métal. La hauteur de ligne, c, est le précurseur du em [10].&lt;/p&gt;

&lt;h3&gt;
  
  
  Les unités relatives liées au&amp;nbsp;viewport
&lt;/h3&gt;

&lt;p&gt;Ces longueurs sont relatives au viewport. Le viewport correspond à la zone d'affichage visible d'un appareil. Pour un navigateur web, c'est le contenu de la fenêtre, sans prendre en compte le reste de l'interface. Ce concept, supporté par les navigateurs depuis les années 2012, a grandement facilité la responsivité des interactivités, en particulier sur mobile&amp;nbsp;:&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%2F6wizkrur3cq5wrft6qx5.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%2F6wizkrur3cq5wrft6qx5.png" alt="mobile unit table" width="720" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La plupart du temps, on utilisera le viewport dynamique pour concevoir des interfaces adaptées aussi bien au desktop qu'au mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Les pourcentages
&lt;/h3&gt;

&lt;p&gt;Les pourcentages (%) s'interprètent de manière relative à la valeur de la même propriété définie sur l'élément parent. Width dépendra de la width du parent, tandis que font-size dépendra de la font-size du parent [3].&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR&amp;nbsp;: 99% des&amp;nbsp;cas
&lt;/h2&gt;

&lt;p&gt;Des unités, il en existe beaucoup mais nous n'allons en utiliser qu'une toute petite partie dans le cadre du développement web.&lt;br&gt;
Vous trouverez ci-dessous un récapitulatif des unités les plus utilisées [6]&amp;nbsp;:&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%2Ftg5uz3322yhgdb0hfi1k.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%2Ftg5uz3322yhgdb0hfi1k.png" alt="mobile unit table" width="720" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ce qui, honnêtement, nous enlève une bonne épine du pied, non&amp;nbsp;? Nous passons de plus de vingt unités, à seulement six&amp;nbsp;!&lt;/p&gt;

&lt;p&gt;Cependant, il reste essentiel de faire les bons choix parmi ces six unités, et ce, pour chaque propriété (ou presque) que vous utiliserez en CSS. Ce choix dépendra du contexte et des enjeux de votre projet.&lt;/p&gt;

&lt;p&gt;Il fallait placer les bases, dans le prochaine article je m'attaquerai à des cas concrets et peut être même un petit peu d'architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source utile
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Unités CSS&amp;nbsp;: em, px, pt, cm, in.&amp;nbsp;.&amp;nbsp;.&amp;nbsp;. (s. d.). &lt;a href="https://www.w3.org/Style/Examples/007/units.fr.html" rel="noopener noreferrer"&gt;https://www.w3.org/Style/Examples/007/units.fr.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wikipedia contributors. (2025, 25 mai). Quad (typography). Wikipedia. &lt;a href="https://en.m.wikipedia.org/wiki/Quad_(typography)" rel="noopener noreferrer"&gt;https://en.m.wikipedia.org/wiki/Quad_(typography)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cascading Style Sheets, level 1. (s. d.). &lt;a href="https://www.w3.org/TR/CSS1/#percentage-units" rel="noopener noreferrer"&gt;https://www.w3.org/TR/CSS1/#percentage-units&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Media types. (s. d.). &lt;a href="https://www.w3.org/TR/CSS2/media.html#q7.0" rel="noopener noreferrer"&gt;https://www.w3.org/TR/CSS2/media.html#q7.0&lt;/a&gt;
5.An Introduction to Cascading Style Sheets. (s. d.). &lt;a href="https://nwalsh.com/docs/articles/css/" rel="noopener noreferrer"&gt;https://nwalsh.com/docs/articles/css/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Valeurs et unités CSS - Apprendre le développement web | MDN. (1970, 1 janvier). MDN Web Docs. &lt;a href="https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Styling_basics/Values_and_units" rel="noopener noreferrer"&gt;https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Styling_basics/Values_and_units&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reichenstein, O. (2025, 14 mai). Responsive Typography&amp;nbsp;: The Basics. iA. &lt;a href="https://ia.net/topics/responsive-typography-the-basics" rel="noopener noreferrer"&gt;https://ia.net/topics/responsive-typography-the-basics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wikipedia contributors. (2025a, mai 12). Em (typography). Wikipedia. &lt;a href="https://en.m.wikipedia.org/wiki/Em_(typography)" rel="noopener noreferrer"&gt;https://en.m.wikipedia.org/wiki/Em_(typography)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Syntax and basic data types. (s. d.). &lt;a href="https://www.w3.org/TR/CSS22/syndata.html#length-units" rel="noopener noreferrer"&gt;https://www.w3.org/TR/CSS22/syndata.html#length-units&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Gérez vos erreurs du métier aux codes HTTP</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Tue, 09 Jun 2026 07:51:31 +0000</pubDate>
      <link>https://dev.to/avisto/gerez-vos-erreurs-du-metier-aux-codes-http-28jd</link>
      <guid>https://dev.to/avisto/gerez-vos-erreurs-du-metier-aux-codes-http-28jd</guid>
      <description>&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%2Fsxbwojef7xg4z0w1c4aj.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%2Fsxbwojef7xg4z0w1c4aj.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LLa gestion des erreurs en Go, c’est souvent la dernière chose à laquelle on pense au démarrage d’un projet et la première qui pose problème quand le code grossit. Un http.Error ici, un if err != nil { return } là, et quelques messages dispersés dans les handlers. Ça fonctionne. Jusqu'au jour où il faut ajouter un nouveau cas d'erreur et qu'on réalise qu'il faut parcourir toute la codebase pour s'assurer de n'en avoir oublié aucun.&lt;/p&gt;

&lt;p&gt;Go met pourtant à disposition tous les outils nécessaires pour centraliser cette logique proprement. Cet article s’adresse aux développeurs qui connaissent les bases du langage et veulent structurer la gestion d’erreurs de leur API de façon maintenable. On va construire une architecture en trois couches : métier, API, HTTP. Dans laquelle chaque erreur est typée, chaque décision HTTP est prise en un seul endroit, et aucun détail interne n’est exposé au client.&lt;/p&gt;

&lt;h3&gt;
  
  
  le problème
&lt;/h3&gt;

&lt;p&gt;Voici un handler tiré d’un projet interne :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CapteurController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;pathId&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathValue&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="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;pgtype&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
 &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteCapteur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&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;Ce handler est parfaitement lisible. Mais il cache quelques problèmes.&lt;/p&gt;

&lt;p&gt;Si le paramètre id n'est pas un UUID valide, le client reçoit une 500 alors qu’une 400 serait plus approprié car c'est le client qui a envoyé une mauvaise requête.&lt;/p&gt;

&lt;p&gt;Si le capteur n’existe pas, il reçoit aussi une 500 au lieu d’une 404.&lt;/p&gt;

&lt;p&gt;Et dans les deux cas, le corps de la réponse est vide : aucun message pour expliquer ce qui a raté.&lt;/p&gt;

&lt;p&gt;Ce pattern se retrouve en vingt exemplaires dans la codebase. Chaque handler prend ses propres décisions, souvent par défaut. Ajouter une règle transverse, par exemple retourner un &lt;code&gt;404&lt;/code&gt; sur toutes les ressources introuvables, oblige à parcourir tous les handlers un par un.&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%2Fl6bpgwfugj0wbkgdvfyx.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%2Fl6bpgwfugj0wbkgdvfyx.png" width="799" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L’objectif est de concentrer toute la gestion des erreurs HTTP en un seul endroit&lt;/strong&gt; , de façon que les handlers se contentent de propager leurs erreurs sans savoir ce qu’il en advient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Centraliser la gestion d’erreurs
&lt;/h3&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%2Fv5yxtzyqq6q4cy6f3tkb.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%2Fv5yxtzyqq6q4cy6f3tkb.png" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Etape 1 : Déclaration des erreurs sentinelles
&lt;/h4&gt;

&lt;p&gt;La première étape, c’est de définir des erreurs nommées qui servent de référence partagée entre les couches. On appelle ça des erreurs sentinelles.&lt;/p&gt;

&lt;p&gt;Exemple :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Erreurs de la couche controller/api&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ErrInternalServerError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"internal server error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid request body"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrMissingCookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing session cookie"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrMissingParameter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing parameter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidRouteParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid route parameter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidQueryParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid query parameter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrBodyMismatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"body mismatch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidPassword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unauthorized"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrForbidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"forbidden"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Erreurs de la couche DB&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ErrNotFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrUnique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unique constraint violation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrForeignKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foreign key constraint violation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrNotNull&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not null constraint violation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrTxClosed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transaction closed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrUnknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unknown database error"&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;Ces valeurs se comparent avec errors.Is(err, ErrNotFound). Elles forment le vocabulaire partagé entre les couches, sans rien révéler des détails d'implémentation.&lt;/p&gt;

&lt;p&gt;Pourquoi définir ses erreurs avec errors.New et pas juste une chaine de caractère ? errors.New retourne un pointeur vers une structure interne. Deux appels avec le même message produisent deux valeurs distinctes :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// false&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;L’identité d’une sentinelle est celle de son adresse mémoire, pas son message.&lt;/strong&gt; C’est ce qui les rend sûres : var ErrNotFound = errors.New("not found") déclare une identité unique partagée dans tout le programme. Deux packages peuvent avoir une erreur "not found" sans que les errors.Is s'emmêlent.&lt;/p&gt;

&lt;h4&gt;
  
  
  Etape 2 : Enrichir les erreurs avec du contexte
&lt;/h4&gt;

&lt;p&gt;Les sentinelles ont un défaut : elles ne portent pas de contexte. ErrInvalidBody seul ne dit pas quel champ pose problème.&lt;/p&gt;

&lt;p&gt;La solution naïve serait de créer une nouvelle erreur avec fmt.Errorf("question title cannot be empty"). Mais alors errors.Is(err, ErrInvalidBody) retournerait false, l’identité de l’erreur a été perdue, et le mapping HTTP ne fonctionnerait plus.&lt;/p&gt;

&lt;p&gt;Mais il est possible d’implémenter la méthode Unwrap() error ou Unwrap() []error sur un type personnalisé afin d'“envelopper” une erreur sentinelle. Comme on le détaillera dans la suite, cela permet à Go de construire une chaîne d'erreurs qu'il pourra ensuite parcourir pour l'identifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;BadRequestError&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;badRequestError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&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;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;badRequestError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&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;ErrInvalidBody&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce type fait trois choses à la fois :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error() retourne le message personnalisé ("question title cannot be empty")&lt;/li&gt;
&lt;li&gt;Unwrap() expose l'erreur sentinelle sous-jacente (ErrInvalidBody)&lt;/li&gt;
&lt;li&gt;errors.Is(err, ErrInvalidBody) retourne true grâce à la traversée automatique de la chaîne&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En pratique cette logique s’applique sans même connaître le cadre dans lequel cette erreur sera consommée.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;QuestionProperties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"question title cannot be empty"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnswerList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&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;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"question must have at least 2 answers"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;validCount&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;1&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;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"question must have exactly one correct answer"&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="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La couche validation ne sait pas ce que le handler fera de l’erreur. Elle retourne juste des erreurs typées métier. C’est le principe de responsabilité unique appliqué à la gestion d’erreur.&lt;/p&gt;

&lt;h3&gt;
  
  
  Etape 3 : Parcourir la chaîne d’erreur
&lt;/h3&gt;

&lt;p&gt;Dans la partie précédente nous avions mentionné : erreur “enveloppée” et chaîne d’erreur. En go les erreurs forment un arbre dont la racine est l’erreur que l’on manipule. Cette arbre est parcouru par go à l’aide d’appel successifs à la méthode Unwrap.&lt;/p&gt;

&lt;p&gt;Dans les fonctions de mapping d’erreurs nous allons retrouver deux méthodes qui parcourent cet arbre errors.Is et errors.As.&lt;/p&gt;

&lt;p&gt;errors.Is(err, target) parcourt récursivement la chaîne d'erreurs via Unwrap jusqu'à trouver une valeur égale à target. C'est la fonction de test d'identité : elle répond à "est-ce que cette erreur est, ou contient, telle sentinelle ?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"titre manquant"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Unwrap() → ErrInvalidBody&lt;/span&gt;
&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// true — trouvé après un niveau d'Unwrap&lt;/span&gt;
&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// false — pas dans la chaîne&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;errors.As(err, &amp;amp;target) fait la même traversée, mais avec une assertion de type : elle cherche le premier maillon qui peut être assigné à target. Utile quand on veut extraire des données depuis une erreur concrète, pas juste tester son identité.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;badRequestError&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// accès direct au champ du type concret&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;fmt.Errorf("contexte : %w", err) est un raccourci pour créer une chaîne sans définir de type custom. Il génère une erreur dont Unwrap() retourne err. Parfait pour ajouter du contexte dans les logs, sans avoir besoin d'inspecter le type en aval.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// propager avec contexte — visible dans les logs, pas dans la réponse HTTP&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"updateQuiz: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;errors.As(err, &amp;amp;target) est particulièrement utile à la frontière d'une couche externe, comme une API ou une base de données, là où l'on souhaite inspecter le détail d'une erreur afin de la traduire en réponse métier :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;mapPgErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[ERROR] Database : %+v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pgx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNoRows&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;ErrNotFound&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pgx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTxClosed&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;ErrTxClosed&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pgErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pgconn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PgError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pgErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;pgErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23505"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// UNIQUE violation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrUnique&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23503"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// FOREIGN KEY violation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrForeignKey&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23502"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// NOT NULL violation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrNotNull&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23514"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"22P02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"22001"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// CHECK, invalid text, truncation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidInput&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%w: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrUnknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;errors.As(err, &amp;amp;pgErr) extrait l'erreur concrète de pgx pour lire le code PostgreSQL. Les couches supérieures n'ont jamais entendu parler de pgx. Elles reçoivent ErrNotFound ou ErrUnique, c'est tout.&lt;/p&gt;

&lt;h4&gt;
  
  
  Etape 4 : Mapper les erreurs vers leurs réponses HTTP
&lt;/h4&gt;

&lt;p&gt;Toutes les erreurs convergent vers une unique fonction mapError :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;mapError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"resource not found"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrMissingCookie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid credentials"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrForbidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusForbidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrForbidden&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrBodyMismatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnprocessableEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrBodyMismatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrUnique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusConflict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"resource already exists"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrInvalidInput&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidQueryParam&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidRouteParam&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrMissingParameter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInternalServerError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&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;Deux choses méritent l’attention ici :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pour les erreurs 400, on retourne err.Error() directement. C'est là que Unwrap joue son rôle complet : errors.Is reconnaît ErrInvalidBody via la chaîne, et err.Error() retourne le message du type concret. Le client reçoit "question title cannot be empty", pas "invalid request body".&lt;/li&gt;
&lt;li&gt;Pour ErrInvalidPassword en revanche, on ne retourne pas err.Error(). On retourne "invalid credentials". La raison : retourner "invalid password" confirmerait implicitement que le compte existe mais que le mot de passe est faux, une information qu'un attaquant peut exploiter pour de l'énumération de comptes. Le message interne reste dans les logs =&amp;gt; le client reçoit quelque chose de neutre.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Etape 5 : Comment utiliser cette gestion d’erreur ?
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;errorResponse&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"error"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;HandleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&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;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[ERROR] API: %+v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mapError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;WriteJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errorResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&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;Chaque handler appelle simplement HandleError et retourne :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// api/user/controller.go&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;CreateUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="n"&gt;CreateUserDTO&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&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;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&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;Password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;UserOutDTO&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromDBUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&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;Le handler ne sait pas si l’erreur est un conflit unique (409), une validation (400), ou une erreur interne (500). Il délègue entièrement cette décision à HandleError.&lt;/p&gt;

&lt;p&gt;Ce découplage a une conséquence concrète sur la maintenabilité. Ajouter un ErrRateLimited qui retourne un 429 ? On déclare la sentinelle, on ajoute un cas dans mapError, et c'est réglé pour l'ensemble du codebase. Zéro modification dans les handlers existants.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;L’architecture présentée repose sur quatre principes qui se renforcent mutuellement.&lt;/p&gt;

&lt;p&gt;Les &lt;strong&gt;sentinelles&lt;/strong&gt; définissent un vocabulaire d’erreurs stable, comparable par identité de pointeur. Elles constituent le contrat entre les couches.&lt;/p&gt;

&lt;p&gt;Les &lt;strong&gt;types custom avec&lt;/strong&gt; Unwrap permettent d'enrichir ce vocabulaire avec du contexte sans briser la comparaison. C'est la clé pour avoir à la fois des messages utiles pour l'utilisateur et un routage HTTP fiable.&lt;/p&gt;

&lt;p&gt;Le &lt;strong&gt;mappeur centralisé&lt;/strong&gt; isole la politique HTTP du reste du code. Tout ce savoir est concentré dans mapError, ce qui le rend facile à auditer et à modifier.&lt;/p&gt;

&lt;p&gt;La &lt;strong&gt;discipline sur&lt;/strong&gt; err.Error() ferme le système : on ne surface ce message que pour des erreurs dont la chaîne a été explicitement rédigée pour un utilisateur final. Tout ce qui vient d'un système externe est absorbé par une couche de traduction avant d'atteindre la réponse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pour aller plus loin
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/errors" rel="noopener noreferrer"&gt;errors package — Go standard library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/error-handling-and-go" rel="noopener noreferrer"&gt;Error handling and Go — The Go Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/go1.13-errors" rel="noopener noreferrer"&gt;Working with Errors in Go 1.13 — The Go Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwaredevelopment</category>
      <category>cleanarchitecture</category>
      <category>go</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>From business logic to HTTP codes error handling in Go</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Wed, 03 Jun 2026 13:05:39 +0000</pubDate>
      <link>https://dev.to/avisto/from-business-logic-to-http-codes-error-handling-in-go-2ec</link>
      <guid>https://dev.to/avisto/from-business-logic-to-http-codes-error-handling-in-go-2ec</guid>
      <description>&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%2Fsxbwojef7xg4z0w1c4aj.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%2Fsxbwojef7xg4z0w1c4aj.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Error handling in Go is usually the last thing anyone thinks about at the start of a project, and the first thing that causes pain when the codebase grows. An http.Error here, an if err != nil { return } there, a few hardcoded strings scattered across handlers. It works. Until the day you need to add a new error case and realize you have to grep through the entire codebase to make sure nothing slips through.&lt;/p&gt;

&lt;p&gt;Go actually gives you everything you need to centralize this cleanly. This article is for developers who know the language basics and want to structure their API’s error handling in a way that scales. We’ll build a three-layer architecture business, API, HTTP where every error is typed, every HTTP decision is made in one place, and nothing internal ever leaks to the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here’s a handler from a real internal project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CapteurController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;pathId&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathValue&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="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;pgtype&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;
 &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteCapteur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&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;Perfectly readable. But it hides a few problems.&lt;/p&gt;

&lt;p&gt;If id isn't a valid UUID, the client gets a 500 when a 400 would be correct, since the client sent a bad request. If the resource doesn't exist, they also get a 500 instead of a 404. And in both cases, the response body is empty: no message, no context, nothing.&lt;/p&gt;

&lt;p&gt;This pattern repeats twenty times across the codebase. Each handler makes its own decisions, usually by default. Adding a cross-cutting rule say, always return a 404 for missing resources means touching every handler individually.&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%2Fl6bpgwfugj0wbkgdvfyx.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%2Fl6bpgwfugj0wbkgdvfyx.png" width="799" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal is to push all HTTP error decisions into one place, so handlers can simply propagate errors without knowing what happens to them.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to centralized error handling
&lt;/h3&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%2Fv5yxtzyqq6q4cy6f3tkb.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%2Fv5yxtzyqq6q4cy6f3tkb.png" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 1 Sentinel errors&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The first step is to define named errors that act as a shared reference across layers. These are called sentinel errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Errors controller/api&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ErrInternalServerError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"internal server error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid request body"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrMissingCookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing session cookie"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrMissingParameter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing parameter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidRouteParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid route parameter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidQueryParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid query parameter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrBodyMismatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"body mismatch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidPassword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unauthorized"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrForbidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"forbidden"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Errors DB layer&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ErrNotFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrUnique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unique constraint violation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrForeignKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foreign key constraint violation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrNotNull&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not null constraint violation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrTxClosed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transaction closed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrUnknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unknown database error"&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;These are compared with errors.Is(err, ErrNotFound). They form a shared vocabulary between layers without exposing any implementation detail.&lt;/p&gt;

&lt;p&gt;Why use errors.New instead of a plain string? Because errors.New returns a pointer to an internal struct. Two calls with the same message produce two distinct values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// false&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A sentinel’s identity is its memory address, not its message. That’s what makes them safe: var ErrNotFound = errors.New("not found") declares a unique identity shared across the entire program. Two packages can each have a "not found" error without errors.Is getting confused between them.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 2 Adding context without losing identity&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Sentinels have one limitation: they carry no context. ErrInvalidBody alone doesn't tell you which field is invalid.&lt;/p&gt;

&lt;p&gt;The naive fix would be fmt.Errorf("question title cannot be empty") but then errors.Is(err, ErrInvalidBody) returns false. The error's identity is lost, and the HTTP mapping breaks.&lt;/p&gt;

&lt;p&gt;The right approach is to implement Unwrap() on a custom type, wrapping the sentinel inside a richer error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;BadRequestError&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;badRequestError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&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;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;badRequestError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&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;ErrInvalidBody&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type does three things at once: Error() returns the human-readable message, Unwrap() exposes the underlying sentinel, and errors.Is(err, ErrInvalidBody) returns true because Go traverses the chain automatically.&lt;/p&gt;

&lt;p&gt;In practice, the caller doesn’t need to know how the error will be consumed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;QuestionProperties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"question title cannot be empty"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnswerList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&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;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"question must have at least 2 answers"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;validCount&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;1&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;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"question must have exactly one correct answer"&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="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validation layer just returns typed errors. What happens to them is someone else’s problem. That’s the single responsibility principle applied to error handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3 Traversing the error chain&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In Go, errors form a tree rooted at the error you’re holding. Go traverses that tree through successive calls to Unwrap. Two functions do this traversal in the mapping layer.&lt;/p&gt;

&lt;p&gt;errors.Is(err, target) walks the chain recursively until it finds a value equal to target. It answers the question: "does this error contain this sentinel?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewBadRequestError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"titre manquant"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Unwrap() → ErrInvalidBody&lt;/span&gt;
&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// true - find after Unwrap&lt;/span&gt;
&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// false - not in the list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;errors.As(err, &amp;amp;target) does the same traversal but with a type assertion: it finds the first link in the chain that can be assigned to target. Useful when you need to extract data from a concrete error, not just test its identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;badRequestError&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Direct access to the field of the concrete type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;fmt.Errorf("context: %w", err) is a shortcut for building a chain without defining a custom type. It produces an error whose Unwrap() returns err useful for adding log context without needing to inspect the type downstream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Propagate with context visible in the logs, not in the HTTP response&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"updateQuiz: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;errors.As becomes especially valuable at external layer boundaries databases, third-party APIs where you want to inspect a low-level error and translate it into a business-layer response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;mapPgErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[ERROR] Database : %+v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pgx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNoRows&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;ErrNotFound&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pgx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTxClosed&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;ErrTxClosed&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pgErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pgconn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PgError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pgErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;pgErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23505"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// UNIQUE violation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrUnique&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23503"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// FOREIGN KEY violation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrForeignKey&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23502"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// NOT NULL violation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrNotNull&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"23514"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"22P02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"22001"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// CHECK, invalid text, truncation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidInput&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%w: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrUnknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;errors.As extracts the concrete pgx error to read the PostgreSQL code. Everything above this function has never heard of pgx. It receives ErrNotFound or ErrUnique nothing more.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 4 Mapping errors to HTTP responses&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;All errors converge on a single mapError function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;mapError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"resource not found"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrMissingCookie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid credentials"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrForbidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusForbidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrForbidden&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrBodyMismatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnprocessableEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrBodyMismatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrUnique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusConflict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"resource already exists"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrInvalidInput&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidQueryParam&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidRouteParam&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrMissingParameter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInternalServerError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&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;Two things worth noting here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For 400 errors, we return err.Error() directly. This is where Unwrap pays off completely: errors.Is recognizes ErrInvalidBody through the chain, and err.Error() returns the message from the concrete type. The client gets "question title cannot be empty", not the generic "invalid request body".&lt;/li&gt;
&lt;li&gt;For ErrInvalidPassword, we deliberately don't return err.Error(). We return "invalid credentials" instead. Returning "invalid password" would implicitly confirm that the account exists but the password is wrong information an attacker can use for account enumeration. The internal message stays in the logs; the client gets something neutral.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 5 Wiring it together
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;errorResponse&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"error"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;HandleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&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;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[ERROR] API: %+v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mapError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;WriteJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errorResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&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;Every handler calls HandleError and returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// api/user/controller.go&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;CreateUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="n"&gt;CreateUserDTO&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&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;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&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;Password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;UserOutDTO&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromDBUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&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 handler has no idea whether the error is a conflict (409), a validation failure (400), or an internal error (500). It delegates that decision entirely to HandleError.&lt;/p&gt;

&lt;p&gt;This decoupling has a concrete payoff for maintainability. Need to add an ErrRateLimited that returns a 429? Declare the sentinel, add one case to mapError, and you're done across the entire codebase, zero changes to existing handlers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The architecture presented here rests on four principles that reinforce each other.&lt;/p&gt;

&lt;p&gt;Sentinel errors define a stable error vocabulary, comparable by pointer identity. They are the contract between layers.&lt;/p&gt;

&lt;p&gt;Custom types with Unwrap let you enrich that vocabulary with context without breaking comparisons. That's the key to having both useful client messages and reliable HTTP routing.&lt;/p&gt;

&lt;p&gt;The centralized mapper isolates HTTP policy from the rest of the code. All that knowledge lives in mapError, making it easy to audit and extend.&lt;/p&gt;

&lt;p&gt;Discipline around err.Error() closes the system: you only surface that message for errors whose chain was explicitly written for an end user. Anything coming from an external system gets absorbed by a translation layer before it ever reaches a response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/errors" rel="noopener noreferrer"&gt;errors package — Go standard library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/error-handling-and-go" rel="noopener noreferrer"&gt;Error handling and Go — The Go Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/go1.13-errors" rel="noopener noreferrer"&gt;Working with Errors in Go 1.13 — The Go Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>go</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Le pattern que son créateur a fini par abandonner</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Mon, 01 Jun 2026 08:37:56 +0000</pubDate>
      <link>https://dev.to/avisto/le-pattern-que-son-createur-a-fini-par-abandonner-3mm4</link>
      <guid>https://dev.to/avisto/le-pattern-que-son-createur-a-fini-par-abandonner-3mm4</guid>
      <description>&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%2Fwmtt1lw13drc82rnf7mr.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%2Fwmtt1lw13drc82rnf7mr.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connaissez-vous le &lt;strong&gt;container/presentational pattern&lt;/strong&gt;  ? Peu importe la réponse : dans cet article, on s’intéresse surtout à son histoire. Ceci dit, si la réponse est &lt;em&gt;non&lt;/em&gt; et que vous débutez dans le développement front-end, restez, vous êtes au bon endroit.&lt;/p&gt;

&lt;p&gt;Ce n’est ni le seul “pattern” existant, ni une solution magique à tous vos problèmes d’architecture front-end. Mais c’est un concept structurant : comprendre ses origines, sa philosophie et ses limites est un vrai plus pour tout développeur.&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%2Fy7sy2ja9rkzwm3igaan4.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%2Fy7sy2ja9rkzwm3igaan4.png" width="687" height="117"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Et au passage, on expliquera aussi pourquoi Google vous renvoie encore des résultats contradictoires quand vous cherchez “container/presentational pattern” [3].&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ce n’est pas un concept récent. On peut même dire que sa version moderne a plus de dix ans. Pourtant, sa simplicité apparente fait que chacun semble en avoir sa propre définition. En voici une qui servira de base à notre discussion :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Presentational components&lt;/strong&gt;  : Components that &lt;strong&gt;&lt;em&gt;only&lt;/em&gt;&lt;/strong&gt; care about &lt;strong&gt;&lt;em&gt;how&lt;/em&gt;&lt;/strong&gt; a specific data is shown to the user.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Container components&lt;/strong&gt;  : Components that &lt;strong&gt;&lt;em&gt;mainly&lt;/em&gt;&lt;/strong&gt; care about &lt;strong&gt;&lt;em&gt;what&lt;/em&gt;&lt;/strong&gt; data is shown by &lt;strong&gt;&lt;em&gt;which&lt;/em&gt;&lt;/strong&gt; component to the user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Autrement dit, on sépare nos composants en deux catégories : ceux qui affichent, et ceux qui décident quoi afficher. Voilà, c’est tout rien de compliqué.&lt;/p&gt;

&lt;p&gt;Sauf que, en creusant, on découvre autant de sources que de définitions [5]. Elles se ressemblent, mais ne sont jamais identiques, comme s’il n’existait pas de définition officielle. Et pour cause : vous ne trouverez ni trace de ce pattern sur refactoring.guru, ni dans le livre du Gang of Four. Il n’y a pas de source “officiel” qui maintiens et se rend responsable de la définition.&lt;/p&gt;

&lt;p&gt;Pour comprendre son origine, il faut revenir à l’article qui a popularisé le terme dans la communauté front-end : en 2015, un article de Dan Abramov [1] présente ce pattern comme une approche élégante pour structurer une clean architecture en React.&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%2Fy1of3zazlu1bzerv62kn.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%2Fy1of3zazlu1bzerv62kn.png" width="800" height="595"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;45K 👏&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cet article a eu un impact énorme. Dan Abramov, cofondateur de Redux et développeur influent de l’écosystème React [13], n’était pas un inconnu. Et à l’époque, React était en pleine ascension : le Virtual DOM faisait sensation et tout le monde attendait les nouveautés du framework avec impatience.&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%2Fx2vf5s0t5pz3ghw5kxpz.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%2Fx2vf5s0t5pz3ghw5kxpz.png" width="800" height="377"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Oui, on parle de React sans Hooks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;C’est donc logiquement que l’article d’Abramov est devenu la référence implicite du pattern. Mais il est essentiel de le rappeler : il ne l’a pas inventé. Il l’a simplement popularisé, car il y voyait un moyen simple de structurer le code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I call them Container and Presentational components, but I also heard Fat and Skinny, Smart and Dumb, Stateful and Pure, Screens and Components, etc. These are not exactly the same, but the core idea is similar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Autrement dit, le concept existait déjà sous d’autres noms. Alors d’où vient-il vraiment ?&lt;/p&gt;

&lt;p&gt;Pour trouver ses racines, il faut remonter à 2004, lorsque Microsoft introduit le pattern MVVM (Model–View–ViewModel), inspiré du Presentation Model de Martin Fowler [9].&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%2Fc14zg9f173tnhjykt5au.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%2Fc14zg9f173tnhjykt5au.png" width="771" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le MVVM sépare clairement la logique métier de la logique de présentation d’une application, et permet de découpler la logique de l’interface utilisateur (UI) de son rendu. Ça commence à ressembler à quelque chose de familier, non ? Séparer la vue de la logique, exactement ce que cherche à faire le pattern container/presentational. La différence principale, ici, réside dans la notion de composant, absente du MVVM original.&lt;/p&gt;

&lt;p&gt;Aujourd’hui, l’architecture component-based [4,7] est devenue la norme dans le développement front-end : React, Svelte, Angular, Vue… tous reposent sur ce principe. Mais cela n’a pas toujours été le cas [2].&lt;/p&gt;

&lt;p&gt;À l’origine, les applications front-end étaient beaucoup plus monolithiques, souvent basées sur des structures MVC [8]. C’est dans ce contexte que naît Backbone.js en 2010, un des premiers frameworks populaires à s’appuyer sur le MVC [6]. Peu après, AngularJS apporte un two-way data binding plus dynamique, se rapprochant du MVVM, mais sans encore adopter une architecture orientée composants.&lt;/p&gt;

&lt;p&gt;Il faut attendre 2013, avec React, pour voir apparaître une architecture réellement component-based. React influence alors l’ensemble des frameworks qui suivront [11,12].&lt;/p&gt;

&lt;p&gt;Avec cette évolution, le pattern MVVM s’adapte : la séparation interface/logiciel demeure, mais elle s’incarne désormais dans les composants eux-mêmes. Le rôle du ViewModel est en grande partie absorbé par la logique du composant.&lt;/p&gt;

&lt;p&gt;Nous voilà en 2015.&lt;br&gt;&lt;br&gt;
La boucle est bouclée : le pattern container/presentational est formalisé sur Medium par Dan Abramov.&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%2Fhj2wb0l0ropmrh9oeq4u.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%2Fhj2wb0l0ropmrh9oeq4u.png" width="799" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Retournement de situation en 2019, Dan Abramov met à jour son article. Il y explique qu’il ne recommande plus d’utiliser ce pattern. Et si vous avez déjà travaillé avec React, la raison est évidente : l’arrivée des Hooks rend souvent cette séparation superflue [3].&lt;/p&gt;

&lt;h4&gt;
  
  
  Pattern c’est vraiment le terme ?
&lt;/h4&gt;

&lt;p&gt;J’attire votre attention sur la notion de ‘pattern’. Le Dumb/Smart n’a jamais été validé par une instance de référence. C’est un pattern “communautaire”, né de l’adaptation naturelle des développeurs à de nouveaux moyens techniques.&lt;/p&gt;

&lt;p&gt;C’est une évolution presque organique de l’écosystème front-end. On peut donc se demander si le terme “pattern” est vraiment légitime.&lt;/p&gt;

&lt;p&gt;Un pattern au sens classique : celui du Gang of Four, c’est une solution nommée, documentée, avec un contexte d’application précis et des compromis explicites. Le container/presentational ne coche pas ces cases. Pas de documentation de référence, pas d’instance qui en maintient la définition, et comme on l’a vu, un auteur qui finit par se rétracter.&lt;/p&gt;

&lt;p&gt;On pourrait donc dire que c’est davantage une convention partagée qu’un pattern à proprement parler. Une pratique qui a émergé naturellement, s’est diffusée par l’usage, et s’est stabilisée autour d’une idée centrale sans jamais se cristalliser en définition unique.&lt;/p&gt;

&lt;p&gt;Mais c’est là que ça devient intéressant. Le GoF date de 1994. L’écosystème front-end moderne n’existait pas. Les patterns d’aujourd’hui n’émergent plus dans des livres académiques, ils naissent sur Medium, se discutent sur GitHub, se valident en production dans des milliers de projets. Est-ce que ça les rend moins légitimes ?&lt;/p&gt;

&lt;p&gt;Et c’est précisément cette absence de définition canonique qui répond à la question posée en introduction : pourquoi Google renvoie-t-il des résultats contradictoires quand on cherche “container/presentational pattern” ? Ce n’est pas une erreur, ce n’est pas un manque de sources correctes. C’est le reflet fidèle de la nature même du concept. Chaque développeur qui a écrit sur le sujet l’a fait depuis sa propre expérience, son propre framework. Il n’existe pas de source à laquelle se référer pour départager les interprétations.&lt;/p&gt;

&lt;p&gt;C’est inconfortable si on cherche une réponse définitive. C’est fascinant si on accepte que certaines idées structurantes du développement fonctionnent ainsi : non pas imposées par le haut, mais construites collectivement par une communauté qui résout les mêmes problèmes avec les mêmes outils.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Alors, ce pattern est-il mort ?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Pas tout à fait. Dan Abramov a bien mis à jour son article en 2019 pour expliquer que les Hooks React rendent cette séparation souvent superflue. Mais cette conclusion ne vaut que pour React et l’écosystème web ne se résume pas à un seul framework.&lt;/p&gt;

&lt;p&gt;Dans Vue.js, Angular, dans Svelte, la question de séparer logique et présentation reste entière et pertinente. Le pattern n’a pas disparu : il s’est adapté, d’ailleurs dans react aussi simplement on ne le fait plus de la même manière.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pour rappel
&lt;/h4&gt;

&lt;p&gt;On a commencé par la brique de base : un bon composant, c’est un contexte clair et une responsabilité unique. Ni trop large, ni trop flou. C’est le fondement sans lequel aucune architecture ne tient.&lt;/p&gt;

&lt;p&gt;On a ensuite vu comment organiser ces composants entre eux grâce au pattern smart/dumb. Les composants atomiques et moléculaires gèrent l’affichage. Les pages gèrent la logique, les appels HTTP, la transformation des données. Le flux descend, ne remonte jamais. Quand un bug apparaît, vous savez exactement où chercher.&lt;/p&gt;

&lt;p&gt;Cet article ajoute la dernière couche : ce n’est pas un pattern sorti de nulle part. Il est le produit d’une évolution organique de l’écosystème front-end. Le comprendre dans son histoire, c’est comprendre pourquoi la séparation logique/vue n’est pas un dogme à appliquer mécaniquement, mais un principe à adapter à son contexte.&lt;/p&gt;

&lt;p&gt;Ce qui ne change pas, en revanche, c’est l’enjeu derrière tout ça : la maintenabilité. Une base de code que personne ne comprend six mois plus tard n’est pas une base de code, c’est une dette.&lt;/p&gt;

&lt;p&gt;L’architecture front-end est souvent traitée comme un détail, repoussée au moment où “le projet sera plus stable”. C’est exactement l’inverse qu’il faut faire. Les décisions structurelles prises au début d’un projet sont celles qu’on paie et dont on bénéficie pendant des années.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Abramov, D. (2019, 17 février). Presentational and Container Components — Dan Abramov — Medium. &lt;em&gt;Medium&lt;/em&gt;. &lt;a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0" rel="noopener noreferrer"&gt;https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Barroca, L., Hall, J., &amp;amp; Hall, P. (2000). An Introduction and History of Software Architectures, Components, and Reuse. Dans &lt;em&gt;Springer eBooks&lt;/em&gt; (p. 1‑11). &lt;a href="https://doi.org/10.1007/978-1-4471-0367-7_1" rel="noopener noreferrer"&gt;https://doi.org/10.1007/978-1-4471-0367-7_1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Borenkraout, M. (2024, 2 octobre). Why you should stop using the “container/presentational” pattern in Redux. &lt;em&gt;Medium&lt;/em&gt;. &lt;a href="https://medium.com/@matanbobi/why-you-should-stop-using-the-container-presentational-pattern-in-redux-29b112406128" rel="noopener noreferrer"&gt;https://medium.com/@matanbobi/why-you-should-stop-using-the-container-presentational-pattern-in-redux-29b112406128&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Component-Based Architecture and Design : A Practical Guide with Examples&lt;/em&gt;. (s. d.). Maruti Techlabs. &lt;a href="https://marutitech.com/guide-to-component-based-architecture/" rel="noopener noreferrer"&gt;https://marutitech.com/guide-to-component-based-architecture/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Container/Presentational pattern&lt;/em&gt;. (s. d.). &lt;a href="https://www.patterns.dev/react/presentational-container-pattern/" rel="noopener noreferrer"&gt;https://www.patterns.dev/react/presentational-container-pattern/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Crudu, V. (2025, 23 juin). Debunking Common Myths About Backbone.js in Developer Forums. &lt;em&gt;MoldStud — Custom Software Development Company&lt;/em&gt;. &lt;a href="https://moldstud.com/articles/p-debunking-common-myths-about-backbonejs-in-developer-forums" rel="noopener noreferrer"&gt;https://moldstud.com/articles/p-debunking-common-myths-about-backbonejs-in-developer-forums&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Das, A., &amp;amp; Das, A. (2024a, août 25). Ultimate Guide to Component-Based Architecture in Web Development. &lt;em&gt;PixelFreeStudio Blog -&lt;/em&gt;. &lt;a href="https://blog.pixelfreestudio.com/ultimate-guide-to-component-based-architecture-in-web-development/" rel="noopener noreferrer"&gt;https://blog.pixelfreestudio.com/ultimate-guide-to-component-based-architecture-in-web-development/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Das, A., &amp;amp; Das, A. (2024b, août 29). The Impact of Component-Based Architecture on Web Performance — PixelFreeStudio Blog. &lt;em&gt;PixelFreeStudio Blog -&lt;/em&gt;. &lt;a href="https://blog.pixelfreestudio.com/the-impact-of-component-based-architecture-on-web-performance/" rel="noopener noreferrer"&gt;https://blog.pixelfreestudio.com/the-impact-of-component-based-architecture-on-web-performance/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Fowler, M. (s. d.). &lt;em&gt;Presentation model&lt;/em&gt;. &lt;a href="http://martinfowler.com" rel="noopener noreferrer"&gt;http://martinfowler.com&lt;/a&gt; . &lt;a href="https://martinfowler.com/eaaDev/PresentationModel.html" rel="noopener noreferrer"&gt;https://martinfowler.com/eaaDev/PresentationModel.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kothapalli, M. (2024). The Evolution of Component-Based Architecture in Front-End Development. &lt;em&gt;Zenodo&lt;/em&gt;. &lt;a href="https://doi.org/10.5281/zenodo.12772844" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.12772844&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Suranga, S. (2025, 12 février). &lt;em&gt;A guide to modern frontend architecture patterns — LogRocket Blog&lt;/em&gt;. LogRocket Blog. &lt;a href="https://blog.logrocket.com/guide-modern-frontend-architecture-patterns/" rel="noopener noreferrer"&gt;https://blog.logrocket.com/guide-modern-frontend-architecture-patterns/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wanyoike, M. (2024, 4 juin). &lt;em&gt;History of front-end frameworks — LogRocket Blog&lt;/em&gt;. LogRocket Blog. &lt;a href="https://blog.logrocket.com/history-of-frontend-frameworks/" rel="noopener noreferrer"&gt;https://blog.logrocket.com/history-of-frontend-frameworks/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Polycube AI Studio. (2024, 4 novembre). &lt;em&gt;Dan Abramov : pionnier de Redux et visionnaire du développement JavaScript&lt;/em&gt;. Best Speakers. &lt;a href="https://bestspeakers.blog/2024/11/04/dan-abramov-pionnier-de-redux-et-visionnaire-du-developpement-javascript/" rel="noopener noreferrer"&gt;https://bestspeakers.blog/2024/11/04/dan-abramov-pionnier-de-redux-et-visionnaire-du-developpement-javascript/&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>softwaredevelopment</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>The Pattern Its Own Creator Eventually Abandoned</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Thu, 28 May 2026 09:12:05 +0000</pubDate>
      <link>https://dev.to/avisto/the-pattern-its-own-creator-eventually-abandoned-3m9b</link>
      <guid>https://dev.to/avisto/the-pattern-its-own-creator-eventually-abandoned-3m9b</guid>
      <description>&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%2Fwmtt1lw13drc82rnf7mr.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%2Fwmtt1lw13drc82rnf7mr.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you know the container/presentational pattern? It doesn’t matter either way this article is really about its history. That said, if the answer is no and you’re just getting started in front-end development, stick around. You’re in the right place.&lt;/p&gt;

&lt;p&gt;This isn’t the only pattern out there, and it won’t solve every front-end architecture problem you’ll ever face. But it’s a foundational concept: understanding where it comes from, what it stands for, and where it falls short is genuinely valuable for any developer.&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%2Fy7sy2ja9rkzwm3igaan4.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%2Fy7sy2ja9rkzwm3igaan4.png" width="687" height="117"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;And along the way, we’ll also answer why Google still returns contradictory results when you search for “container/presentational pattern.” [3]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This isn’t a new idea. Its modern form is over a decade old. Yet its apparent simplicity means everyone seems to have their own definition. Here’s the one we’ll use as a baseline:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Presentational components:&lt;/strong&gt; Components that only care about how specific data is shown to the user. &lt;strong&gt;Container components:&lt;/strong&gt; Components that mainly care about what data is shown by which component to the use_r._&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, we split components into two categories: those that display, and those that decide what to display. That’s it — nothing complicated.&lt;/p&gt;

&lt;p&gt;Except that the more you dig, the more definitions you find. They’re similar, but never quite the same — as if no official version exists. And that’s exactly the case: you won’t find this pattern on refactoring.guru, and it’s not in the Gang of Four book. There’s no authoritative source that owns and maintains the definition.&lt;/p&gt;

&lt;p&gt;To understand where it came from, you have to go back to the article that put it on the map. In 2015, Dan Abramov published a piece [1] presenting this pattern as an elegant approach to structuring clean React architecture.&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%2Fy1of3zazlu1bzerv62kn.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%2Fy1of3zazlu1bzerv62kn.png" width="800" height="595"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;45K 👏&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The article was huge. Dan Abramov co-creator of Redux and one of the most influential voices in the React ecosystem wasn’t some unknown blogger. And at the time, React was taking off fast: the Virtual DOM was generating real excitement, and the community was watching the framework’s every move.&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%2Fx2vf5s0t5pz3ghw5kxpz.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%2Fx2vf5s0t5pz3ghw5kxpz.png" width="800" height="377"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Worth noting: this is React without Hooks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So it made sense that Abramov’s article became the de facto reference for the pattern. But it’s important to be clear: he didn’t invent it. He just gave it a wider audience because he found it useful for structuring code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I call them Container and Presentational components, but I also heard Fat and Skinny, Smart and Dumb, Stateful and Pure, Screens and Components, etc. These are not exactly the same, but the core idea is similar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The concept already existed under other names. So where does it actually come from?&lt;/p&gt;

&lt;p&gt;To find the roots, you have to go back to 2004, when Microsoft introduced the MVVM pattern (Model–View–ViewModel) [9] , itself inspired by Martin Fowler’s Presentation Model.&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%2Fc14zg9f173tnhjykt5au.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%2Fc14zg9f173tnhjykt5au.png" width="771" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MVVM draws a clear line between business logic and presentation logic, decoupling the UI from its rendering. Sound familiar? Separating view from logic is exactly what the container/presentational pattern is after. The key difference is that MVVM predates the concept of components entirely.&lt;/p&gt;

&lt;p&gt;Component-based [4,7] architecture is now the standard in front-end development : React, Vue, Angular, Svelte all build on it. But that wasn’t always the case [2].&lt;/p&gt;

&lt;p&gt;Early front-end applications were far more monolithic, typically built around MVC structures [8]. Backbone.js, born in 2010, was one of the first popular frameworks to lean on MVC [6]. AngularJS followed with a more dynamic two-way data binding approach, edging closer to MVVM but still without a component-based architecture.&lt;/p&gt;

&lt;p&gt;That shift didn’t happen until React arrived in 2013. From there, it set the template for virtually every framework that came after [11,12].&lt;/p&gt;

&lt;p&gt;As components became the building block of front-end development, the MVVM pattern adapted. The separation between interface and logic remained, but it now lived inside components themselves. The ViewModel’s role was largely absorbed by component logic.&lt;/p&gt;

&lt;p&gt;Which brings us to 2015. The container/presentational pattern gets formalized on Medium by Dan Abramov.&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%2Fhj2wb0l0ropmrh9oeq4u.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%2Fhj2wb0l0ropmrh9oeq4u.png" width="799" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then in 2019, he updates the article and walks it back. If you’ve worked with React, the reason is obvious: Hooks make the separation largely unnecessary [3].&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is “pattern” even the right word?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s worth pausing on this. The smart/dumb distinction was never validated by any official body. It’s a community pattern one that emerged organically as developers adapted to new tools and constraints.&lt;/p&gt;

&lt;p&gt;In the classical sense, the Gang of Four sense a pattern is a named, documented solution with a precise context and explicit trade-offs. The container/presentational pattern doesn’t really meet that bar. No reference documentation, no authority maintaining the definition, and as we’ve seen, the person who popularized it eventually stepped back from it.&lt;/p&gt;

&lt;p&gt;You could argue it’s less a pattern and more a shared convention: a practice that spread through use, stabilized around a central idea, but never hardened into a single definition.&lt;/p&gt;

&lt;p&gt;Here’s where it gets interesting though. The GoF book is from 1994. Modern front-end didn’t exist yet. Today’s patterns don’t emerge from academic publications they’re born on Medium, debated on GitHub, and validated in production across thousands of projects. Does that make them less legitimate?&lt;/p&gt;

&lt;p&gt;And this is precisely what answers the question from the introduction: why does Google return contradictory results for “container/presentational pattern”? It’s not a gap in the literature. It’s an accurate reflection of what the concept actually is. Every developer who has written about it brought their own experience, their own framework, their own reading of Abramov’s article. There’s no canonical source to settle the debate so every interpretation coexists, and Google surfaces them all.&lt;/p&gt;

&lt;p&gt;That’s uncomfortable if you’re looking for a definitive answer. It’s fascinating if you accept that some of the most structurally important ideas in software development work exactly this way: not handed down from above, but built collectively by a community solving the same problems with the same tools.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;So is the pattern dead?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Not quite. Abramov’s 2019 update only really applies to React, and the web ecosystem is bigger than one framework. In Vue, Angular, and Svelte, the question of separating logic from presentation is still very much alive. The pattern hasn’t disappeared; it’s evolved. Even in React, the separation still exists it just looks different now.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;To bring it all together&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;This series started with the basic unit: a good component has a clear context and a single responsibility. Not too broad, not too vague. It’s the foundation everything else depends on.&lt;/p&gt;

&lt;p&gt;The second article looked at how to organize those components the smart/dumb pattern. Atomic and molecular components handle display. Pages handle logic, HTTP calls, data transformation. Data flows down, never up. When a bug appears, you know exactly where to look.&lt;/p&gt;

&lt;p&gt;This article adds the final layer: this pattern didn’t appear out of nowhere. It’s the product of an organic evolution in the front-end ecosystem. Understanding its history is what makes the difference between applying it mechanically and knowing when to reach for it and when not to.&lt;/p&gt;

&lt;p&gt;What doesn’t change across any of this is the underlying goal: maintainability. A codebase nobody can make sense of six months later isn’t a codebase : it’s debt.&lt;/p&gt;

&lt;p&gt;Front-end architecture is often treated as a secondary concern, something to sort out once the project “stabilizes.” That’s exactly backwards. The structural decisions made early in a project are the ones you live with or suffer under for years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Abramov, D. (2019, 17 février). Presentational and Container Components — Dan Abramov — Medium. &lt;em&gt;Medium&lt;/em&gt;. &lt;a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0" rel="noopener noreferrer"&gt;https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Barroca, L., Hall, J., &amp;amp; Hall, P. (2000). An Introduction and History of Software Architectures, Components, and Reuse. Dans &lt;em&gt;Springer eBooks&lt;/em&gt; (p. 1‑11). &lt;a href="https://doi.org/10.1007/978-1-4471-0367-7_1" rel="noopener noreferrer"&gt;https://doi.org/10.1007/978-1-4471-0367-7_1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Borenkraout, M. (2024, 2 octobre). Why you should stop using the “container/presentational” pattern in Redux. &lt;em&gt;Medium&lt;/em&gt;. &lt;a href="https://medium.com/@matanbobi/why-you-should-stop-using-the-container-presentational-pattern-in-redux-29b112406128" rel="noopener noreferrer"&gt;https://medium.com/@matanbobi/why-you-should-stop-using-the-container-presentational-pattern-in-redux-29b112406128&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Component-Based Architecture and Design : A Practical Guide with Examples&lt;/em&gt;. (s. d.). Maruti Techlabs. &lt;a href="https://marutitech.com/guide-to-component-based-architecture/" rel="noopener noreferrer"&gt;https://marutitech.com/guide-to-component-based-architecture/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Container/Presentational pattern&lt;/em&gt;. (s. d.). &lt;a href="https://www.patterns.dev/react/presentational-container-pattern/" rel="noopener noreferrer"&gt;https://www.patterns.dev/react/presentational-container-pattern/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Crudu, V. (2025, 23 juin). Debunking Common Myths About Backbone.js in Developer Forums. &lt;em&gt;MoldStud — Custom Software Development Company&lt;/em&gt;. &lt;a href="https://moldstud.com/articles/p-debunking-common-myths-about-backbonejs-in-developer-forums" rel="noopener noreferrer"&gt;https://moldstud.com/articles/p-debunking-common-myths-about-backbonejs-in-developer-forums&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Das, A., &amp;amp; Das, A. (2024a, août 25). Ultimate Guide to Component-Based Architecture in Web Development. &lt;em&gt;PixelFreeStudio Blog -&lt;/em&gt;. &lt;a href="https://blog.pixelfreestudio.com/ultimate-guide-to-component-based-architecture-in-web-development/" rel="noopener noreferrer"&gt;https://blog.pixelfreestudio.com/ultimate-guide-to-component-based-architecture-in-web-development/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Das, A., &amp;amp; Das, A. (2024b, août 29). The Impact of Component-Based Architecture on Web Performance — PixelFreeStudio Blog. &lt;em&gt;PixelFreeStudio Blog -&lt;/em&gt;. &lt;a href="https://blog.pixelfreestudio.com/the-impact-of-component-based-architecture-on-web-performance/" rel="noopener noreferrer"&gt;https://blog.pixelfreestudio.com/the-impact-of-component-based-architecture-on-web-performance/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Fowler, M. (s. d.). &lt;em&gt;Presentation model&lt;/em&gt;. &lt;a href="http://martinfowler.com" rel="noopener noreferrer"&gt;http://martinfowler.com&lt;/a&gt; . &lt;a href="https://martinfowler.com/eaaDev/PresentationModel.html" rel="noopener noreferrer"&gt;https://martinfowler.com/eaaDev/PresentationModel.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kothapalli, M. (2024). The Evolution of Component-Based Architecture in Front-End Development. &lt;em&gt;Zenodo&lt;/em&gt;. &lt;a href="https://doi.org/10.5281/zenodo.12772844" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.12772844&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Suranga, S. (2025, 12 février). &lt;em&gt;A guide to modern frontend architecture patterns — LogRocket Blog&lt;/em&gt;. LogRocket Blog. &lt;a href="https://blog.logrocket.com/guide-modern-frontend-architecture-patterns/" rel="noopener noreferrer"&gt;https://blog.logrocket.com/guide-modern-frontend-architecture-patterns/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wanyoike, M. (2024, 4 juin). &lt;em&gt;History of front-end frameworks — LogRocket Blog&lt;/em&gt;. LogRocket Blog. &lt;a href="https://blog.logrocket.com/history-of-frontend-frameworks/" rel="noopener noreferrer"&gt;https://blog.logrocket.com/history-of-frontend-frameworks/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Polycube AI Studio. (2024, 4 novembre). &lt;em&gt;Dan Abramov : pionnier de Redux et visionnaire du développement JavaScript&lt;/em&gt;. Best Speakers. &lt;a href="https://bestspeakers.blog/2024/11/04/dan-abramov-pionnier-de-redux-et-visionnaire-du-developpement-javascript/" rel="noopener noreferrer"&gt;https://bestspeakers.blog/2024/11/04/dan-abramov-pionnier-de-redux-et-visionnaire-du-developpement-javascript/&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>angular</category>
      <category>softwaredevelopment</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Organiser ses composants avec le pattern Container/Presentational</title>
      <dc:creator>Avisto</dc:creator>
      <pubDate>Mon, 25 May 2026 12:41:13 +0000</pubDate>
      <link>https://dev.to/avisto/organiser-ses-composants-avec-le-pattern-containerpresentational-45e2</link>
      <guid>https://dev.to/avisto/organiser-ses-composants-avec-le-pattern-containerpresentational-45e2</guid>
      <description>&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%2F5yg6fyau235472lpk33r.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%2F5yg6fyau235472lpk33r.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La solution pour organiser vos composants, c’est le pattern Presentational/Container. On a vu dans le précédent article comment penser un bon composant un contexte clair et une responsabilité unique. On s’attaque maintenant à comment les organiser correctement ensemble.&lt;/p&gt;

&lt;p&gt;Ce pattern est simple mais très efficace. Il n’existe pas de définition parfaitement consensuelle, mais voici celle que je trouve la plus pertinente :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presentational Components:&lt;/strong&gt; des composants qui se soucient uniquement de la façon dont une donnée spécifique est affichée à l’utilisateur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container Components:&lt;/strong&gt; des composants qui se soucient principalement de quelles données sont affichées par quel composant à l’utilisateur.&lt;/p&gt;

&lt;p&gt;Pour des raisons de simplicité, nous utiliserons les termes « smart » et « dumb » dans la suite de l’article.&lt;/p&gt;

&lt;p&gt;Ce pattern propose de séparer la logique de la vue. Autrement dit, de distinguer les composants utilisés pour afficher des données à l’utilisateur de ceux chargés d’appeler ou de transformer ces données.&lt;/p&gt;

&lt;h3&gt;
  
  
  Les presentational : revisiter l’atomic design
&lt;/h3&gt;

&lt;p&gt;L’atomic design est une méthodologie de création d’interfaces web proposée par Brad Frost en 2015. Elle repose sur la distinction de cinq niveaux de conception. Le principe est clair : les atomes se combinent pour former des molécules, qui elles-mêmes composent des organismes, puis des templates, et enfin des pages.&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%2Fjmycsefuh2i7nabwrx6w.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%2Fjmycsefuh2i7nabwrx6w.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dans notre cas, nous ne conserverons que les niveaux atomique et moléculaire. Les autres sont rarement utiles : les templates et les pages ne sont souvent que des assemblages spécifiques, difficiles à réutiliser. Les frontières deviennent floues à mesure qu’on monte dans la hiérarchie.&lt;/p&gt;

&lt;p&gt;Nos composants dumb se répartissent donc en deux catégories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un composant atomique&lt;/strong&gt; tolère une certaine généricité : lire un terme comme « item » dans ce contexte n’a rien de choquant. Ce sont les briques réutilisables, sans logique métier : les boutons, les champs de saisie, les icônes… bref, les éléments du design system. Leur rôle est limité à l’affichage et à la gestion des interactions simples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Les composants moléculaires&lt;/strong&gt; , eux, représentent un contexte plus concret. Ils rassemblent plusieurs composants atomiques et un tout petit peu de logique pour répondre à un besoin précis dans une situation donnée. Un article-card en est un bon exemple.&lt;/p&gt;

&lt;p&gt;Faire cette distinction dès le départ permet de gagner énormément de temps. On identifie immédiatement les composants réutilisables à l’échelle du projet et ceux qui appartiennent à un contexte spécifique, ce qui aide à préserver à la fois le contexte et la responsabilité de chaque composant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Les containers : utiliser les pages
&lt;/h3&gt;

&lt;p&gt;Viennent ensuite les composants responsables de la logique et de la cohérence des données. Ce rôle est confié aux pages. Une page est donc responsable de récupérer, transformer et distribuer les données aux composants enfants qui s’occupent de la présentation. Elle gère à la fois la logique et le layout.&lt;/p&gt;

&lt;p&gt;Certains préfèrent déléguer la gestion du layout à d’autres structures et conserver dans la page uniquement la logique. C’est possible, mais cette approche tend à complexifier inutilement le code. En pratique, il est souvent plus simple et plus lisible de regrouper ces deux aspects dans un seul composant page.&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%2Fwykr9u8dwv2ohujh19ci.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%2Fwykr9u8dwv2ohujh19ci.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Concrètement, tous les appels HTTP et les interactions avec le store se font au niveau de la page. Celle-ci transforme ensuite les données et les passe à ses enfants dans le format exact qu’ils attendent. Cette structure rend quasi inévitable la mise en place d’un flux de données unidirectionnel. On peut suivre la donnée depuis le back-end jusqu’à la vue de manière prévisible et simple : les données entrent dans l’application, sont transformées en amont, puis distribuées par morceaux dans les composants purement visuels.&lt;/p&gt;

&lt;p&gt;L’objectif n’est pas de promouvoir le props drilling excessif ou la remontée d’événements dans tous les sens, mais simplement de limiter les effets de bord. En cascade, la lecture du code reste claire et les mutations d’état imprévues sont contenues, ce qui réduit les risques de désynchronisation entre composants et conserve une architecture lisible et stable dans le temps.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mention honorable : One-way Data Flow
&lt;/h4&gt;

&lt;p&gt;Cette organisation n’est pas arbitraire : elle découle d’un principe plus fondamental, le flux de données unidirectionnel. Les données entrent par le haut, au niveau de la page, et descendent vers les composants enfants. Jamais l’inverse. Un composant dumb reçoit des données, les affiche et remonte des événements mais il ne va jamais chercher lui-même ce qu’il doit afficher.&lt;/p&gt;

&lt;p&gt;Ce flux prévisible est ce qui rend le projet maintenable sur la durée. Quand un bug apparaît, vous savez exactement où regarder : si l’affichage est faux, le problème est dans le composant dumb. Si la donnée est fausse, le problème est dans la page. Le débogage devient mécanique plutôt qu’instinctif.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quelques conseils
&lt;/h3&gt;

&lt;p&gt;Avant de conclure, quelques points à garder en tête sur l’utilisation de ce pattern :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Il n’y a aucun problème à imbriquer un composant smart dans un autre smart.&lt;/li&gt;
&lt;li&gt;Ce n’est pas absolu : un composant dumb peut parfois appeler une API, notamment s’il gère une configuration qui lui est propre.&lt;/li&gt;
&lt;li&gt;Certains frameworks et librairies de state management : Vue.js avec Pinia, par exemple rendent ce pattern largement superflu.&lt;/li&gt;
&lt;li&gt;Évitez de trop driller vos props à travers plusieurs niveaux de composants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Une bonne architecture, c’est la base de la scalabilité et de la maintenabilité d’un projet c’est d’autant plus vrai pour les applications qui vivent plusieurs années et où les développeurs se succèdent. Ce pattern ne révolutionne pas votre code du jour au lendemain. Mais dans six mois, quand un nouveau développeur ouvre le projet pour la première fois, il saura exactement où chercher la logique et où chercher l’interface. C’est tout l’intérêt.&lt;/p&gt;

&lt;p&gt;Pour finir cette série, un dernier article la semaine prochaine autour de l’histoire de ce pattern parce que comprendre d’où viennent les idées change la façon dont on les utilise.&lt;/p&gt;

</description>
      <category>cleancode</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
