<?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: Theodo</title>
    <description>The latest articles on DEV Community by Theodo (@theodo).</description>
    <link>https://dev.to/theodo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7937%2F45dc8b8b-50c8-4196-bd90-d9e010ed2630.png</url>
      <title>DEV Community: Theodo</title>
      <link>https://dev.to/theodo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/theodo"/>
    <language>en</language>
    <item>
      <title>How to configure CORS for Next.js</title>
      <dc:creator>Marek Elmayan</dc:creator>
      <pubDate>Wed, 29 Jan 2025 13:17:18 +0000</pubDate>
      <link>https://dev.to/theodo/how-to-configure-cors-for-nextjs-5ek2</link>
      <guid>https://dev.to/theodo/how-to-configure-cors-for-nextjs-5ek2</guid>
      <description>&lt;p&gt;Hey 👋,&lt;/p&gt;

&lt;p&gt;I've been working on a new package called &lt;a href="https://github.com/marek-e/next-armored" rel="noopener noreferrer"&gt;next-armored&lt;/a&gt; that &lt;strong&gt;helps you secure your Next.js application&lt;/strong&gt;. My first release is out, and I'm excited to share it with you. The &lt;strong&gt;first tool&lt;/strong&gt; I added is &lt;strong&gt;a middleware to help you configure CORS&lt;/strong&gt; for your Next.js server.&lt;/p&gt;

&lt;p&gt;If you're only interested in how to use it, and how to set it up, you can directly go check the 👉 tutorial section 👈.&lt;/p&gt;

&lt;p&gt;Otherwise, let's start with a quick reminder of what &lt;strong&gt;Cross-Origin Resource Sharing (CORS)&lt;/strong&gt; is and when you need to set it up in your &lt;strong&gt;Next.js app&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/marek-e/next-armored" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Drop a star on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/next-armored" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Check on NPM 📦&lt;/a&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 I - What is the CORS policy ?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cross-Origin Resource Sharing a web browser security feature
&lt;/h3&gt;

&lt;p&gt;CORS is a security feature that allows you to control which origins are allowed to access your resources. It is &lt;strong&gt;enforced by web browsers&lt;/strong&gt; through &lt;code&gt;Access-control-allow-*&lt;/code&gt; headers.&lt;/p&gt;

&lt;p&gt;CORS protects users of your app from malicious sites trying to perform operations (like retrieving personal information or account deletion) on your server without their knowledge. While the request will still reach your server, if it is correctly configured, it will attach CORS security headers based on the request’s origin.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If the request comes from your allowed origins, the browser will allow access to the server’s response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the request originates from a disallowed or malicious site, the browser will block access to the response.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, in the simplest case of a GET request, an attacker could try to retrieve information. For other methods, such as DELETE, the browser sends a preflight request first. Based on the server's CORS configuration, the browser decides whether to proceed with the 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%2Fmrxpvuvkhpiqfce95gcq.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%2Fmrxpvuvkhpiqfce95gcq.png" alt="CORS explained with a schema" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more in-depth information, check this &lt;a href="https://blog.theodo.com/2024/07/mental-model-of-cors/" rel="noopener noreferrer"&gt;article by a friend of mine&lt;/a&gt; or the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;MDN Documentation&lt;/a&gt;.&lt;br&gt;
For insights into CORS vulnerabilities, explore &lt;a href="https://outpost24.com/blog/exploiting-permissive-cors-configurations/" rel="noopener noreferrer"&gt;Outpost24’s blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  ❓ What is an origin, and how do you differentiate them?
&lt;/h3&gt;

&lt;p&gt;An origin is defined as a combination of the &lt;strong&gt;protocol, host, and port&lt;/strong&gt; of a resource. For example, the origin of &lt;code&gt;https://example.com/index.html&lt;/code&gt; is &lt;code&gt;https://example.com&lt;/code&gt;. A change in any of these three components results in a different origin.&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%2Fh75nuejuc1yn4jm4mf96.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%2Fh75nuejuc1yn4jm4mf96.png" alt="Origin explained with a schema" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚡️ Subdomains change the host, so &lt;code&gt;https://example.com&lt;/code&gt; and &lt;code&gt;https://api.example.com&lt;/code&gt; are considered different origins.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🛠️ II - When should you configure CORS for your Next.js application?
&lt;/h2&gt;

&lt;p&gt;We saw that &lt;code&gt;https://example.com&lt;/code&gt; and &lt;code&gt;https://api.example.com&lt;/code&gt; are different origins. So if your server is at &lt;code&gt;https://api.example.com&lt;/code&gt; but your frontend app at &lt;code&gt;https://example.com&lt;/code&gt;, you need to configure CORS on your server to allow requests from your frontend.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why is that?&lt;/em&gt; Historically, browsers enforce the &lt;strong&gt;Same Origin Policy (SOP)&lt;/strong&gt;, allowing requests only between the same origin for security. This policy’s limitations necessitated the introduction of CORS to enable controlled cross-origin requests, such as inter-operability between different subdomains or public APIs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Now, what about Next.js ?&lt;/em&gt; If you use Next.js 13 or later with the app router (e.g., a folder like ./src/app/api), your frontend (e.g., &lt;a href="https://your-domain.com" rel="noopener noreferrer"&gt;https://your-domain.com&lt;/a&gt;) and API (e.g., &lt;a href="https://your-domain.com/api" rel="noopener noreferrer"&gt;https://your-domain.com/api&lt;/a&gt;) share the same origin. Here, the SOP suffices, and enabling CORS without a valid reason could introduce vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;So, when should you enable CORS?&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If your API is accessed by another frontend&lt;/strong&gt; (e.g., &lt;code&gt;https://admin.your-domain.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If your API is public&lt;/strong&gt;, allowing access from any origin (ensure the API doesn’t handle sensitive data).&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt; 👨🏼‍💻 III - How to configure CORS for Next.js with next-armored
&lt;/h2&gt;
&lt;h3&gt;
  
  
  📦 Install the next-armored package
&lt;/h3&gt;

&lt;p&gt;Select your favorite package manager (e.g. pnpm) and install the &lt;code&gt;next-armored&lt;/code&gt; package by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add next-armored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🥇 Use the CORS middleware as your first middleware
&lt;/h3&gt;

&lt;p&gt;Create or update your &lt;code&gt;./src/middleware.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createCorsMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-armored/cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;corsMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCorsMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5173&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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="nf"&gt;corsMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/:path*&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;In Next.js, the middleware is executed before every request matching the &lt;code&gt;matcher&lt;/code&gt; pattern inside the config object.&lt;br&gt;
For this example, I will suppose that you just created the middleware file. So you just need to match all your api routes.&lt;br&gt;
If you are using the app router, you can match all your api routes by using the &lt;code&gt;'/api/:path*'&lt;/code&gt; pattern.&lt;br&gt;
Then import the &lt;code&gt;createCorsMiddleware&lt;/code&gt; function from &lt;code&gt;next-armored/cors&lt;/code&gt; and pass your configuration object to it. This will allow you to build the cors middleware with the origins you want to allow and pass it to the middleware function.&lt;/p&gt;
&lt;h3&gt;
  
  
  🎼 Compose your CORS middleware with other middleware
&lt;/h3&gt;

&lt;p&gt;If you already have middleware, compose them as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createCorsMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-armored/cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;corsMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCorsMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5173&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otherMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otherMiddleware&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;otherMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;isApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api&lt;/span&gt;&lt;span class="dl"&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;isApi&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="nf"&gt;corsMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/:path*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home/:path*&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;If your matcher didn't previously include the api routes, you will have to add it to the matcher.&lt;br&gt;
Then you can call the other middleware first and get the response.&lt;br&gt;
Since the cors middleware should be applied only to the api routes, you have to check if the request is an api request and then apply the cors middleware only in that case. This can be done easily by checking the pathname of the request with &lt;code&gt;request.nextUrl.pathname.startsWith('/api')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, pass the response to the cors middleware as the 2nd argument. In this case, the cors middleware will attach the headers to the existing response instead of returning a new response.&lt;/p&gt;



&lt;p&gt;Alternatively, use a utility to chain middleware. Here is a snippet of how you can do it but it shall be adapted to the need of each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CustomMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextFetchEvent&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;NextResponse&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;NextMiddlewareResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MiddlewareFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;CustomMiddleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chainMiddlewares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MiddlewareFactory&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&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;CustomMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&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;current&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chainMiddlewares&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&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="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&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="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextFetchEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;middleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chainMiddleware&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;middleware1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;middleware2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;corsMiddleware&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;
  
  
  ⚙️ Full CORS middleware configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;corsMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCorsMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5173&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;optionsSuccessStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;allowCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;exposedHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;preflightContinue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;You have to at least specify the &lt;code&gt;origins&lt;/code&gt; option because keeping &lt;code&gt;*&lt;/code&gt; by default is not recommended as it can be a security risk. If you are building a public api, you can allow all origins by passing &lt;code&gt;origins: ['*']&lt;/code&gt; but you should be aware of the security implications.&lt;/p&gt;

&lt;p&gt;You can define other options as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;origins&lt;/code&gt; - array of origins that are allowed to access the resource. The only one to be required.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;methods&lt;/code&gt; - array of methods that are allowed to access the resource. Default value to &lt;code&gt;["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;headers&lt;/code&gt; - array of headers that are allowed to access the resource. Default value to &lt;code&gt;["Content-Type", "Authorization"]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxAge&lt;/code&gt; - number of seconds that the results of a preflight request can be cached. Default value to &lt;code&gt;60 * 60 * 24&lt;/code&gt; (1 day).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;optionsSuccessStatus&lt;/code&gt; - status code to send for successful OPTIONS requests. Default value to &lt;code&gt;204&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allowCredentials&lt;/code&gt; - boolean value to indicate if credentials are allowed to be sent with the request. Default value to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exposedHeaders&lt;/code&gt; - array of headers that are exposed to the client. Default value to &lt;code&gt;[]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preflightContinue&lt;/code&gt; - boolean value to indicate if it should pass the CORS preflight response to the next handler. Default value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚫 Enable/disable CORS for specific paths
&lt;/h3&gt;

&lt;p&gt;Enable CORS for specific paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;corsMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCorsMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5173&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;additionalIncludes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example&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;blockquote&gt;
&lt;p&gt;This will enable CORS for all paths that start with &lt;code&gt;/api/v2&lt;/code&gt; and at the same time include &lt;code&gt;example&lt;/code&gt; in the pathname.&lt;br&gt;
&lt;code&gt;api/v2/product/example&lt;/code&gt; will have CORS enabled, but &lt;code&gt;api/v1/product/example/test&lt;/code&gt; will not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Disable CORS for specific paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;corsMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCorsMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5173&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;excludes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/restricted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In this case, all the routes starting with &lt;code&gt;/api/restricted&lt;/code&gt; will not enforce CORS but the SOP (Same-Origin Policy) as the default behavior suggests.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔮 IV - Why use &lt;code&gt;next-armored&lt;/code&gt;?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🚀 Easy to use&lt;/strong&gt;: Create the middleware with few lines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🔧 Flexible&lt;/strong&gt;: Customize middleware configurations to fit your needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🛡️ Secure&lt;/strong&gt;: Default configurations are based on best practices and security standards.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;✅ Type-safe&lt;/strong&gt;: Middleware is fully typed and compatible with Next.js and TypeScript. Also it's tree-shakable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🌐 Open source&lt;/strong&gt;: Audit, report issues, and contribute.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔎 V - Next steps for &lt;code&gt;next-armored&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I started with a cors middleware for Next.js but my goal is to create a bunch of utils to help you secure your Next.js application. So I definitely plan to add more utils and more middleware to this package.&lt;br&gt;
The next one will probably be a middleware to handle Content Security Policy (CSP) headers. But if you have any suggestion, or specific needs, please let me know. 😊&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/marek-e/next-armored" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Drop a star on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/next-armored" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Check on NPM 📦&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>nextjs</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Mastering API Versioning: Strategies for Seamless Frontend-Backend Communication in Mobile Apps</title>
      <dc:creator>Abdelmoujib MEGZARI</dc:creator>
      <pubDate>Tue, 09 Jul 2024 09:21:06 +0000</pubDate>
      <link>https://dev.to/theodo/mastering-api-versioning-strategies-for-seamless-frontend-backend-communication-in-mobile-apps-12o7</link>
      <guid>https://dev.to/theodo/mastering-api-versioning-strategies-for-seamless-frontend-backend-communication-in-mobile-apps-12o7</guid>
      <description>&lt;p&gt;Effective API versioning is essential for maintaining seamless communication between the frontend and backend. This article explains why API versioning is important and analyses various versioning strategies, offering practical insights for backend and mobile app developers. My goal is to equip you with the knowledge to manage API versions efficiently, by ensuring a practical experience for developers while maintaining a seamless user experience. This article is based on our experience with API versioning in the &lt;a href="https://pass.culture.fr/" rel="noopener noreferrer"&gt;PassCulture&lt;/a&gt; app.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Context
&lt;/h1&gt;

&lt;p&gt;In the PassCulture app, as with many applications, we control both the frontend and the backend. However, we do not control the version of the frontend that the user is using. This lack of control over the app version necessitates effective API versioning to ensure compatibility and functionality across different user versions. As the app evolves, the API code may change to accommodate new versions of the app, which may not be compatible with older versions.&lt;/p&gt;

&lt;p&gt;For example, consider an API endpoint for user authentication that initially requires a username and password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;username&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&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;user&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generate_token&lt;/span&gt;&lt;span class="p"&gt;()}),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid credentials&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend (app V1) would send a request to this endpoint with the following code:&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, suppose we want to add two-factor authentication to the app. We could modify the endpoint to require an additional field, &lt;code&gt;otp&lt;/code&gt;, for the one-time password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;username&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;otp&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;otp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&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;user&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_otp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generate_token&lt;/span&gt;&lt;span class="p"&gt;()}),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid credentials&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend (app V2) would need to be updated to include the &lt;code&gt;otp&lt;/code&gt; field in the request.&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, app V1 would not include the otp field, resulting in an error when users on V1 try to authenticate. To avoid this issue, we need to implement API versioning. This way, both versions of the app can interact with the backend appropriately: app V1 can continue to use the original endpoint without otp, while app V2 can use a new version of the endpoint that requires otp. This ensures compatibility and a seamless user experience across different app versions.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Alternatives to API Versioning
&lt;/h1&gt;

&lt;p&gt;Is API versioning the only solution to manage changes in the backend code? Not necessarily. Here are some alternatives to API versioning:&lt;/p&gt;

&lt;h2&gt;
  
  
  a. Forced App Updates
&lt;/h2&gt;

&lt;p&gt;One way to manage changes in the backend code is to force users to update their app. This approach ensures that all users are on the latest version of the app, which is compatible with the latest backend code. However, forced app updates can be disruptive to users and may not always be feasible, especially if users are on older devices or have limited internet connectivity.&lt;/p&gt;

&lt;p&gt;Forced updates are a good solution for critical issues, such as security vulnerabilities, that require immediate action.However, they may not be the best approach for non-critical changes or new features.&lt;/p&gt;

&lt;h2&gt;
  
  
  b. Feature Flags
&lt;/h2&gt;

&lt;p&gt;Feature flags allow you to control the visibility of new features in the app without requiring a new version. By using feature flags, you can gradually roll out new features to users without forcing them to update the app. However, feature flags can be complex to manage and may not be suitable for all types of changes.&lt;/p&gt;

&lt;p&gt;If we take the previous example of adding two-factor authentication, we can write the V2 code as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twoFactorAuth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123456&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;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we would enable the feature flag when all users have updated their app to V2 and we will simultaniously update the backend code to handle the new field.&lt;/p&gt;

&lt;p&gt;We will then need to clean the code to remove the old code and the feature flag.&lt;/p&gt;

&lt;p&gt;This approach can be useful for simultaneously rolling out new features across different app versions. However, it requires careful management of feature flags and may introduce complexity to the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  c. API Route Backward Compatibility
&lt;/h2&gt;

&lt;p&gt;Another approach is to maintain backward compatibility in the API routes. This means that the backend code is designed to handle requests from older app versions, even if the route has been updated. For example, the backend code can check if the request contains the &lt;code&gt;otp&lt;/code&gt; field and handle it accordingly.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
      &lt;span class="n"&gt;username&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;otp&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;otp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Use get to avoid KeyError
&lt;/span&gt;      &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&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;user&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_otp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_token&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;opt&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nf"&gt;limit_access_to_some_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid credentials&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach can be useful for minor changes. However, it can lead to complex code and potential bugs if not managed properly. It also requires careful testing to ensure that backward compatibility is maintained. It also requires the developer to clean the code once the old version of the app is no longer used.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. &lt;strong&gt;Possible Versioning Solutions&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;When managing multiple API versions, there are several strategies to consider. Mainly three strategies come to mind:&lt;/p&gt;

&lt;h2&gt;
  
  
  a. &lt;strong&gt;Code level versioning&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This solution consists of managing the different versions of the routes in the code.&lt;br&gt;
In this case, the different route versions are just different routes in the code. We declare new routes with the version included in the URL.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/v1/auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nd"&gt;@route.deprecated&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/v2/auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth_v2&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, The app specifies the version of the route it wants to use in the URL.&lt;/p&gt;

&lt;p&gt;For example the app V1 will use the route &lt;code&gt;/v1/auth&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v1/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app V2 will use the route &lt;code&gt;/v2/auth&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v2/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci0bc01309idsh0xq47b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci0bc01309idsh0xq47b.png" alt="code versionning schema:&amp;lt;br&amp;gt;
This diagram illustrates the interaction between different versions of a mobile application (App V1 and App V2) with a single API POD. The diagram includes the following elements:&amp;lt;br&amp;gt;
Mobile Applications:&amp;lt;br&amp;gt;
App V1&amp;lt;br&amp;gt;
App V2&amp;lt;br&amp;gt;
API Endpoint:&amp;lt;br&amp;gt;
API served by a single POD&amp;lt;br&amp;gt;
Routes:&amp;lt;br&amp;gt;
App V1 sends requests to v1/route, directed to the API POD.&amp;lt;br&amp;gt;
App V2 sends requests to v2/route, directed to the same API POD.&amp;lt;br&amp;gt;
The diagram visually represents the flow of requests from the mobile applications directly to the single API POD, showing how different app versions interact with the same API endpoint through their respective routes" width="245" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  b. &lt;strong&gt;Infrastructure level versioning&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This solution consists of deploying multiple API instances in parallel on different pods. Each instance corresponds to a version of the API. The app specifies the version of the API it wants to use in the request. The infrastructure is responsible for routing the request to the correct API instance.&lt;/p&gt;

&lt;p&gt;A Pod is a group of one or more containers, with shared storage/network resources, and a specification for how to run the containers. Pods are the smallest deployable units of computing that can be created and managed in Kubernetes. Read more about &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/" rel="noopener noreferrer"&gt;Kubernetes Pods&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dy8d4al8233azjqozys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dy8d4al8233azjqozys.png" alt="Ifrastucture versionning schema:&amp;lt;br&amp;gt;
This diagram illustrates the interaction between different versions of a mobile application (App V1, App V2, and App V3) with their corresponding API versions via a load balancer. The diagram includes the following elements:&amp;lt;br&amp;gt;
Mobile Applications:&amp;lt;br&amp;gt;
App V1&amp;lt;br&amp;gt;
App V2&amp;lt;br&amp;gt;
App V3&amp;lt;br&amp;gt;
API Endpoints:&amp;lt;br&amp;gt;
API v1 (served by POD 1)&amp;lt;br&amp;gt;
API v2 (served by POD 2)&amp;lt;br&amp;gt;
Routes:&amp;lt;br&amp;gt;
App V1 sends requests to v1/route1 and v1/route2, both directed to API v1.&amp;lt;br&amp;gt;
App V2 sends requests to v1/route1 (to API v1) and v2/route2 (to API v2).&amp;lt;br&amp;gt;
App V3 sends requests to v2/route1 and v2/route2, both directed to API v2.&amp;lt;br&amp;gt;
Load Balancer:&amp;lt;br&amp;gt;
Routes the requests from the mobile applications to the appropriate API endpoints based on the requested route.&amp;lt;br&amp;gt;
The diagram visually represents the flow of requests from the mobile applications through the load balancer to the specific API versions, showing how different app versions interact with different API versions." width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, whenever the developer wants to create a new version of a route, he just needs to change the code without thinking about the old version. The old version of the route will still be available as long as the old pod is still running, and the new version will be available as soon as a new pod is deployed with the latest API code.&lt;/p&gt;

&lt;p&gt;When a pod is no longer used, it can be deleted. This will delete the old version of the route. Deleting the pod will depend on the oldest version of the app users can still use.&lt;/p&gt;

&lt;p&gt;In general, a git branch is created for each version of the API. In case of critical issues, modifying the old code can be done by cherry-picking the fix commit to the versions branch and redeploying it from that branch.&lt;/p&gt;

&lt;p&gt;In contrast, to code level versioning, infrastructure level versioning doesn't require the developer to clean the code when the old version is no longer used. Additionaly, infrastructure level versioning ensures that a change in the code will not affect the old versions of the app, thus requiring no quality check on the old versions of the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  c. &lt;strong&gt;Backend For Frontend (BFF) level versioning&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This type of versioning is a compromise between the two previous solutions. It consists of splitting the backend into two parts: the main backend and the BFF. The main backend is responsible for the business logic and the data access. The BFF is responsible for calling the main backend and formatting the data in the way that each app version expects. The BFF is versioned, and the app specifies the version of the BFF it wants to use in the request.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85aak3lrs4bd0286sdu2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F85aak3lrs4bd0286sdu2.png" alt="BFF versionning schema:&amp;lt;br&amp;gt;
This diagram illustrates the interaction between different versions of a mobile application (App V1, App V2, and App V3) with their corresponding API versions via a load balancer, along with their connection to the main backend. The diagram includes the following elements:&amp;lt;br&amp;gt;
Mobile Applications:&amp;lt;br&amp;gt;
App V1&amp;lt;br&amp;gt;
App V2&amp;lt;br&amp;gt;
App V3&amp;lt;br&amp;gt;
API Endpoints (Backend for Frontend):&amp;lt;br&amp;gt;
API v1 (served by POD 1)&amp;lt;br&amp;gt;
API v2 (served by POD 2)&amp;lt;br&amp;gt;
Main Backend:&amp;lt;br&amp;gt;
Main Backend (served by POD 3)&amp;lt;br&amp;gt;
Routes:&amp;lt;br&amp;gt;
App V1 sends requests to v1/route1 and v1/route2, both directed to API v1.&amp;lt;br&amp;gt;
App V2 sends requests to v1/route1 (to API v1) and v2/route2 (to API v2).&amp;lt;br&amp;gt;
App V3 sends requests to v2/route1 and v2/route2, both directed to API v2.&amp;lt;br&amp;gt;
Load Balancer:&amp;lt;br&amp;gt;
Routes the requests from the mobile applications to the appropriate API endpoints based on the requested route.&amp;lt;br&amp;gt;
Connections to Main Backend:&amp;lt;br&amp;gt;
Both API v1 and API v2 have connections to the Main Backend, allowing them to access core backend services.&amp;lt;br&amp;gt;
The diagram visually represents the flow of requests from the mobile applications through the load balancer to the specific API versions, and subsequently to the main backend. It shows how different app versions interact with different API versions and how these APIs connect to the main backend system.&amp;lt;br&amp;gt;
" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main contrast between this solution and the infrastructure level versioning is that it ensures that a change in the business logic will affect all versions of the app. While keeping the advantage of the infrastructure level versioning of not needing to clean the code.&lt;br&gt;
However it requires a good separation of the business logic from the data formatting and the data access.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. &lt;strong&gt;Key Considerations&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;In order to choose the best solution for API versioning, we consider the following key criteria:&lt;/p&gt;

&lt;h2&gt;
  
  
  a. &lt;strong&gt;Correcting Critical Bugs Across Versions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It’s crucial to be able to check which versions are affected by a bug or a vunerability. It’s also important to be able to fix the bug in all affected versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  b. &lt;strong&gt;Same business logic for all versions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It’s important to ensure that a change in the business logic will affect all versions of the app. Since this can create inconsistances between the different versions of the app. And a use of an old business logic can be a critical security issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  c. &lt;strong&gt;Maniability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It should be easy for the developers to manage the multiple versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to create, update or delete a version of a route&lt;/li&gt;
&lt;li&gt;Easy to mark versions as deprecated&lt;/li&gt;
&lt;li&gt;Easy to update the app's code to use a new version of a route&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  d. &lt;strong&gt;No Version inter-dependency&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;When managing multiple versions, it's important to be able to make changes to a specific version without affecting others. For example, if there is a bug in version 1.0.0, you should be able to fix it without impacting version 2.0.0. Releasing a new version should not affect existing versions, thereby avoiding the risk of introducing new bugs into old versions. This approach ensures that older versions remain stable and reliable, eliminating the need for unnecessary quality checks on all versions with every release. This strategy should allow for better control over the quality of older versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  e. &lt;strong&gt;Traceability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It should be easy to know which version of a route is being used by which version of the app and vice versa.&lt;/p&gt;

&lt;p&gt;This allows for better tracking of the usage of the different versions of the routes, helping on decisions for when to delete or update existing routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  f. &lt;strong&gt;Deployment process&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The chosen solution should make the deployment process simple. It should be easy to deploy a new version of a route.&lt;/p&gt;

&lt;h2&gt;
  
  
  g. &lt;strong&gt;Set Up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The effort required to set up the solution should be taken into account. The solution should be easy to set up and maintain.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. &lt;strong&gt;Evaluation of the different solutions&lt;/strong&gt;
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Criteria&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Infrastructure Level Versioning&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Code Level Versioning&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;BFF Level Versioning&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Correcting Critical Bugs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;▲ Capable of addressing fixes; requires identifying and applying fixes individually to each version, followed by re-deployment for each.&lt;/td&gt;
&lt;td&gt;✔ Single fix in the codebase with a single re-deployment.&lt;/td&gt;
&lt;td&gt;▲ A single fix is possible if it pertains to business logic; otherwise it's similar to infrastructure level versioning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;No Version inter-dependency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✔ Completely isolated instances&lt;/td&gt;
&lt;td&gt;✘ Changes in the code might impact multiple versions.&lt;/td&gt;
&lt;td&gt;▲ Changes in the main backend (business logic) can influence multiple versions, but a change to a route has no impact on other versions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maniability: route creation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✔ Simple route creation.&lt;/td&gt;
&lt;td&gt;✔ Simple route creation.&lt;/td&gt;
&lt;td&gt;✔ Simple route creation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maniability: route update&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;▲ Update requires some effort: need to modify the concerned route on the different branches associated to each version that we want to update. requires a redployment of the pods associated to the concerned versions.&lt;/td&gt;
&lt;td&gt;✔ Simple route update&lt;/td&gt;
&lt;td&gt;▲ Update requires some effort when the change is on the BFF level. Easy to update when the change is in the main backend.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maniability: route deletion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✔ Deletion doesn't require developer intervention: The pod is automatically terminated when no routes for the specific version are in use. However, you cannot delete a single route without deleting all routes for that version(The whole pod is terminated).&lt;/td&gt;
&lt;td&gt;▲ Requires developper intervention. Can delete a single route of a specific version.&lt;/td&gt;
&lt;td&gt;✔ Doesn't require developper intervention, Can not delete a single route without deleting all routes for that version.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traceability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✔ Easy to track.&lt;/td&gt;
&lt;td&gt;✔ Easy to track.&lt;/td&gt;
&lt;td&gt;✔ Easy to track.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment Process&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✘ Complex: multiple instances manage&lt;/td&gt;
&lt;td&gt;✔ Simple: single deployment process&lt;/td&gt;
&lt;td&gt;✘ Complex: multiple instances manage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Set Up&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✘ Complex&lt;/td&gt;
&lt;td&gt;✔ Simple&lt;/td&gt;
&lt;td&gt;✘ The most complex: requires a good separation of the buisness logic, and the features&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;✔: Solution meets the criterion effectively.&lt;/li&gt;
&lt;li&gt;▲: Solution moderately satisfies the criterion.&lt;/li&gt;
&lt;li&gt;✘: Solution does not meet the criterion or makes it more complex.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  6. Our choice
&lt;/h1&gt;

&lt;p&gt;After evaluating the different versioning strategies, we opted for code-level versioning. Here’s why:&lt;/p&gt;

&lt;p&gt;Complexity: We ruled out infrastructure-level versioning due to its complexity in setup and maintenance. Additionally, it was crucial for us that any change in business logic affects all app versions, which infrastructure-level versioning does not guarantee.&lt;/p&gt;

&lt;p&gt;Independence: While BFF level versioning provides some degree of version independence, it also introduces significant setup and maintenance challenges. Furthermore, it doesn’t ensure full version independence.&lt;/p&gt;

&lt;p&gt;Business Logic Separation: By effectively separating the business logic from data formatting and access, code-level versioning can offer the same benefits as BFF level versioning. In our scenario, complete independence between versions is not critical, thanks to our comprehensive test suite that catches most bugs introduced by changes. Additionally, we are working towards automating end-to-end tests to identify critical bugs in older app versions.&lt;/p&gt;

&lt;p&gt;Practicality: Given our context, simplicity is key. Code-level versioning emerged as the most straightforward and practical solution, aligning well with our testing strategies and maintenance capabilities.&lt;/p&gt;

&lt;p&gt;Therefore, we settled on code-level versioning as it meets our needs without adding unnecessary complexity.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I have explained the importance of API versioning and presented three solutions—infrastructure-level, BFF-level, and code-level versioning—each with its own pros and cons.&lt;/p&gt;

&lt;p&gt;If you are managing an application where backend and frontend evolve independently, versioning is crucial. I suggest starting by determining your priorities and assessing the most important criteria, and I hope this article will help you to select the best versioning strategy for your needs.&lt;/p&gt;

</description>
      <category>api</category>
    </item>
    <item>
      <title>Learn how to implement VAT for your e-commerce website with Sylius in 6 minutes</title>
      <dc:creator>Charlier</dc:creator>
      <pubDate>Wed, 24 Apr 2024 14:59:32 +0000</pubDate>
      <link>https://dev.to/theodo/learn-how-to-implement-vat-for-your-e-commerce-website-with-sylius-in-6-minutes-m3m</link>
      <guid>https://dev.to/theodo/learn-how-to-implement-vat-for-your-e-commerce-website-with-sylius-in-6-minutes-m3m</guid>
      <description>&lt;p&gt;If you are using Sylius for your e-commerce website, then you will inevitably come across the subject of Value Added Tax (VAT). As a software engineer at &lt;a href="https://www.theodo.fr/" rel="noopener noreferrer"&gt;Theodo&lt;/a&gt;, I have spent countless hours customizing every one of its aspects, for it to behave in the exact way I wanted it to. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So you don't need to! Here are the main steps you can follow to avoid wasting any time!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of contents&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Setting up your first VAT engine 🔧

&lt;ol&gt;
&lt;li&gt;
Tax category 🏷&lt;/li&gt;
&lt;li&gt;
Zone 🌍&lt;/li&gt;
&lt;li&gt;
Tax rate 💸&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
How does Sylius VAT engine works? 🧠

&lt;ol&gt;
&lt;li&gt;
The Order Processor ✨&lt;/li&gt;
&lt;li&gt;
The Applicators 👨‍💻&lt;/li&gt;
&lt;li&gt;
The Calculators 🧮&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
My tips and tricks 🥇

&lt;ol&gt;
&lt;li&gt;
Changing the default address 🏠&lt;/li&gt;
&lt;li&gt;
Multiple zones for one country 🌐&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
Conclusion: customize your website 🔥&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;h2 id="setting-up-the-engine"&gt;Setting up your first VAT engine 🔧&lt;/h2&gt;
&lt;/h2&gt;

&lt;p&gt;Sylius VAT handling revolves around three distinct entities, allowing it to choose which rate it has to apply. One of these entities is linked to the article - &lt;strong&gt;the tax category&lt;/strong&gt;, another is linked to the customer - &lt;strong&gt;the zone&lt;/strong&gt;, and the last one connects the latter two - &lt;strong&gt;the tax rate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gjuh2eez0k9pb7w2hwm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gjuh2eez0k9pb7w2hwm.jpg" alt="A schema representing the different entities involved in Sylius VAT calculation" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;h3 id="tax-category"&gt;Tax Category 🏷&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;The tax category is just a category for your product, which defines which rates should be applied. &lt;strong&gt;It allows you to sell products which aren’t taxed for the same rate&lt;/strong&gt; - for example, clothes and food.&lt;br&gt;
You’ll need to add all of your tax categories to your fixtures (the base data you want to have every time you restart your shop - see &lt;a href="https://docs.sylius.com/en/1.12/book/architecture/fixtures.html" rel="noopener noreferrer"&gt;Sylius documentation&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;fixtures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;tax_category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;custom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;food&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Food'&lt;/span&gt;
                        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;food'&lt;/span&gt;
                    &lt;span class="na"&gt;clothes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Clothes'&lt;/span&gt;
                        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clothes'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;h3 id="zone"&gt;Zone 🌍&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;The zone is a Sylius concept which is way broader than the VAT: it is &lt;strong&gt;either a group of countries, a group of provinces, or a group of other zones&lt;/strong&gt;. Here, what we will be looking at is the zone of the customer. A zone has an interesting characteristic, called scope, which defines what it can be used for: ‘shipping’, ‘tax’, or ‘all’. A zone with a shipping scope is only used to calculate shipping methods and fees, whereas a zone with tax scope is only used to calculate VAT. If the scope is all, the zone will be used for both.&lt;br&gt;
You’ll need to define the different zones that you need to your fixtures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;fixtures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;geographical&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;countries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FR'&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DE'&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;US'&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GB'&lt;/span&gt;
                &lt;span class="na"&gt;zones&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;EUROPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Europe'&lt;/span&gt;
                        &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tax'&lt;/span&gt;
                        &lt;span class="na"&gt;countries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FR'&lt;/span&gt;
                            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DE'&lt;/span&gt;
                    &lt;span class="na"&gt;AMERICA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;America'&lt;/span&gt;
                        &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tax'&lt;/span&gt;
                        &lt;span class="na"&gt;countries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;US'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;h3 id="tax-rate"&gt;Tax Rate 💸&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, a tax rate is an entity that defines &lt;strong&gt;all the necessary information for Sylius to calculate the amount of taxes&lt;/strong&gt; your customer should pay. This must at least contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a tax category: which of the products are concerned&lt;/li&gt;
&lt;li&gt;a zone: which of your customers are concerned&lt;/li&gt;
&lt;li&gt;an amount: how much is the VAT ? For 20%, you have to write 0.2.&lt;/li&gt;
&lt;li&gt;a calculator: how should the VAT be calculated ? Sylius provides a calculator named default that does a basic division, but you can define and provide others if you need to.&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;isIncludedInPrice&lt;/code&gt; boolean: is the VAT included in the price that you defined initially for the article ?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last field, &lt;code&gt;isIncludedInPrice&lt;/code&gt;, might seem enigmatic at first glance, however it is quite simple. It defines how the VAT should be calculated. For example, for a T-shirt costing 12€, and a tax rate of 20%:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;code&gt;isIncludedInPrice&lt;/code&gt; is false, that means that the 12€ was tax free, and the final price is 14.40 € (2.40€ of VAT included)&lt;/li&gt;
&lt;li&gt;is &lt;code&gt;isIncludedInPrice&lt;/code&gt; is true, that means that the 12€ was tax included, and Sylius will keep track of 2€ of this being taxes, without changing the total amount.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll need to define &lt;strong&gt;a tax rate for every couple of zone and tax category&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;clothes-europe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clothes-europe'&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Tax&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;clothes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Europe'&lt;/span&gt;
        &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clothes'&lt;/span&gt;
        &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EUROPE'&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;
        &lt;span class="na"&gt;calculator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default'&lt;/span&gt;
        &lt;span class="na"&gt;included_in_price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;clothes-america&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clothes-america'&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Tax&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;clothes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;America'&lt;/span&gt;
        &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clothes'&lt;/span&gt;
        &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AMERICA'&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.15&lt;/span&gt;
        &lt;span class="na"&gt;calculator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default'&lt;/span&gt;
        &lt;span class="na"&gt;included_in_price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;food-europe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;food-europe'&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Tax&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;food&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Europe'&lt;/span&gt;
        &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;food'&lt;/span&gt;
        &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EUROPE'&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;
        &lt;span class="na"&gt;calculator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default'&lt;/span&gt;
        &lt;span class="na"&gt;included_in_price&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only need to setup a basic VAT engine, without any added complexity, then you’re good to go! Jump to your code editor and try it out! &lt;strong&gt;Sylius will handle all the calculations on its own&lt;/strong&gt;, and add the correct adjustments (modifications of price) to your articles automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;h2 id="understanding-sylius-vat"&gt;How does Sylius VAT engine works? 🧠&lt;/h2&gt;
&lt;/h2&gt;

&lt;p&gt;However, &lt;strong&gt;if you need a custom behavior of your engine&lt;/strong&gt; (for example: billing a commission and adding VAT to it), or if you want a deeper understanding of this system, I’ll dive further into the engine now.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;h3 id="order-processor"&gt;The Order Processor ✨&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;The VAT engine is included in the OrderProcessor: &lt;strong&gt;a list of operations that are executed every time a cart is modified&lt;/strong&gt; (for example, it is triggered by adding an item to cart, changing the shipping address, etc…). That means that the VAT will be erased and recalculated every time your customer modifies its cart. This includes recalculating the shipping fees, the VAT, ensuring that every item is sold at the correct price, etc...&lt;/p&gt;

&lt;p&gt;When the OrderProcessor asks for a VAT calculation, it calls a service called OrderTaxesProcessor, whose only goal is to call some Applicators. Applicators are where the magic happens, and are responsible for &lt;strong&gt;finding the correct tax amount&lt;/strong&gt;, and &lt;strong&gt;adding this amount to the order&lt;/strong&gt;. Each object that can generate VAT has a dedicated Applicator: there is one for the OrderItem, one for the shipping fees, etc…&lt;/p&gt;

&lt;p&gt;Note that, prior to asking for a VAT calculation, the OrderProcessor calls another service which &lt;strong&gt;removes the taxes from the order&lt;/strong&gt;. That allows us to calculate the VAT again, from an empty and controlled state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvhqae7fqkrha1qq17ul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvhqae7fqkrha1qq17ul.png" alt="A schema of the different steps of the Order Processor used by Sylius" width="800" height="1077"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Schema of the different steps of the Order Processor from &lt;a href="https://docs.sylius.com/en/1.12/" rel="noopener noreferrer"&gt;Sylius documentation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;h3 id="applicators"&gt;The Applicators 👨‍💻&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;An applicator will do the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find the zone&lt;/strong&gt; which corresponds to the current cart. If the order has a billing address, it uses it to find the zone. Otherwise, it uses the default zone of your channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the tax category&lt;/strong&gt; of every item in the cart.&lt;/li&gt;
&lt;li&gt;For each item, &lt;strong&gt;look for a tax rate&lt;/strong&gt; that has both the correct tax category and zone.&lt;/li&gt;
&lt;li&gt;If a tax rate is found, &lt;strong&gt;adding VAT adjustments&lt;/strong&gt; with the correct rate, otherwise do nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;h3 id="calculators"&gt;The Calculators 🧮&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;The Sylius VAT calculators are some pieces of code that handle &lt;strong&gt;the calculation of the correct tax amounts&lt;/strong&gt;. They are given a price and a tax rate, and they return the correct amount of vat. Sylius, by default, provides one that calculates the VAT the way you expect it (a basic division), but if you need to implement some custom logic here (for example, no VAT at all for product that cost less than 5€), you can do so easily.&lt;/p&gt;

&lt;p&gt;The only things to do are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create a service&lt;/strong&gt; which implements the Sylius CalculatorInterface with your own logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Register it in your services&lt;/strong&gt; with the dedicated tag and the name you want to use in your fixtures.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// NoTaxesIfPriceLowerThanFiveCalculator.php&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NoTaxesIfPriceLowerThanFiveCalculator&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CalculatorInterface&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;TaxRateInterface&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;float&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="nv"&gt;$base&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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="mi"&gt;0&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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isIncludedInPrice&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="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$base&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAmount&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="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAmount&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// services.yaml&lt;/span&gt;
    &lt;span class="nc"&gt;App\Taxation\Calculator\NoTaxesIfPriceLowerThanFiveCalculator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="o"&gt;-&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="s1"&gt;'sylius.tax_calculator'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'no_taxes_if_price_lower_than_five'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;h2 id="tips-and-tricks"&gt;My tips and tricks 🥇&lt;/h2&gt;
&lt;/h2&gt;

&lt;p&gt;Now you know almost everything that you need to customize and code tax calculations with Sylius with ease. However, there is still &lt;strong&gt;some details and tricks&lt;/strong&gt; that I discovered which can be useful to anyone working with this system.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;h3 id="changing-default-address"&gt;Changing the default address 🏠&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;By default, to calculate the zone which corresponds to the current cart, Sylius uses the billing address. However, if you want it to &lt;strong&gt;use the shipping address&lt;/strong&gt; instead, you don't have to add any code or to overwrite anything. There is an optional parameter that you can add to your yaml configuration, and which does exactly that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;sylius_core&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;shipping_address_based_taxation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;h3 id="multiple-zones-by-country"&gt;Multiple zones for one country 🌐&lt;/h3&gt;
&lt;/h3&gt;

&lt;p&gt;If a country is attached to multiple zones having 'tax' as their scope, then issues may arise. Indeed, &lt;strong&gt;Sylius expects a country to have a single zone&lt;/strong&gt; available for tax, and, if it finds more that one, it will &lt;strong&gt;randomly return one of them&lt;/strong&gt;. This will leave you with unexpected behaviors, which can cause severe bugs. Instead, you should split your countries into multiple zones, until no country is present multiple times.&lt;/p&gt;

&lt;p&gt;Example: I deliver food and clothes in FR, IE (Ireland) and UK. Food has a 10% rate in every country, but clothes are only taxed in FR with 15% rate. I would like to create a zone containing every country (EUROPE), and a zone containing only FR (FRANCE), so that I can create two tax rates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one for EUROPE, food, with a rate of 10%&lt;/li&gt;
&lt;li&gt;the other for FRANCE, clothes, and 15%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, this won't work, as France is included in two different zones. Instead, I have to declare a zone containing IE and UK (GREAT-BRITAIN) and a zone containing FR (FRANCE), and then create 3 tax rates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one for GREAT-BRITAIN, food, with a rate of 10%&lt;/li&gt;
&lt;li&gt;one for FRANCE, food, with 10%&lt;/li&gt;
&lt;li&gt;and one for FRANCE, clothes, and 15%&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;h2 id="conclusion"&gt;Conclusion: customize your website 🔥&lt;/h2&gt;
&lt;/h2&gt;

&lt;p&gt;You can now easily twist the Sylius VAT engine to your needs, and explore its features in detail. And the best part is that you can replace any part of this engine! Thanks to the Sylius architecture using numerous specialized services, you can easily override and replace any code that doesn't exactly meet your needs, allowing you to create the e-commerce website that exactly fits your business.&lt;/p&gt;

</description>
      <category>php</category>
      <category>backend</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to generate Typescript interfaces from your Spring Boot backend</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Tue, 09 Apr 2024 12:02:00 +0000</pubDate>
      <link>https://dev.to/theodo/how-to-generate-typescript-interfaces-from-your-spring-boot-backend-kfk</link>
      <guid>https://dev.to/theodo/how-to-generate-typescript-interfaces-from-your-spring-boot-backend-kfk</guid>
      <description>&lt;p&gt;Starting a full stack project with Spring Boot and a modern frontend framework like React in Typescript, you rapidly fall into the issue of defining your interfaces twice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once on Spring Boot side where you create your response/request &lt;a href="https://en.wikipedia.org/wiki/Data_transfer_object" rel="noopener noreferrer"&gt;DTO&lt;/a&gt; for your controllers,&lt;/li&gt;
&lt;li&gt;and again on the frontend where you have to write those interfaces again in Typescript this time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not only is this poor developer experience, leading to a loss in productivity, it simply leads to bugs as you can easily forget to upload your interfaces on one side.&lt;/p&gt;

&lt;p&gt;Great news! Avoiding this pitfall is actually possible and easy ?! with a quick setup, which is the topic of the following guide, let's go!&lt;/p&gt;

&lt;h2&gt;
  
  
  How can we generate Typescript interfaces from our Spring Boot backend
&lt;/h2&gt;

&lt;p&gt;Advancement in code generation and support of standards like Swagger/Open API to describe REST APIs allow for effective code generation. Meaning we only need to set up a proper Swagger on our backend api in Spring Boot to benefit from interface generation for all our interfaces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvbz4431jida4ku0ygx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvbz4431jida4ku0ygx4.png" alt="Code generation flow: a Spring Boot api export a Swagger which is read by our code generation tool, Orval. Orval then generate all our frontend interfaces in Typescript." width="686" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;We will need to expose a Swagger on Spring Boot application and then to configure our code generation tool to target it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up SpringDoc
&lt;/h3&gt;

&lt;p&gt;To generate our Swagger we will use SpringDoc as SpringFox is outdated and less maintained. (&lt;a href="https://springdoc.org/migrating-from-springfox.html" rel="noopener noreferrer"&gt;You can even relatively easily migrate from SpringFox to SpringDoc&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Setting up SpringDoc with Maven is classic, &lt;a href="https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui" rel="noopener noreferrer"&gt;get the latest version&lt;/a&gt;, add it to your pom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Swagger --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springdoc&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;springdoc-openapi-ui&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.6.9&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using some sort of authentication, you will need to allow unauthenticated access to the Swagger, eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... rest of your auth&lt;/span&gt;
 &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebSecurity&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Allow Swagger to be accessed without authentication&lt;/span&gt;
    &lt;span class="n"&gt;web&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ignoring&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api-docs"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api-docs/swagger-config"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/swagger-ui.html"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/swagger-ui/**"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which you can limit to dev environment if you are not comfortable allowing your backend users to discover your API endpoints using Swagger, to do so use profiles/environment variables as &lt;a href="https://springdoc.org/#disabling-the-springdoc-openapi-endpoints" rel="noopener noreferrer"&gt;you can easily disable SpringDoc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now start or restart your Spring Boot application, you should be able to get your Swagger (JSON) on &lt;code&gt;http://server:port/context-path/v3/api-docs&lt;/code&gt;, for the application created for this guide it's: &lt;code&gt;http://localhost:8080/guide/v3/api-docs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One additional step is to configure your Swagger to regroup enums usage definition (meaning when you use an enum in two DTO you will expose only one enum definition in Swagger instead of duplicating the definition):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OpenApiConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// use Reusable Enums for Swagger generation:&lt;/span&gt;
    &lt;span class="c1"&gt;// see https://springdoc.org/#how-can-i-apply-enumasref-true-to-all-enums&lt;/span&gt;
    &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;swagger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;v3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jackson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ModelResolver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enumsAsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ... you can also describe your api bellow&lt;/span&gt;
  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OpenAPI&lt;/span&gt; &lt;span class="nf"&gt;openAPI&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My shiny API"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My shiny API description"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v0.0.1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;externalDocs&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExternalDocumentation&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Documentation"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://my.shiny.api.doc/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="c1"&gt;// can simply be a link to a README&lt;/span&gt;
      &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup is enough for code generation, however I recommend to dive deeper into SpringDoc, it also offers you an UI &lt;code&gt;http://server:port/context-path/swagger-ui.html&lt;/code&gt; which can be an excellent way to document your api for your clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Orval
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://orval.dev/" rel="noopener noreferrer"&gt;Orval&lt;/a&gt; is a recent code generator that propose code generation up to API client generation with the tool of your choice: fetch, Axios, React Query, SWR. For this guide, I will cover interface generation only, however if you are starting a project, I highly recommend setting up client generation as well.&lt;/p&gt;

&lt;p&gt;First install Orval as a dev dependency on your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;orval &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then create an &lt;code&gt;orval.config.ts&lt;/code&gt; file in your frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orval&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;evo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;model/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// enable/disable test mock generation&lt;/span&gt;
      &lt;span class="c1"&gt;// I recommend enabling this option if you generate an api client&lt;/span&gt;
      &lt;span class="na"&gt;prettier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// recommended if you use prettier&lt;/span&gt;
      &lt;span class="na"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// recreate the whole folder (avoid outdated files)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// use your own Swagger url: http://server:port/context-path/v3/api-docs&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080/guide/v3/api-docs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your backend is running, you can generate your interface running &lt;code&gt;npx orval --config ./orval.config.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I recommend setting up a script in package.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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gen:types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orval --config ./orval.config.ts"&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;That's it! Now you only need to launch this command to update your frontend interface when you modify your backend, no more duplication 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;p&gt;You can go a bit further as mentioned in passing in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SpringDoc provides a great UI to self document your API, and you can customize the Swagger generated for your clients,&lt;/li&gt;
&lt;li&gt;Orval has many features including client generation, creating mock for your API, I &lt;em&gt;highly&lt;/em&gt; recommend looking into it,&lt;/li&gt;
&lt;li&gt;the next step would be to automate client side type generation in CI to test that every pull request has up to date types.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>springboot</category>
      <category>typesafe</category>
      <category>devex</category>
    </item>
    <item>
      <title>Mastering File Upload Security: DoS and Antivirus</title>
      <dc:creator>Marek Elmayan</dc:creator>
      <pubDate>Wed, 20 Mar 2024 13:45:00 +0000</pubDate>
      <link>https://dev.to/theodo/mastering-file-upload-security-dos-and-antivirus-1me9</link>
      <guid>https://dev.to/theodo/mastering-file-upload-security-dos-and-antivirus-1me9</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mitigate Denial of Service (DoS) threats by imposing restrictions on both the size of files and the quantity of uploads permitted on your server.&lt;/li&gt;
&lt;li&gt;Implement antivirus scanning for files that users download, ensuring protection against malware infections.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  File Upload vulnerabilities and security challenges
&lt;/h2&gt;

&lt;p&gt;Incorporating a file upload feature into your website introduces a spectrum of vulnerabilities that can compromise not only your servers but also the integrity and safety of your users' data.&lt;/p&gt;

&lt;p&gt;This article aims to shed light on the critical security challenges associated with file uploads and offers a roadmap to safeguard your digital infrastructure against malicious attacks. We'll explore strategies to prevent attackers from exploiting file upload functionalities to overload and destabilize your servers. Additionally, considering the risks posed when users download files uploaded by others, we emphasize the importance of integrating robust antivirus scanning processes.&lt;/p&gt;

&lt;h2 id="DoS-attacks"&gt;⚡️ Denial of Service (DoS) attacks&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Denial of Service (DoS)&lt;/strong&gt; attack is a malicious attempt to &lt;strong&gt;disrupt&lt;/strong&gt; the normal &lt;strong&gt;functioning&lt;/strong&gt; of a network, service, website, or computer system by overwhelming it with a &lt;strong&gt;flood&lt;/strong&gt; of traffic or requests. The primary objective of a DoS attack is to make a target system or network unavailable to its users, thereby denying access to &lt;strong&gt;legitimate&lt;/strong&gt; users. In our case, uploading specific files can result in service disruptions for &lt;strong&gt;unsafe&lt;/strong&gt; server &lt;strong&gt;configurations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Considering a file upload stream what are the possibilities to block the traffic?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Voluminous File Upload:&lt;/strong&gt; This vulnerability occurs when an attacker uploads an extremely large file, often much larger than what the system is designed to handle. Such a file can consume all available server resources, such as disk space, memory, and processing power, causing the system to slow down or even crash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxin6nnxo45tr9usdl4zu.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%2Fxin6nnxo45tr9usdl4zu.png" alt="An illustration of a cybersecurity attack: a hooded hacker uses a laptop, sending a large file marked 'DANGER' with an upload arrow to a server, where a red lightning bolt signifies a breach." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Small File Upload:&lt;/strong&gt; In this scenario, an attacker uploads a large number of relatively small files. While individual files may not be large, the cumulative impact can lead to resource depletion or storage space exhaustion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrrqj9weydeo025vias2.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%2Fjrrqj9weydeo025vias2.png" alt="An illustration of a cybersecurity attack: a hooded hacker on the left uses a laptop, with arrows leading from him to twelve small documents marked with green upload arrows, then to a server hit by a red lightning bolt, signifying a system breach." width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="prevent-DoS"&gt;🛡️ How to prevent file upload DoS attacks from happening?&lt;/h3&gt;

&lt;p&gt;To deal with these vulnerabilities, it's essential to implement proper security measures in the backend part of your file upload feature. It’s relatively easy and straightforward to do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set&lt;/strong&gt; appropriate file &lt;strong&gt;size limits&lt;/strong&gt; to prevent the upload of excessively large files that could overload your system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set&lt;/strong&gt; a &lt;strong&gt;limit&lt;/strong&gt; to the &lt;strong&gt;number&lt;/strong&gt; of simultaneous files that can be uploaded at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an elementary Typescript implementation example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&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;maxFileSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 50 MB limit&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadedFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&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;files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Assuming 'files' is an array of File objects in the request body&lt;/span&gt;

&lt;span class="c1"&gt;// Check file size and number of files&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;uploadedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxFiles&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Too many files uploaded.&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="k"&gt;for &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;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;uploadedFiles&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxFileSize&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;File size exceeds the limit.&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="c1"&gt;// Handle the file, e.g., save it to the server&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Files uploaded successfully!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More general tips to prevent any type of DoS attacks are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing &lt;strong&gt;rate limiting&lt;/strong&gt; for file uploads to prevent the rapid uploading of numerous small files. This helps control the flow of incoming data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP blocking&lt;/strong&gt; → blocking traffic from known or suspected malicious sources to prevent DoS traffic from reaching its target. You can find lists of abusive IPs, for instance on &lt;a href="https://www.abuseipdb.com/" rel="noopener noreferrer"&gt;abuseipdb.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Using a &lt;strong&gt;Content Delivery Networks&lt;/strong&gt; (CDNs) such as &lt;a href="https://www.cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;, can help absorb large amounts of traffic and mitigate the impact of DoS attacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Additional benefits of DoS protection measure: 💰 &amp;amp; 🌱
&lt;/h3&gt;

&lt;p&gt;Using &lt;strong&gt;smaller and fewer files&lt;/strong&gt; can bring also other &lt;strong&gt;benefits&lt;/strong&gt; to you and the environment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;strong&gt;less quantity&lt;/strong&gt; to store on your server, you will have &lt;strong&gt;better performances&lt;/strong&gt; and &lt;strong&gt;less to pay&lt;/strong&gt; at the end of the month.&lt;/li&gt;
&lt;li&gt;Moreover, less storage usage will &lt;strong&gt;reduce the environmental impact&lt;/strong&gt; of manufacturing, operating, and cooling data centers.&lt;/li&gt;
&lt;li&gt;Sending voluminous files is also costly so even though front-end file size validation isn’t enough for security measure it can help to reduce the environmental impact of the uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="zip-bomb"&gt;💣 What is a Zip Bomb? A type of DoS attack&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Zip Bomb&lt;/strong&gt;, also known as a Zip of Death or decompression bomb, is a &lt;strong&gt;malicious archive file&lt;/strong&gt; designed to crash or render useless the program or system reading it. It's a &lt;strong&gt;type of DoS attack&lt;/strong&gt;. The concept behind a Zip Bomb is to exploit the file compression algorithm's ability to highly compress a large amount of data. A Zip Bomb file appears small and innocuous but contains an &lt;strong&gt;enormous amount of data when decompressed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a targeted system tries to decompress such a file, it can exhaust system resources due to the disproportionally large size of the decompressed data compared to the compressed file. For example, a Zip Bomb could be a file that is only a few kilobytes in size but expands to several gigabytes or even terabytes when decompressed. This can lead to &lt;strong&gt;system crashes&lt;/strong&gt;, slowdowns, or can render the system inoperable, thus achieving the attacker's goal of &lt;strong&gt;denying service&lt;/strong&gt; to legitimate users.&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%2Fckjodf12kq0xy0chwzty.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%2Fckjodf12kq0xy0chwzty.png" alt="An illustration of a cyber attack sequence: a figure in a purple hood uses a laptop with a skull emblem, symbolizing a hacker. At the center, a yellow folder labeled 'ZIP' with a bomb indicates a malicious 'zip bomb.' To the right, an explosion in front of a cloud and servers shows the zip bomb's impact in a cloud environment." width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zip Bombs are not only limited to Zip files; they can be created in any file format that supports compression, such as RAR or 7z. Accepting an archive file is always a delicate matter, and decompressing it on your server even more so. The defense against Zip Bombs involves careful scanning and validation of input files, limiting the maximum size of decompressed data, and using updated antivirus and anti-malware programs capable of detecting such threats.&lt;/p&gt;

&lt;h2 id="antivirus-scanning"&gt;🕵️‍♂️ Antivirus scanning for user protection&lt;/h2&gt;

&lt;p&gt;The purpose of uploading files is very often to share them with other people thus another user is supposed to download the file afterwards. These users can inadvertently become targets of malicious activity when using file upload features on web applications. In many cases, attackers may attempt to exploit the trust of users by uploading files that appear harmless but, in reality, contain malware or other malicious content. This can include infected documents, executables, or scripts. When these files are downloaded and executed on a user's system, it can lead to compromised security and potential data breaches.&lt;/p&gt;

&lt;p&gt;If the uploaded files are &lt;strong&gt;retrievable&lt;/strong&gt;, to safeguard both your platform and mainly your users, it is &lt;strong&gt;crucial&lt;/strong&gt; to run an up-to-date &lt;strong&gt;virus scanner&lt;/strong&gt;. Virus scanners are very adept at spotting malicious files masquerading as a different file type, invisible to most unaware users. This proactive measure can &lt;strong&gt;automatically check&lt;/strong&gt; each uploaded file for potential threats, helping ensure that users are not unwittingly exposed to malicious content.&lt;/p&gt;

&lt;p&gt;Running an antivirus can also help you secure your server, because it can detect potentially malicious files that succeeded in bypassing all your previous security checks.&lt;/p&gt;

&lt;p&gt;Before really uploading a file you can &lt;strong&gt;isolate&lt;/strong&gt; it and run an antivirus to identify potential threats, then only finally uploading it. This way all potential threats are being &lt;strong&gt;neutralised&lt;/strong&gt; before having access to sensitive data.&lt;/p&gt;

&lt;h3 id="antivirus-recommendations"&gt;⭐️ Recommendations to go beyond simple antivirus scanning&lt;/h3&gt;

&lt;p&gt;For maximal safety, you can implement &lt;strong&gt;multi-scanning&lt;/strong&gt;. For each file run multiple anti-malware engines, which can be &lt;strong&gt;parallelised&lt;/strong&gt;, in order to get the highest detection rate and the shortest window of exposure to malware outbreaks. Here are some reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Malware can &lt;strong&gt;bypass&lt;/strong&gt; a single antivirus engine.&lt;/li&gt;
&lt;li&gt;Different anti-virus vendors have different &lt;strong&gt;response times&lt;/strong&gt; to outbreaks due to their location and focused markets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False positives&lt;/strong&gt; in virus detection are a common side-effect of any malware scanning solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lastly, if you have to choose between an &lt;strong&gt;Online&lt;/strong&gt; and an &lt;strong&gt;Offline&lt;/strong&gt; antivirus, you should probably prefer an online solution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you use offline antivirus software, you have to download and install it in your system. While &lt;strong&gt;online&lt;/strong&gt; virus scanner does &lt;strong&gt;not require&lt;/strong&gt; any &lt;strong&gt;storage space&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Protection &amp;amp; Regular Updates&lt;/strong&gt;: Online antivirus software constantly updates its virus definitions and scans your system in real-time, providing immediate protection against the latest threats. Online antivirus solutions automatically receive updates and patches to keep up with the evolving threat landscape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-Based&lt;/strong&gt;: Online antivirus relies on cloud servers to analyse files and detect malware. This reduces the strain on your computer's resources and can be more efficient since the processing part is usually done on high-speed cloud-based servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Detection&lt;/strong&gt;: Online antivirus solutions often use behavioural analysis and machine learning algorithms to identify previously unseen malware, improving their ability to detect new threats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less Control &amp;amp; Privacy&lt;/strong&gt;: Data sent to external servers and no customisation according to personal preferences are the main drawbacks that would make you prefer an offline solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My recommendation would be to use &lt;a href="https://www.clamav.net/" rel="noopener noreferrer"&gt;ClamAV&lt;/a&gt; an open source antivirus engine. It is a versatile tool designed to detect multiple types of threats from numerous file formats and other use cases (cross-platform, integration such as mail server). Finally, it is updated on a daily basis, ensuring protection against the latest known threats. This rapid update cycle is crucial for an antivirus tool to be effective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up AWS antivirus scanning for files in S3 Buckets
&lt;/h3&gt;

&lt;p&gt;You can find a guide on the AWS blog, detailing &lt;a href="https://aws.amazon.com/fr/blogs/apn/integrating-amazon-s3-malware-scanning-into-your-application-workflow-with-cloud-storage-security/" rel="noopener noreferrer"&gt;how to integrate Amazon S3 Malware Scanning into your web App&lt;/a&gt;. &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;Amazon Simple Storage Service (Amazon S3)&lt;/a&gt; is a highly scalable object storage service that allows file storage. Currently, when a file is uploaded to an S3 Bucket, two scanning engines are available: &lt;strong&gt;Sophos&lt;/strong&gt; and &lt;strong&gt;ClamAV&lt;/strong&gt;, to detect malicious ones. Additionally, both engines can be used together for an even higher safety.&lt;/p&gt;

&lt;h3 id="asynchronous-scanning"&gt;Asynchronous antivirus scanning to enhance performance and UX&lt;/h3&gt;

&lt;p&gt;Running an extensive antivirus engine can be quite &lt;strong&gt;time-consuming&lt;/strong&gt; thus implementing &lt;strong&gt;asynchronous&lt;/strong&gt; antivirus scanning can significantly &lt;strong&gt;enhance&lt;/strong&gt; both &lt;strong&gt;performance&lt;/strong&gt; and &lt;strong&gt;user experience&lt;/strong&gt; in systems that require real-time or near-real-time processing. This approach involves decoupling the scanning process from the main application flow, allowing the application to continue executing without waiting for the scan to complete.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a user uploads a file, the &lt;strong&gt;server immediately acknowledges receipt&lt;/strong&gt; and begins the upload process, providing a responsive experience.&lt;/li&gt;
&lt;li&gt;The file is being &lt;strong&gt;processed in the background&lt;/strong&gt;, scanned and uploaded if successful or either deleted or quarantined if not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send a status update&lt;/strong&gt; on the upload process to the user, such as through email notifications, dashboard updates, or real-time alerts using web sockets.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="race-conditions"&gt;🏎️ Race conditions applied to file upload&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://portswigger.net/web-security/race-conditions" rel="noopener noreferrer"&gt;Race conditions in cybersecurity&lt;/a&gt; refer to a scenario where the outcome of a process is unexpectedly altered due to the &lt;strong&gt;timing&lt;/strong&gt; of events not happening as planned. This occurs in a concurrent environment where multiple processes or threads execute in &lt;strong&gt;overlapping timeframes&lt;/strong&gt;, leading to &lt;strong&gt;conflicts&lt;/strong&gt; in accessing or modifying shared resources. The security risk arises when an attacker can &lt;strong&gt;manipulate&lt;/strong&gt; the timing of actions to gain unauthorized access or privileges, modify data, or exploit system functionality in unintended ways.&lt;/p&gt;

&lt;p&gt;In the context of file upload vulnerabilities, race conditions can be particularly concerning. There are multiple scenarios where the file security checks could be bypassed. Here is one example:&lt;/p&gt;

&lt;p&gt;Imagine a web application that allows users to upload files. The application checks the file for security purposes (e.g., ensuring it's not a malicious executable) before moving it to a permanent storage location. A race condition vulnerability could occur if an attacker manages to manipulate the timing of the upload process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initial Upload&lt;/strong&gt;: The attacker begins by uploading a benign file that passes the application's security checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing Manipulation&lt;/strong&gt;: Right after the initial check but before the file is moved to its permanent location, the attacker quickly replaces the benign file with a malicious one, exploiting the time gap in the process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution of Malicious File&lt;/strong&gt;: The application, believing the file to be safe, moves it to permanent storage, from where the attacker can trigger the execution or processing of the malicious file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This exploitation leverages the race condition to bypass security checks, allowing the upload and execution of potentially harmful files.&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%2F9ue236e43gm51941ntxg.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%2F9ue236e43gm51941ntxg.png" alt="The image depicts a 'Race condition vulnerability' in file uploads. A cloud symbolizes a server with a refresh icon, linked to 'Antivirus Scan' and 'Bucket Upload' boxes. A hooded hacker uses a laptop with a skull, connected by lines to two 'file.pdf' versions: one 'safe' with a green check, the other 'evil' with a purple emoji. A dashed 'frame to override file.pdf' line shows the 'evil' file replacing the 'safe' during upload, highlighting the exploitation of a race condition to insert a malicious file." width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can try yourself exploiting a file upload race condition vulnerability, of a different kind, on this &lt;a href="https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-race-condition" rel="noopener noreferrer"&gt;Portswigger’s Lab&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;I hope this article has provided you with a comprehensive understanding of the security challenges associated with file uploads and the strategies to mitigate these risks. By implementing the recommended security measures, you can ensure that your file upload feature is robust, resilient, and secure, protecting your servers and users from potential threats.&lt;/p&gt;

&lt;p&gt;If you'd like to find out more about file type validation, take a look at the first article in this series: &lt;a href="https://dev.to/theodo/mastering-file-upload-security-understanding-file-types-5fd6"&gt;Mastering File Upload Security: Understanding File Types&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>fileupload</category>
    </item>
    <item>
      <title>API Load Testing: Enhance Your Skills with Locust</title>
      <dc:creator>Paul Royer</dc:creator>
      <pubDate>Wed, 28 Feb 2024 13:30:00 +0000</pubDate>
      <link>https://dev.to/theodo/api-load-testing-enhance-your-skills-with-locust-2367</link>
      <guid>https://dev.to/theodo/api-load-testing-enhance-your-skills-with-locust-2367</guid>
      <description>&lt;p&gt;Load testing is a serious job and doing it poorly can have disastrous consequences for big companies' production servers. But &lt;strong&gt;load testing is not only for experts&lt;/strong&gt; in massive companies! You may want to quickly check that your server performances are appropriate, using only your own computer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://locust.io/" rel="noopener noreferrer"&gt;Locust&lt;/a&gt; is a perfect tool to use on such occasion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A great user interface, allowing you to &lt;strong&gt;launch tests and monitor results in real-time&lt;/strong&gt;, directly from your web browser.&lt;/li&gt;
&lt;li&gt;Writting scripts in Python, perhaps the most popular language according to, for example, the &lt;a href="https://spectrum.ieee.org/the-top-programming-languages-2023" rel="noopener noreferrer"&gt;top programming language IEEE study&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Installable in seconds, free and open source&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start load testing with Locust!
&lt;/h2&gt;

&lt;p&gt;First, you need an API to load test. Let's say you have an amusement park application. It's running on &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; and exposing the route &lt;code&gt;GET /bookings&lt;/code&gt; with two query parameters: &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To begin with Locust, you basically need a dozen seconds. With pip already installed, just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip3 &lt;span class="nb"&gt;install &lt;/span&gt;locust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you need to write a locust file, describing how users will interact with your API. Let's create a basic one, under the name &lt;code&gt;locustfile.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_booking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;
        &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-06-08&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bookings?age=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;date=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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 User class represents the expected behaviour of a user. For each user created, Locust will execute on a loop all the tasks defined. In this simple case, we have only one task and the requested URL will be always the same as age and date variables are hardcoded. We just set a pause of 1 to 2 seconds between each user loop.&lt;/p&gt;

&lt;p&gt;Set-up is done, let's swarm into our API using Locust! In your terminal, &lt;strong&gt;in the folder where your locustfile is located,&lt;/strong&gt; simply run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;On your browser, head to &lt;a href="http://0.0.0.0:8089/" rel="noopener noreferrer"&gt;http://0.0.0.0:8089/&lt;/a&gt; and fill up the three parameters before launching the test:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nfgsi1dpcata05e4eja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nfgsi1dpcata05e4eja.png" alt="Locust interface filled with a number of users of 20, a spawn rate of 1 and host set to http://localhost:8080" width="398" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press "Start swarming" and tadaa! ✨ We can now observe the response time of our API fluctuate in real-time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91ibhfebbqlkngf65ftu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91ibhfebbqlkngf65ftu.png" alt="A time serie graph representing the 50th and 95th percentiles of response time" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the number of users grows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farnrqmks9zby8ru3v166.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farnrqmks9zby8ru3v166.png" alt="A time serie graph, showing the number of active users ramping up until 20 before stabilizing" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the test is done, you can download a report in HTML format. Allowing you to quickly share the results visually with your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefiting from Locust plugins to deliver more value
&lt;/h2&gt;

&lt;p&gt;Here, we load-tested our API in a basic way (one endpoint, always the same request parameters). We can do a lot more, and most of the time we must!&lt;/p&gt;

&lt;p&gt;Let's say your API is enjoying some success. To handle the increasing load, you &lt;a href="https://blog.theodo.com/2023/09/how-to-cache-in-java-application/" rel="noopener noreferrer"&gt;implement some cache on your endpoint&lt;/a&gt;, allowing fast response to common requests. It's nice, but &lt;strong&gt;if you keep having only one request when load testing, cache will handle all your requests.&lt;/strong&gt; So your server will be able to accept way more requests than in a real situation, where all requests could be different and cache would miss some of them.&lt;/p&gt;

&lt;p&gt;To solve this issue, we should &lt;strong&gt;vary the parameters of the requests we send&lt;/strong&gt;, that is &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;date&lt;/code&gt;. Here is one of the many possibilities, using the &lt;a href="https://github.com/SvenskaSpel/locust-plugins/blob/master/examples/csvreader_ex.py" rel="noopener noreferrer"&gt;CSV reader plugin for Locust&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;First, we need to create a CSV file listing all the test cases we want to use, for example in a file named &lt;code&gt;booking-param.csv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12, 2024-06-08
20, 2024-06-08
12, 2024-06-09
20, 2024-06-09
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use a little script to build this CSV by making the &lt;a href="https://en.wikipedia.org/wiki/Cartesian_product#A_deck_of_cards" rel="noopener noreferrer"&gt;cartesian product&lt;/a&gt; of all your parameters' possible values.&lt;/p&gt;

&lt;p&gt;Then, we modify the locust file to import our parameters from the CSV file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust_plugins.csvreader&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CSVReader&lt;/span&gt; &lt;span class="c1"&gt;# add import to the CSV reader plugin
&lt;/span&gt;
&lt;span class="n"&gt;booking_param_reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CSVReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;booking-param.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# read the CSV you created
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_booking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;booking_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking_param_reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booking_params&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="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booking_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bookings?age=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;date=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bookings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relaunch your script and each task will be executed with a different line of the CSV file you created, making each request different (if you send more requests than there are lines in your CSV file - it will quickly happen - further requests will be sent starting over from the start of the file).&lt;/p&gt;

&lt;p&gt;Note that here we add a &lt;code&gt;name&lt;/code&gt; argument to our &lt;code&gt;get&lt;/code&gt; method: &lt;strong&gt;Locust will group all these requests in one line in its report,&lt;/strong&gt; which would prove very convenient for analysis!&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay tuned: Locust is evolving
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://k6.io/blog/comparing-best-open-source-load-testing-tools/" rel="noopener noreferrer"&gt;2020 exhaustive comparison of load testing software&lt;/a&gt; (done by a k6 engineer 😉), Locust was pinpointed for its low performance and the clumsiness of running it locally on several CPU cores. Since then &lt;strong&gt;Locust can run a process for each of your cores with a single command:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locust --processes -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although Locust was created in 2011, it keeps gaining functionality, making it worth giving it a (re)-try!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>performance</category>
      <category>testing</category>
    </item>
    <item>
      <title>Hexagonal Architecture for Dummies by a Dummy</title>
      <dc:creator>Gatien Ducornaud</dc:creator>
      <pubDate>Tue, 13 Feb 2024 15:07:59 +0000</pubDate>
      <link>https://dev.to/theodo/hexagonal-architecture-for-dummies-by-a-dummy-4o5l</link>
      <guid>https://dev.to/theodo/hexagonal-architecture-for-dummies-by-a-dummy-4o5l</guid>
      <description>&lt;p&gt;TL;DR: You’ll discover the advantages of Hexagonal Architecture in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplifying Your Code&lt;/strong&gt;: Transforming intricate coding and testing efforts into manageable, beginner-friendly tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gaining Real-World Insights&lt;/strong&gt;: Understand how applying Hexagonal Architecture in actual projects not only upholds code quality but also promotes ongoing learning.&lt;/li&gt;
&lt;li&gt;Plus, benefit from practical code examples &lt;strong&gt;I’ve tackled as a junior developer&lt;/strong&gt;, illustrating these concepts in action!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  As a rookie, Hexagonal Architecture can be your best friend 👨‍💻
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I arrived on my first project with no prior experience in web development and a very academic/theoretical approach to code. It was also the tech lead's first project in such a role. However, he had experience in hexagonal architecture and believed in its power to help produce quality code, even for a rookie. It is the learnings from this experience that I wish to share here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hexagonal Architecture in a (non-boring) nutshell 🧠
&lt;/h2&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%2Fl13aysn98znjxknhts7d.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%2Fl13aysn98znjxknhts7d.png" alt="Generic schematic for hexagonal architecture" width="670" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I understand it, hexagonal architecture aims to protect your domain (where your business logic resides) from the real world, thanks to ports. All processes and data need to go through gates (the ports), so even if some aspects outside of the domain change, if they still follow the rules set by the ports, the code in the domain does not need to change.&lt;/p&gt;

&lt;p&gt;In practice, the ports are interfaces, and we ensure that everything goes through ports thanks to dependency inversion, where the implementation of the interfaces does not know of other implementations, but rather other ports. Thanks to that, any implementation of a given interface can be swapped out for any other implementation with no consequence to the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hexagonal Architecture to the rescue of junior developers 🛟
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Discovering and improving the codebase step by step ⛏️
&lt;/h3&gt;

&lt;p&gt;By design, Hexagonal Architecture enforces SOLID principles, so the scope of each change can be limited to one file. Do you need to add a new route? Just modify a controller! Need to sort your data a different way? Just change the sorting in your domain! Thanks to dependency inversion, you will not have to chase your changes all over your codebase.&lt;/p&gt;

&lt;p&gt;For example, we link users with agencies, and those “linkings” can be discarded. For some use-cases, we do not want to return the discarded linkings. To us, it is one line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Linking&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getLinkings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&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;linkingRepositoryPort&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLinkings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linking&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;linking&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;discarded&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sorted&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;linking1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linking2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;linking2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linking1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementing new features is facilitated ⚡
&lt;/h3&gt;

&lt;p&gt;The example given previously works fine if you are modifying existing code, but what about a brand new feature? In that case, you can copy the existing structure, as most the boilerplate code is the same. You don’t have to be familiar with Hexagonal Architecture from the start, as adapting existing pattern will (most of the time) result in a new feature that also respects Hexagonal Architecture itself.&lt;/p&gt;

&lt;p&gt;For example, when I created the feature I presented in the previous example, I copied the existing pattern we had for Agencies and created (from the database to the route):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;table&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;entity&lt;/code&gt; to represent the data in the table&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;repository&lt;/code&gt; interface (with SpringJPA) to get the data from the table&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;repository&lt;/code&gt; port interface to better use the data form my domain&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;repository&lt;/code&gt; service implementing the port to actually call the repository to get the data&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;controller&lt;/code&gt; to create the routes needed for the feature&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;feature port&lt;/code&gt; to define the actions of the domain&lt;/li&gt;
&lt;li&gt;And the &lt;code&gt;service&lt;/code&gt; in the domain to implement the feature port&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Writing tests is straightforward 🧪
&lt;/h3&gt;

&lt;p&gt;I was always taught that tests were important, but I was never made to write them, so I rarely did, as it was often painful to do so. However, hexagonal architecture simplifies testing tremendously, especially for the more worthwhile domain tests, where you check if your core logic is sound.&lt;/p&gt;

&lt;p&gt;Here is an example of one of my tests, intentionally without any context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_save_linking_if_same_valid_does_not_exists&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GIVEN&lt;/span&gt;
    &lt;span class="n"&gt;doReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;empty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkingRepositoryPort&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValidLinkingId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AGENCY_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// WHEN&lt;/span&gt;
    &lt;span class="n"&gt;linkingService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveLinking&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AGENCY_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// THEN&lt;/span&gt;
    &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkingRepositoryPort&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;saveLinking&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AGENCY_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_not_save_linking_if_there_is_already_a_valid_one&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GIVEN&lt;/span&gt;
    &lt;span class="n"&gt;doReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LINKING_ID&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkingRepositoryPort&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValidLinkingId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AGENCY_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// WHEN&lt;/span&gt;
    &lt;span class="nc"&gt;LinkingId&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linkingService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveLinking&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;AGENCY_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// THEN&lt;/span&gt;
    &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LINKING_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkingRepositoryPort&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;saveLinking&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those tests can be read even without knowledge of the code and also serve to document the intended behavior of the domain. Furthermore, I didn't have to delve into specifics on how to mock my database or set up and tear down a specific object, so tests practically write themselves!&lt;/p&gt;

&lt;p&gt;(As a footnote: as you can also see, given Dependency Injection, you already have your Ins and Outs, so it makes it extremely easy to do Test Driven Development, and to check if every main use case of your domain is tested. I personally found it very useful to get into the habit of writing tests, and to write documentation, as the tests are the documentation)&lt;/p&gt;

&lt;h3&gt;
  
  
  Focusing on small tasks leading to everyday learnings 🔁
&lt;/h3&gt;

&lt;p&gt;I also found Hexagonal Architecture to be a great help in promoting everyday learnings. As you can play with specific functionalities without having to worry about how they'll impact the rest of the code, you can deep dive into specific issues when they arise.&lt;/p&gt;

&lt;p&gt;As an example, we wanted to improve the number of queries of repositories made for each request. Given that we could be certain that the resulting data would be used in the same way, I was free to experiment with the way the entities were linked&lt;/p&gt;

&lt;p&gt;For example, in my &lt;code&gt;LinkingEntity&lt;/code&gt;, whether I have&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cascade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CascadeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FetchType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LAZY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"agency_id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@OnDelete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OnDeleteAction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CASCADE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;AgencyEntity&lt;/span&gt; &lt;span class="n"&gt;agency&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cascade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CascadeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FetchType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EAGER&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"agency_id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@OnDelete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OnDeleteAction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CASCADE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;AgencyEntity&lt;/span&gt; &lt;span class="n"&gt;agency&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not matter for the rest of the application, and I could see the number of request made in each case, and explore the impact on the repository.&lt;/p&gt;

&lt;p&gt;However, there are still difficulties I faced and I would like to highlight so they can be anticipated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hexagonal Architecture drawbacks for a Junior Developer 🚧
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hexagonal Lingo can be overwhelming 📚
&lt;/h3&gt;

&lt;p&gt;When I first started on the project, I had all the names mixed up. “What do you mean a Repository is not what implements a Repository Port? Why does an implementation in my domain implement an interface outside of it?”&lt;/p&gt;

&lt;p&gt;In order to sort it out, I found it useful to visualize it by placing each type of class or package in the theoretical diagram. This was a good help to link the theoretical aspects to the way we used it in the code. For example, a typical feature would look like this in our case:&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%2Fwuqzagm3gow78swge5q9.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%2Fwuqzagm3gow78swge5q9.png" alt="Example schematic of use of hexagonal architecture" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  There are a LOT of patterns 🔢
&lt;/h3&gt;

&lt;p&gt;When I first started to get confused about what went where, I drew a diagram similar to the previous one and thought that I understood how it worked. But then I had to do a request towards another web-service, and I had no idea how to do it. So I copied the API interface I saw for performing GET calls, but there was class implementing such interface. Turned out that the diagram looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbj6752lvri2wuig18x35.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%2Fbj6752lvri2wuig18x35.png" alt="Different example of use of hexagonal architecture" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It may not seem very different, but it does imply non-trivial differences in the code. So it implies continuous learning about hexagonal architecture itself, not a just an initial time investment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lots of code for simple things 💣
&lt;/h3&gt;

&lt;p&gt;Following learning the new pattern for API, I implemented it for the feature. In order to do that, I created or modified the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a controller,&lt;/li&gt;
&lt;li&gt;a new class for the data transfer object,&lt;/li&gt;
&lt;li&gt;the feature port,&lt;/li&gt;
&lt;li&gt;its implementation,&lt;/li&gt;
&lt;li&gt;the object class in the domain,&lt;/li&gt;
&lt;li&gt;the API,&lt;/li&gt;
&lt;li&gt;the class for the object how it is received,&lt;/li&gt;
&lt;li&gt;the configuration to instantiate the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In total, I created 6 new files and modified 2 others for what amounted basically to "Call a given service, tweak the return data a bit, and return that," which could have been done in a much shorter way.&lt;/p&gt;

&lt;p&gt;Such work for a simple feature can be frustrating, especially it is repeated. There, the theoretical knowledge of the flexibility the hexagonal architecture offers was a good justifier of why such work was required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parting words
&lt;/h3&gt;

&lt;p&gt;As you may guess, I would highly recommend using hexagonal architecture on your own projects. But, based on my own experience as a rookie, I would also recommend it if you are in charge of a project with junior developers, so as to help them improve their skills and produce quality code faster.&lt;/p&gt;

&lt;p&gt;Happy hexagonal coding! 👨‍💻🌐&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>architecture</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to beautify java code reliably</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Wed, 07 Feb 2024 13:50:00 +0000</pubDate>
      <link>https://dev.to/theodo/how-to-beautify-java-code-reliably-1886</link>
      <guid>https://dev.to/theodo/how-to-beautify-java-code-reliably-1886</guid>
      <description>&lt;p&gt;I recently had to set up code formatting on a spring boot java application. Auto code formatting is important to avoiding useless diffs in source files, reducing noise in code review, allowing reviewers to focus on what matters.&lt;/p&gt;

&lt;p&gt;Ideally we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic formatting on file save in IDE's (vscode, IntelliJ)&lt;/li&gt;
&lt;li&gt;Capacity to run in CI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted to integrate well with vscode and after trying some solution like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;java autoformat, which I couldn't run in CI and that was not really portable between IDEs&lt;/li&gt;
&lt;li&gt;maven auto formatting plugin (didn't run on save easily in IDEs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ended up, from a friend suggestion, looking into a &lt;a href="https://github.com/jhipster/prettier-java" rel="noopener noreferrer"&gt;java prettier plugin&lt;/a&gt;. Prettier allowed for an easy integration with most Web IDE, can be configured to format on save for vscode and is trivial to run in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing prettier for java
&lt;/h2&gt;

&lt;p&gt;Let's start with the setup in command line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install nodejs: (&lt;a href="https://github.com/Schniz/fnm" rel="noopener noreferrer"&gt;I highly recommend using a node version manager like fnm&lt;/a&gt;) and to install a recent node version (&lt;a href="https://nodejs.org/en/about/releases/" rel="noopener noreferrer"&gt;current long term support is 16+&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;package.json&lt;/code&gt; file at the root of your project and add prettier and the java plugin:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&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;"prettier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.5.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prettier-plugin-java"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.6.1"&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;"engines"&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;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16.x"&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;"scripts"&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;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier --write &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{,**/}*.{java,yml,yaml,md,json}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"check-format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier --check &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{,**/}*.{java,yml,yaml,md,json}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run check-format"&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;ul&gt;
&lt;li&gt;Install the required node_module: &lt;code&gt;npm install&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Don't forget to add &lt;code&gt;node_modules&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡 &lt;a href="https://prettier.io/docs/en/ignore.html" rel="noopener noreferrer"&gt;I highly recommend adding a &lt;code&gt;.prettierignore&lt;/code&gt; that list autogenerated files&lt;/a&gt; to avoid formatting those, eg:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You can now format java code running &lt;code&gt;npm run format&lt;/code&gt; 🚀&lt;/p&gt;

&lt;p&gt;💡 Do not forget to add node as a dependency for your project installation and to run &lt;code&gt;npm ci&lt;/code&gt; in your project installation script for new developers.&lt;/p&gt;

&lt;p&gt;We now want to improve developer experience by integrating with IDEs, running the formatter in CLI being impractical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monorepo
&lt;/h3&gt;

&lt;p&gt;If you are using a monorepo, I highly recommend setting up prettier in the root of your monorepo instead of setting up prettier in each sub directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating with IDEs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  vscode
&lt;/h3&gt;

&lt;p&gt;To set up prettier in vscode: add the prettier for java extension to recommended vscode extensions for the project, to do so, you can simply add the id of the extension to: &lt;code&gt;.vscode/extensions.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
  "recommendations": [
    "pivotal.vscode-boot-dev-pack",
    "vscjava.vscode-java-pack",
    "redhat.vscode-xml",
    "sonarsource.sonarlint-vscode",
    "gabrielbb.vscode-lombok",
    "dbaeumer.vscode-eslint",
&lt;span class="gi"&gt;+   "esbenp.prettier-vscode", // the classic prettier extension
&lt;/span&gt;    "shengchen.vscode-checkstyle",
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then set up this extension as the default linter for java file in &lt;code&gt;.vscode/settings.json&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
// rest of your configuration
// automatic format on save, I recommend the save on focus lost option as well
&lt;span class="gi"&gt;+   "editor.formatOnSave": true,
+   "[java]": {
+       "editor.defaultFormatter": "esbenp.prettier-vscode"
+   }
&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already we can easily format java code on save:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvsxnqkgfr1l5z6mphgj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvsxnqkgfr1l5z6mphgj.gif" alt="Formatting java code on save" width="428" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I &lt;strong&gt;highly&lt;/strong&gt; recommend checking in the &lt;code&gt;.vscode&lt;/code&gt; folder in your versioning tool (most likely git) to share this configuration with all developers.&lt;/p&gt;
&lt;h3&gt;
  
  
  IntelliJ
&lt;/h3&gt;

&lt;p&gt;To integrate with prettier java formatter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First install the &lt;a href="https://www.jetbrains.com/help/idea/using-file-watchers.html#ws_file_watchers_bedore_you_start" rel="noopener noreferrer"&gt;File Watchers IntelliJ plugin&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then configure the plugin &lt;code&gt;Tools/File Watchers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: Prettier-java
File type: Java
Program:  full path to `.bin/prettier` eg: `node_modules/.bin/prettier`
Arguments: --write $FilePathRelativeToProjectRoot$
Output paths to refresh: $FilePathRelativeToProjectRoot
Auto-save edited files to trigger the watcher: check it
Trigger the watcher on external changes: check it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://prettier.io/docs/en/webstorm.html#running-prettier-on-save-using-file-watcher" rel="noopener noreferrer"&gt;prettier documentation can be found here for IntelliJ integration&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Integrate code formatting check in CI
&lt;/h2&gt;

&lt;p&gt;Now we only need to integrate this check in CI, I'm using gitlab for this example, but the principles can be transposed to any other CI.&lt;/p&gt;

&lt;p&gt;First we need to add a new job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;

&lt;span class="c1"&gt;# ... rest of the file&lt;/span&gt;

&lt;span class="c1"&gt;# beautify backend files with prettier&lt;/span&gt;
&lt;span class="na"&gt;lint-back&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:lts-alpine&lt;/span&gt; &lt;span class="c1"&gt;# We use a node image to run prettier&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt; &lt;span class="c1"&gt;# must match an existing stage&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# none required optimization that explain to gitlab ci that this test has no dependency and can start as soon as possible&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
&lt;span class="c1"&gt;# ... rest of the file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now just push your change, and you are all set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw43qmbm2l97s0jq1rjyw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw43qmbm2l97s0jq1rjyw.png" alt="CI running lint successfully" width="569" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Whisper to your keyboard: Setting up a speech-to-text button</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Tue, 06 Feb 2024 13:58:00 +0000</pubDate>
      <link>https://dev.to/theodo/whisper-to-your-keyboard-setting-up-a-speech-to-text-button-2pj5</link>
      <guid>https://dev.to/theodo/whisper-to-your-keyboard-setting-up-a-speech-to-text-button-2pj5</guid>
      <description>&lt;p&gt;Long story short: I broke my arm while riding my bike and I can't type. Quite the problem as typing is a key part of my daily life as a software engineer. So I decided to add a speech to text button to my keyboard. Here's how I did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to transcript speech to text on linux
&lt;/h2&gt;

&lt;p&gt;First, I looked into how to transcript speech to text on linux. I found a few solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whisper.cpp: a way to run the OpenAI whisper model locally&lt;/li&gt;
&lt;li&gt;OpenAI's API: the original Whisper model as a pay as you go API&lt;/li&gt;
&lt;li&gt;Deepgram: a pay as you go service that offers a speech to text API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After testing whisper.cpp on my machine, it was too slow and inaccurate (out of the box), so I decided to use an API and abandoned the idea of running it locally (for now).&lt;/p&gt;

&lt;p&gt;So I registered for an OpenAI account and tried their API:&lt;/p&gt;

&lt;p&gt;(you can download an &lt;a href="https://dpgr.am/bueller.wav" rel="noopener noreferrer"&gt;example recording here&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./recording"&lt;/span&gt; &lt;span class="c"&gt;# path to the recording without extension&lt;/span&gt;

curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.openai.com/v1/audio/transcriptions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPEN_AI_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: multipart/form-data'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;whisper-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's quite magical, already I could transcript my voice to text.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to record your microphone on linux
&lt;/h2&gt;

&lt;p&gt;This one was a challenge for compatibility and device selection, but basically, what you want to do is record your microphone as a &lt;code&gt;.wav&lt;/code&gt; file and save it. (I could not make mp3 encoding work reliably and it was not required)&lt;/p&gt;

&lt;p&gt;First use &lt;code&gt;arecord&lt;/code&gt; to list your available devices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;arecord &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you should be able to test which input device is your microphone by recording a few samples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./recording"&lt;/span&gt;
&lt;span class="nv"&gt;AUDIO_INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"hw:0,0"&lt;/span&gt; &lt;span class="c"&gt;# your microphone device, test with a few devices to find the right one&lt;/span&gt;
arecord &lt;span class="nt"&gt;--device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUDIO_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="nt"&gt;--duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to write the text file emulating a keyboard
&lt;/h2&gt;

&lt;p&gt;This one is quite easy, I used &lt;code&gt;xdotool&lt;/code&gt; to emulate a keyboard and write the text file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./recording"&lt;/span&gt;
perl &lt;span class="nt"&gt;-pi&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'chomp if eof'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt; &lt;span class="c"&gt;# remove trailing newline if any to avoid sending an extra newline keypress&lt;/span&gt;
xdotool &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="nt"&gt;--clearmodifiers&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it all together: a button to transcript speech to text
&lt;/h2&gt;

&lt;p&gt;Now that we have all the pieces, we can put it all together in a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# usage: exec ./voice-typing.sh twice to start and stop recording&lt;/span&gt;
&lt;span class="c"&gt;# Dependencies: curl, jq, arecord, xdotool, killall&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n\t&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;

&lt;span class="c"&gt;# Configuration&lt;/span&gt;
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;PID_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.recordpid"&lt;/span&gt;
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.voice-type/recording"&lt;/span&gt;
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;MAX_DURATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;AUDIO_INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'hw:0,0'&lt;/span&gt; &lt;span class="c"&gt;# Use `arecord -l` to list available devices&lt;/span&gt;

start_recording&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting new recording..."&lt;/span&gt;
  &lt;span class="nb"&gt;nohup &lt;/span&gt;arecord &lt;span class="nt"&gt;--device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUDIO_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="nt"&gt;--duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MAX_DURATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null &amp;amp;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$!&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

stop_recording&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Stopping recording..."&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;pid
    &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&amp;lt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; killall &lt;span class="nt"&gt;-w&lt;/span&gt; arecord
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;0
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No recording process found."&lt;/span&gt;

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

write_transcript&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  perl &lt;span class="nt"&gt;-pi&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'chomp if eof'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
  xdotool &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="nt"&gt;--clearmodifiers&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

transcribe_with_openai&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  curl &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--fail&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.openai.com/v1/audio/transcriptions &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPEN_AI_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: multipart/form-data'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;whisper-1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

main&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;stop_recording
    transcribe_with_openai
    write_transcript
  &lt;span class="k"&gt;else
    &lt;/span&gt;start_recording
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The script needs to be run twice to start and stop recording. It will then transcript the recording and write the text to the current window.&lt;/p&gt;

&lt;p&gt;To trigger it with a button, simply add a keyboard shortcut to run the script in your keyboard configurations settings on your linux distribution.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu59zkibtjy1bnf4fhogo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu59zkibtjy1bnf4fhogo.png" alt="example keyboard shortcut configuration" width="667" height="933"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and next steps
&lt;/h2&gt;

&lt;p&gt;It was a fun little project to do, and it's quite useful to be able to type with your voice. I'm not sure I'll keep using it after my arm heals, but it's a nice option to have.&lt;/p&gt;

&lt;p&gt;Since using whisper was a little slow, I tried and switched to Deepgram for faster (and sometimes more accurate) transcriptions. I published the &lt;a href="https://github.com/Jeremie-Chauvel/linux-voice-type" rel="noopener noreferrer"&gt;complete script on github, you can find it here&lt;/a&gt;, it checks for requirements and handles errors a little more gracefully.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Effective nodejs version management for the busy developer</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Sun, 04 Feb 2024 12:56:50 +0000</pubDate>
      <link>https://dev.to/theodo/effective-nodejs-version-management-for-the-busy-developer-40fn</link>
      <guid>https://dev.to/theodo/effective-nodejs-version-management-for-the-busy-developer-40fn</guid>
      <description>&lt;p&gt;I highly recommend setting up nodejs with a version manager, &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;nvm&lt;/a&gt; was and still is a popular option, however, I now recommend and have been using &lt;a href="https://github.com/Schniz/fnm" rel="noopener noreferrer"&gt;fnm&lt;/a&gt;, a simpler and faster alternative to manage my nodejs versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install a nodejs version manager: FNM with automatic version switching
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Schniz/fnm#installation" rel="noopener noreferrer"&gt;To install fnm, follow the installation steps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I highly recommend installing the autocompletion, if using zshrc, add this in your &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# if fnm is installed, add to path and load autocomplete&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; ~/.fnm &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;path+&lt;span class="o"&gt;=&lt;/span&gt;~/.fnm
  &lt;span class="c"&gt;# setup env and allow for automatic node version change when directories contains `.node-version` or `.nvmrc`&lt;/span&gt;
  &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;fnm &lt;span class="nb"&gt;env&lt;/span&gt; &lt;span class="nt"&gt;--shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;zsh &lt;span class="nt"&gt;--use-on-cd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="c"&gt;# shell completion&lt;/span&gt;
  &lt;span class="nb"&gt;source&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;fnm completions &lt;span class="nt"&gt;--shell&lt;/span&gt; zsh&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1
  compdef _fnm fnm
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart your shell to take those changes into account: &lt;code&gt;exec $SHELL -l&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a nodejs version
&lt;/h2&gt;

&lt;p&gt;Then you can easily install and switch node versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# install a version&lt;/span&gt;
fnm &lt;span class="nb"&gt;install &lt;/span&gt;lts/iron
&lt;span class="c"&gt;# switch to version&lt;/span&gt;
fnm use lts/iron
&lt;span class="c"&gt;# you can switch to a set version by number as well (eg. v20.9.0)&lt;/span&gt;
node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fi9ghm981w1ih3orslk6e.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%2Fi9ghm981w1ih3orslk6e.png" alt="change node version with fnm use, automatically switch on cd and install new node version with fnm install" width="655" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project setup to promote the correct nodejs version
&lt;/h2&gt;

&lt;p&gt;When using node in a project, I recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up a &lt;code&gt;.nvmrc&lt;/code&gt; file at the root of your project to declare the used nodejs version:
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Using the same version in your runtime, CI, for example with docker:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:iron-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In your project package.json, I recommend using the &lt;code&gt;engines&lt;/code&gt; field to declare the nodejs version:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"engines"&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;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pnpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=8"&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;as well as using engine-strict in your &lt;code&gt;.npmrc&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;engine-strict=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another option is using a preinstall hook for pnpm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus using pnpm as a package manager
&lt;/h2&gt;

&lt;p&gt;I also recommend using &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt; as a package manager, it's faster and more efficient than npm or yarn with great capabilities concerning monorepo setup. On recent nodejs versions (v16.13+), you can install it easily with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;use engines to enforce usage of pnpm or a preinstall hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"engines"&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;"pnpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=8"&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;or&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="err"&gt;scripts:&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;"preinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx only-allow pnpm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to boost your database performances under JPA part 1: @Index 📖</title>
      <dc:creator>Hugo Brunet</dc:creator>
      <pubDate>Fri, 19 Jan 2024 14:30:00 +0000</pubDate>
      <link>https://dev.to/theodo/how-to-boost-your-database-performances-under-jpa-part-1-index-4blc</link>
      <guid>https://dev.to/theodo/how-to-boost-your-database-performances-under-jpa-part-1-index-4blc</guid>
      <description>&lt;p&gt;TL;DR: I will present you how the use of an ORM and its given tools - here &lt;code&gt;@Index&lt;/code&gt; - can boost your application’s performance thanks to the illustration of a booming business and its scale related issues 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to Object-Relational Mapping (ORM) with JPA
&lt;/h2&gt;

&lt;p&gt;Databases play a crucial role in most applications. However, working directly with databases can be complex and time-consuming. This is where Object-Relational Mapping frameworks like Java Persistence API (JPA) come into play.&lt;/p&gt;

&lt;p&gt;ORM frameworks provide a way to map Java objects to relational database entities, allowing developers to interact with databases using object-oriented programming concepts. JPA, is a popular ORM framework that simplifies database operations and enhances productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first trusty partner: JPA 👨‍💼
&lt;/h2&gt;

&lt;p&gt;As the CEO of a new company, you'll soon find yourself needing to retrieve data from a database. But don't worry, you don't have to deal with the difficulty of writing SQL queries and managing connections yourself. That's where our trusty associate, JPA, comes in. With JPA, you can simply ask it to fetch the data you need, and it will take care of all the dirty work for you 😉&lt;/p&gt;

&lt;p&gt;But before searching for employees, you need to have some so here is how we will define them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Entity&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;employees&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Id&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;GeneratedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GenerationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;Long&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;departmentCode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="nx"&gt;departmentCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ... additional properties, getters, and setters&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's say you want to retrieve a list of employees based on their department code. Without JPA, you would have to manually write the SQL query, manage the database connection, and map the query results to Java objects 😪&lt;/p&gt;

&lt;p&gt;But with JPA, here is what you can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.jpa.repository.JpaRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Repository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;EmployeeRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByDepartmentCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;departmentCode&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Your only need is to use the &lt;code&gt;EmployeeRepository&lt;/code&gt; interface, which is already set up for you. Just call the &lt;code&gt;findByDepartmentCode&lt;/code&gt; method, passing in the department code you're interested in, and JPA will handle the rest. It will execute the query, map the results to Java objects, and manage the database connection behind the scenes 🤩&lt;/p&gt;

&lt;p&gt;So, sit back and relax while JPA does the heavy lifting for you. You can focus on running your company while JPA takes care of retrieving the data you need from the database 🏝️&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle your database scale with &lt;code&gt;@Index&lt;/code&gt; annotation 📈
&lt;/h2&gt;

&lt;p&gt;2 years later, the company has experienced tremendous growth, and thousands of employees have been hired. However, the trusty associate, JPA, is struggling to keep up with the increasing workload, &lt;strong&gt;even finding the list of employees related to a department takes hours&lt;/strong&gt;. As the CEO, you are determined to find a solution to help JPA handle the company's growth effectively.&lt;/p&gt;

&lt;p&gt;Fortunately, you come across a powerful tool called the &lt;code&gt;@Index&lt;/code&gt; annotation. This annotation is designed to optimize query performance by creating indexes on specific columns of the database tables and put some order in JPA’s files.&lt;/p&gt;

&lt;p&gt;This guy won’t believe his eyes when he’ll see his room so clean thanks to this single line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"employees"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;@Index&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"department_code_idx"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columnList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"departmentCode"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;}**&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"age"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"departmentCode"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;departmentCode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ... additional properties, getters, and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;All you have done by putting this index in @Table is build an ordered structure &lt;strong&gt;outside&lt;/strong&gt; the database in which you can easily find a departmentCode. Why department codes and not age ? Because I said &lt;strong&gt;columnList=”departmentCode”&lt;/strong&gt;, nothing more. So when JPA will find for datas in a specific department, the search will be blazingly fast ⚡&lt;/p&gt;

&lt;p&gt;⚠️ Keep in mind that you are maybe going to build some more indexes so even if it’s optional, name them well as I said with &lt;strong&gt;name = "department_code_idx"&lt;/strong&gt;.&lt;br&gt;
You better not add a new department everyday or you will have to destroy and re build this data structure because order may have changed ⚠️&lt;/p&gt;

&lt;p&gt;If you don’t want your employees to socialize and would rather only allow one unique employee per department, you can set your index to force unicity of departmentCode thanks to the last available parameter: &lt;strong&gt;unique&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;department_code_idx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;departmentCode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the CEO, you can now focus on running the company, confident that JPA, with the help of the &lt;code&gt;@Index&lt;/code&gt; annotation, can effectively handle the data management needs of the expanding organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s level up: how to index a join table column in JPA ?
&lt;/h2&gt;

&lt;p&gt;You are now a direct concurrent of the GAFAM, thus your employees will need to work on many different missions at the same time and JPA will need to find them in the blink of an eye. Previously, JPA went into the model of the employee to add an index, which means he &lt;strong&gt;needs&lt;/strong&gt; access to the the table in order to index, what if JPA wants to index a table which he can’t instantly access ?&lt;/p&gt;

&lt;p&gt;ManyToMany relations bring this kind of situation where a new table is created without being self suffisant, these are &lt;strong&gt;Join Tables&lt;/strong&gt; and are created as shown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"employees"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"age"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nd"&gt;@ManyToMany&lt;/span&gt;
    &lt;span class="nd"&gt;@JoinTable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"employee_mission"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;joinColumns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"employee_id"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;inverseJoinColumns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@JoinColumn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mission_id"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Mission&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;missions&lt;/span&gt;&lt;span class="o"&gt;;**&lt;/span&gt;

    &lt;span class="c1"&gt;// ... additional properties, getters, and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"missions"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Mission&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ... additional properties, getters, and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunately &lt;code&gt;@JoinTable&lt;/code&gt; annotation allows us to pass one more parameter: &lt;strong&gt;indexes&lt;/strong&gt; which takes a list of indexes to add in the table, so if i want to index both of my columns here is what I can do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ManyToMany&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;JoinTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;employee_mission&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;joinColumns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;JoinColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;employee_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nx"&gt;inverseJoinColumns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;JoinColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mission_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nx"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
                        &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;employee_id_idx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;employee_id&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="nd"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mission_id_idx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mission_id&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="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Mission&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;missions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, a new structure is built in JPA’s office which allows him to quickly find all the missions affected to a given employee and all employees working on a given mission.&lt;/p&gt;

&lt;p&gt;I wish you a good luck for the rest of your journey with this successful business with JPA and his new favorite tool &lt;code&gt;@Index&lt;/code&gt; 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;@Index&lt;/code&gt; in a nutshell
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;@Index&lt;/code&gt; annotation in JPA is used to define indexes on specific columns of database tables. It has three parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;columnList&lt;/strong&gt;: Specifies the name of the column(s) on which the index should be created.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt;: (Optional) Specifies the name of the index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;unique&lt;/strong&gt;: (Optional) Specifies whether the index should enforce uniqueness on the column(s) it is created on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;@Index&lt;/code&gt; can be used in various JPA annotations, such as @Table and @JoinTable or by itself.&lt;/p&gt;

&lt;p&gt;The use of &lt;code&gt;@Index&lt;/code&gt; has divided the response time of some routes in my project by at most 42 with a route going from 26.51s to 0.634s.&lt;/p&gt;

&lt;p&gt;Stay tuned for the next parts of this series, where we will explore other techniques to boost database performances under JPA.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to ensure that all the routes on my Symfony app have access control</title>
      <dc:creator>Thomas Hercule</dc:creator>
      <pubDate>Wed, 17 Jan 2024 10:15:05 +0000</pubDate>
      <link>https://dev.to/theodo/how-to-ensure-that-all-the-routes-on-my-symfony-app-have-access-control-jff</link>
      <guid>https://dev.to/theodo/how-to-ensure-that-all-the-routes-on-my-symfony-app-have-access-control-jff</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Effective access control involves both Symfony's firewall &lt;strong&gt;and&lt;/strong&gt; specific control on each route.&lt;/li&gt;
&lt;li&gt;If you use API Platform, use ACCENT to verify access control on your routes.&lt;/li&gt;
&lt;li&gt;If you use functions in the controller to secure your routes, you can install the composer package I created based on the following part of this article.&lt;/li&gt;
&lt;li&gt;If you're not using API Platform nor functions, you can create a custom script.&lt;/li&gt;
&lt;li&gt;It's important to integrate access control verification into your CI process to maintain a high level of security.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id="what-is-access-control"&gt;What is access control&lt;/h1&gt;

&lt;p&gt;Access control allows you to define access permissions to specific parts of your application. It helps restrict access to certain pages or features for users who do not have the necessary permissions.&lt;/p&gt;

&lt;p&gt;To implement access control, you need to define user roles and corresponding permissions, then apply them to the routes of your application. This can be especially useful for safeguarding sensitive information or important actions, such as modifying or deleting data.&lt;/p&gt;

&lt;p&gt;Implementing access control for routes significantly enhances the security of your Symfony project and safeguards your users’ data.&lt;/p&gt;

&lt;p&gt;Effective access control in your Symfony project involves two main aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Symfony Firewall&lt;/li&gt;
&lt;li&gt;Specific access control for each route&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id="symfony-firewall"&gt;Symfony firewall&lt;/h1&gt;

&lt;p&gt;The Symfony firewall is the initial layer of security for routes, adding global rules to all routes or specific groups of routes.&lt;/p&gt;

&lt;p&gt;The configuration for this firewall is typically found in &lt;strong&gt;&lt;code&gt;config/packages/security.yaml&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this file, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define which URL groups require (or do not require) security checks (logged-in user, specific role, GET/POST requests, etc.).&lt;/li&gt;
&lt;li&gt;Whitelist IPs for certain endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generally, you would grant access to the site only to logged-in users (the default behavior) and disable this check for specific pages (login, password reset, etc.).&lt;/p&gt;

&lt;p&gt;Here's an example of a typical &lt;strong&gt;&lt;code&gt;security.yaml&lt;/code&gt;&lt;/strong&gt; configuration where we ensure the user is logged in for all routes except the login and register pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;access_control&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;^/login&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;IS_AUTHENTICATED_ANONYMOUSLY&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;^/register&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;IS_AUTHENTICATED_ANONYMOUSLY&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;^/&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ROLE_USER&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ROLE_ADMIN&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The role &lt;strong&gt;&lt;code&gt;IS_AUTHENTICATED_ANONYMOUSLY&lt;/code&gt;&lt;/strong&gt; allows access to pages even for non-authenticated users. &lt;strong&gt;&lt;a href="https://symfony.com/doc/current/security/access_control.html" rel="noopener noreferrer"&gt;You can find more details in the Symfony firewall documentation&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although it's possible to have role-based access control in the firewall, it's preferable to implement it on each route:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To handle more complex rules.&lt;/li&gt;
&lt;li&gt;To make the access control directly visible on the relevant functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a Symfony project with more than a dozen routes, as it was the case on my project, you will need a tool to automatically ensure they all have access control.&lt;/p&gt;

&lt;h1 id="how-to-ensure-access-control-for-all-your-symfony-routes"&gt;How to ensure access control for all your symfony routes&lt;/h1&gt;

&lt;p&gt;Depending on the type of project, different solutions can be used to verify the security of these routes. If you are using API Platform, you can save time by utilizing ACCENT. Otherwise, it is possible to create a custom script since there is no pre-existing tool to automate this verification.&lt;/p&gt;

&lt;h2 id="my-project-uses-api-platform-using-accent"&gt;My project uses API Platform: using ACCENT&lt;/h2&gt;

&lt;p&gt;If you use API Platform, you can use &lt;a href="https://github.com/theodo/accent" rel="noopener noreferrer"&gt;ACCENT&lt;/a&gt;. It is a powerful tool to generate a report on the access control of each of your routes with very little configuration.&lt;/p&gt;

&lt;p&gt;To install ACCENT, run: &lt;strong&gt;&lt;code&gt;composer require --dev theodo/accent-bundle&lt;/code&gt;&lt;/strong&gt;.&lt;br&gt;
Then, you can generate a detailed report on the access control of your routes with: &lt;strong&gt;&lt;code&gt;bin/console theodo:access-control&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example of a generated report:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tpj5cgtpijikvphoel5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tpj5cgtpijikvphoel5.png" alt="ACCENT generated report" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above you can see the list of all the projects’ routes with, highlighted in red, the routes that lacks access control.&lt;/p&gt;

&lt;h2&gt;My project doesn't use API Platform&lt;/h2&gt;

&lt;h3 id="my-project-uses-function-to-secure-routes"&gt;My project uses functions to secure routes&lt;/h3&gt;

&lt;p&gt;If your project uses functions to secure your routes in the controllers as such :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdminController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$securityService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// We redirect the user to the login page&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;It also works with Symfony's function (&lt;strong&gt;&lt;code&gt;isGranted&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;denyAccessUnlessGranted&lt;/code&gt;&lt;/strong&gt; for example).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can use the composer package I created based on the script I build in the next part of this article : &lt;a href="https://github.com/Th0masso/symfony-security-access-control-checker" rel="noopener noreferrer"&gt;&lt;strong&gt;symfony-route-security-checker&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install it with : &lt;strong&gt;&lt;code&gt;composer require --dev th0masso/symfony-route-security-checker&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the &lt;code&gt;ssacc-config.dist.yaml&lt;/code&gt; from the package repository into your project. Read the documentation to configure it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, you can generate a detailed report on the access control of your routes with: &lt;strong&gt;&lt;code&gt;bin/console security:check-routes&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03a8cre9408eicrtrm6w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03a8cre9408eicrtrm6w.png" alt="SSACC generated report" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="other-cases"&gt;Other cases&lt;/h3&gt;

&lt;p&gt;You can create a custom script specific to your project. In this section, we will use an example of a script that checks if Symfony security functions are called at the beginning of each function related to routes. If you are using something other than Symfony Security function, this section can still help you write such a script.&lt;/p&gt;

&lt;p&gt;Plan for this section:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access control with Symfony Security&lt;/li&gt;
&lt;li&gt;Why write a script?&lt;/li&gt;
&lt;li&gt;Retrieving the list of project routes&lt;/li&gt;
&lt;li&gt;Verifying that the function has access control&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="access-control-with-symfony-security"&gt;Access control with Symfony security&lt;/h3&gt;

&lt;p&gt;There are several ways to secure routes "manually" using Symfony Security functions: &lt;strong&gt;&lt;code&gt;isGranted&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;denyAccessUnlessGranted&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In annotations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Security\Http\Attribute\IsGranted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdminController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;#[IsGranted('ROLE_ADMIN')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&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;Or by directly calling these functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Security\Http\Attribute\IsGranted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdminController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isGranted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ROLE_ADMIN'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// We redirect the user to the login page&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&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;I have worked on a project where we controlled access to our routes by calling &lt;code&gt;isGranted&lt;/code&gt; or &lt;code&gt;denyAccessUnlessGranted&lt;/code&gt; within our functions. However, the method I used to verify access control on our routes can also work with annotations with minor modifications.&lt;/p&gt;

&lt;h3 id="why-write-a-script"&gt;Why write a script?&lt;/h3&gt;

&lt;p&gt;As there is no existing tool to automatically verify routes in these cases, we have two solutions to ensure that routes have specific access control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually check the routes.&lt;/li&gt;
&lt;li&gt;Create a script to do it for us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of these solutions provide a snapshot of the current state. However, creating a script automates the access control verification for future routes and could potentially be shared and used in other projects.&lt;/p&gt;

&lt;p&gt;In my case, I knew there were over a hundred routes in the project, so doing it manually would have been very redundant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytcwht4t7652c3jjhqfe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytcwht4t7652c3jjhqfe.jpg" alt="kanye meme" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="retrieving-the-list-of-project-routes"&gt;Retrieving the list of project routes&lt;/h3&gt;

&lt;p&gt;Symfony provides a command to list all the routes in the project along with their paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;php bin/console debug:router --show-controllers --format=json&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, for each route in the project, we have its path in the format: &lt;strong&gt;&lt;code&gt;MyPath/MyController.php::MyFunction&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id="verifying-that-the-function-has-access-control"&gt;Verifying that the function has access control&lt;/h3&gt;

&lt;p&gt;Now that we know which functions are associated with which routes and their paths, we can check in the files whether these functions indeed have calls to permission-checking functions such as &lt;strong&gt;&lt;code&gt;isGranted&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;denyAccessUnlessGranted&lt;/code&gt;&lt;/strong&gt;. &lt;br&gt;
These functions can be called &lt;a href="https://symfony.com/bundles/SensioFrameworkExtraBundle/current/annotations/security.html" rel="noopener noreferrer"&gt;through annotations&lt;/a&gt; or directly within the controller function.&lt;/p&gt;

&lt;h4&gt; Using annotations to restrict access &lt;/h4&gt;

&lt;p&gt;If you use annotations to secure your routes, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recover the annotation of the controller's function as a string with PHP's &lt;a href="https://www.php.net/manual/en/reflectionproperty.getdoccomment.php" rel="noopener noreferrer"&gt;ReflectionProperty::getDocComment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;then search for &lt;strong&gt;&lt;code&gt;isGranted&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;denyAccessUnlessGranted&lt;/code&gt;&lt;/strong&gt; using a regular expression.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since I didn't use annotations on my project, I will not go into details on this method.&lt;/p&gt;

&lt;h4&gt; Calling functions to restrict access &lt;/h4&gt;

&lt;p&gt;If you call some permission-checking functions directly into your controller, it's possible to ensure that these functions are called at the beginning of the function used by the route in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using a regular expression&lt;/li&gt;
&lt;li&gt;Using the Abstract Syntax Tree (AST)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AST should be more robust because it can handle more complex cases. For example, with AST, we can determine if the function is called in a condition, a loop, or another function. However, within my team, no one had experience with AST manipulation, and we didn't encounter such complex cases in my project. So, I opted for a solution using a regular expression.&lt;/p&gt;

&lt;p&gt;I then wrote a regular expression that matches &lt;strong&gt;&lt;code&gt;!$this-&amp;gt;isGranted&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;$this-&amp;gt;denyAccessUnlessGranted&lt;/code&gt;&lt;/strong&gt; on the first line of the function used by the route (⚠️ please do not read this monstrosity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'((public function '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$routeFunction&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;')(\([^{]*\{)(\s.*)(\$this-&amp;gt;denyAccessUnlessGranted|!\$this-&amp;gt;isGranted))'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, it's just a matter of checking if we find the expression in the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fileContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked on the first try; all the routes were secured.&lt;/p&gt;

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

&lt;p&gt;I’m kidding, I'm not fluent in regular expressions, and I want others to be able to read and understand this script, so it took me a few hours.&lt;/p&gt;

&lt;p&gt;ℹ️ Fortunately, I was advised to use &lt;strong&gt;&lt;a href="https://regex101.com/" rel="noopener noreferrer"&gt;regex101.com&lt;/a&gt;&lt;/strong&gt;, an incredibly useful website for testing and understanding complex regular expressions.&lt;/p&gt;

&lt;p&gt;Then, I broke down and commented the expression to make it more understandable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// start of regular expression&lt;/span&gt;
&lt;span class="nv"&gt;$regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'('&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// find "public function myFunction"&lt;/span&gt;
&lt;span class="nv"&gt;$regex&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'(public function '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$function&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// then everything until "{"&lt;/span&gt;
&lt;span class="nv"&gt;$regex&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'(\([^{]*\{)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// then everything on next line&lt;/span&gt;
&lt;span class="nv"&gt;$regex&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'(\s.*)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// until "$this-&amp;gt;denyAccessUnlessGranted" or "!$this-&amp;gt;isGranted"&lt;/span&gt;
&lt;span class="nv"&gt;$regex&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'(\$this-&amp;gt;denyAccessUnlessGranted|!\$this-&amp;gt;isGranted)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// end of regular expression&lt;/span&gt;
&lt;span class="nv"&gt;$regex&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;')'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I ran my script with this final version of the expression.&lt;/p&gt;

&lt;p&gt;ℹ️ &lt;strong&gt;&lt;a href="https://github.com/Th0masso/symfony-security-access-control-checker" rel="noopener noreferrer"&gt;You can find the composer package based on this script on github.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The file with the regex and the rest of the logic is on the same repo : &lt;a href="https://github.com/Th0masso/symfony-security-access-control-checker/blob/main/src/Command/AccessControlCheckerCommand.php" rel="noopener noreferrer"&gt;&lt;code&gt;src/Command/AccessControlCheckerCommand.php&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="security-vulnerabilities-found-on-my-project"&gt;Security vulnerabilities found on my project&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4yjd8484p0s0dxa0g56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4yjd8484p0s0dxa0g56.png" alt="Script output" width="740" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;46 Routes Without Access Control ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53ccqgce7sqw7hx6pllq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53ccqgce7sqw7hx6pllq.png" alt="This is fine" width="740" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After investigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7 routes linked to Symfony modules: &lt;strong&gt;&lt;code&gt;web_profiler&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;twig&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;6 routes that should not be verified, therefore accessible to everyone (login page, password recovery, etc.)&lt;/li&gt;
&lt;li&gt;4 exposed API routes, which should not be verified in this way&lt;/li&gt;
&lt;li&gt;9 false positives due to special cases&lt;/li&gt;
&lt;li&gt;3 unused routes (cleaning up dead code 🤩)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;17 routes that were indeed problematic&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After identifying where each of these 17 routes was being used on the site, I realized the importance of having a strict access control strategy for your app.&lt;/p&gt;

&lt;p&gt;For these routes without permission checks, most were harmless, but some allowed critical actions on the site! &lt;strong&gt;This means that any logged-in user could perform these actions if they sent the right HTTP request&lt;/strong&gt; (thanks to Symfony's firewall rejecting non-logged-in users). However, there was no risk of privilege escalation.&lt;/p&gt;

&lt;p&gt;To secure these 17 routes, I organized a meeting with the client to define the access control to be applied for each one.&lt;/p&gt;

&lt;p&gt;Afterwards, the client lived happily ever after, and the site wasn't hacked due to an access control issue... until the day a developer introduced a new route without permission!&lt;/p&gt;

&lt;h2 id="how-to-ensure-new-routes-are-secure-too"&gt;How to ensure new routes are secure too&lt;/h2&gt;

&lt;p&gt;To ensure that new routes added are also secure, it's important to run this verification script automatically. The best way is to add it to your CI (Continuous Integration) to ensure that the script runs before each merge. If you can't add it to your CI for some reason, you can add it to your &lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks" rel="noopener noreferrer"&gt;git pre-commit or pre-push hook&lt;/a&gt; it's not as good because those hooks can be ignored by the developer by using &lt;code&gt;--no-verify&lt;/code&gt; after &lt;code&gt;git commit&lt;/code&gt; or &lt;code&gt;git push&lt;/code&gt;. &lt;br&gt;
Unfortunately, I didn't have the time to implement it in my project's CI due to time constraints.&lt;/p&gt;

&lt;p&gt;Although the performance of my script is quite good, it still takes a few seconds to run if you have hundreds of routes (approximately 5 seconds for 200 routes). To overcome this issue, one approach could be to run the script only on modified files, which would significantly reduce processing time.&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>security</category>
      <category>php</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
