<?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: Renato Silva</title>
    <description>The latest articles on DEV Community by Renato Silva (@renato_silva_71eef0fc385f).</description>
    <link>https://dev.to/renato_silva_71eef0fc385f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1698909%2F98e99ca4-6bff-40dc-9314-cf98388fbbf3.jpg</url>
      <title>DEV Community: Renato Silva</title>
      <link>https://dev.to/renato_silva_71eef0fc385f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/renato_silva_71eef0fc385f"/>
    <language>en</language>
    <item>
      <title>Giving Your API a Voice: Sending Emails with Node.js and Nodemailer</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Mon, 04 May 2026 16:29:03 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/giving-your-api-a-voice-sending-emails-with-nodejs-and-nodemailer-1b1c</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/giving-your-api-a-voice-sending-emails-with-nodejs-and-nodemailer-1b1c</guid>
      <description>&lt;p&gt;A great backend application doesn't just store data in a database; it communicates with the outside world. Whether it's a welcome email, a password reset, or a notification for a new feedback, knowing how to integrate an email service is a crucial skill for any developer.&lt;/p&gt;

&lt;p&gt;In this article, I’ll show you how I integrated email notifications into my Feedback API using &lt;strong&gt;Nodemailer&lt;/strong&gt; and the &lt;strong&gt;Provider Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;When a user submits feedback, the system should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the data.&lt;/li&gt;
&lt;li&gt;Save it to the database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notify the administrator via email.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Architecture Matters: The Provider Pattern
&lt;/h2&gt;

&lt;p&gt;Instead of hardcoding Nodemailer directly into my business logic, I used the &lt;strong&gt;Provider Pattern&lt;/strong&gt;. This keeps the code decoupled. If I decide to switch from Nodemailer to AWS SES or SendGrid in the future, I only need to change one file.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Defining the Contract (Abstract Class)
&lt;/h3&gt;

&lt;p&gt;First, we define what a &lt;code&gt;MailProvider&lt;/code&gt; should do:&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="c1"&gt;// src/providers/mail-provider/mail-provider.js&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MailProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&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;Method 'sendMail' must be implemented.&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MailProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Implementing with Nodemailer
&lt;/h3&gt;

&lt;p&gt;Now, we create the actual implementation. We use environment variables to keep credentials safe.&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="c1"&gt;// src/providers/mail-provider/nodemailer-provider.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nodemailer&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../env&lt;/span&gt;&lt;span class="dl"&gt;"&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;NodemailerProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAIL_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAIL_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAIL_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAIL_PASS&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;async&lt;/span&gt; &lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Feedback Tool &amp;lt;no-reply@feedback.com&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&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="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Integrating into the Use Case
&lt;/h3&gt;

&lt;p&gt;Our &lt;strong&gt;Use Case&lt;/strong&gt; (the business logic) doesn't care how the email is sent; it only knows that it has a &lt;code&gt;mailProvider&lt;/code&gt; capable of sending one.&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="c1"&gt;// src/use-cases/submit-feedback.js&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;submitFeedbackSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Persisting data&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feedback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;feedbackRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Sending notification&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mailProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodemailerProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mailProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Admin &amp;lt;admin@example.com&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New Feedback Received!&lt;/span&gt;&lt;span class="dl"&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="s2"&gt;`&amp;lt;h1&amp;gt;New Feedback&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;From: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&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;✅ Email sent!&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&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;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;❌ Email failed, but feedback was saved:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;feedback&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;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Don't Let Email Failures Break Your App
&lt;/h4&gt;

&lt;p&gt;I wrapped the &lt;code&gt;sendMail&lt;/code&gt; call in a &lt;code&gt;try/catch&lt;/code&gt; block. If the email service is down, the user's feedback is still saved in the database. The user shouldn't see an error 500 just because a notification failed.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Environment Validation
&lt;/h4&gt;

&lt;p&gt;Always use a tool like &lt;strong&gt;Zod&lt;/strong&gt; to validate your SMTP credentials (&lt;code&gt;MAIL_HOST&lt;/code&gt;, &lt;code&gt;MAIL_USER&lt;/code&gt;, etc.). If your credentials are missing, your app should fail early during startup, not when a user tries to send feedback.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Testing with Mailtrap
&lt;/h4&gt;

&lt;p&gt;During development, I used &lt;strong&gt;Mailtrap&lt;/strong&gt;. It acts as a "fake" SMTP server that catches your emails in a virtual inbox, so you don't accidentally spam real addresses while testing.&lt;/p&gt;

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

&lt;p&gt;Adding an email service makes your application feel professional and alive. By using providers and dependency injection, you ensure your code remains maintainable and ready for scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you handle background tasks like emails in your projects? Let's talk in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>backend</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Stop Trusting User Input: The Power of Schema Validation with Zod</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Sun, 03 May 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/stop-trusting-user-input-the-power-of-schema-validation-with-zod-55g</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/stop-trusting-user-input-the-power-of-schema-validation-with-zod-55g</guid>
      <description>&lt;p&gt;In web development, there is one golden rule: &lt;strong&gt;Never trust user input.&lt;/strong&gt; Whether it is a login form, a search bar, or an environment variable, unvalidated data is a leading cause of bugs and security vulnerabilities.&lt;/p&gt;

&lt;p&gt;For a long time, developers relied on manual &lt;code&gt;if/else&lt;/code&gt; checks or complex Regex to validate data. But then came &lt;strong&gt;Zod&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Manual Validation Hell
&lt;/h2&gt;

&lt;p&gt;Imagine you have an endpoint that receives a user profile. Without a schema validator, your code becomes cluttered and hard to maintain:&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="c1"&gt;// Manual validation is messy and error-prone&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/profile&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&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;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Manual type and existence checks&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="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&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;return&lt;/span&gt; &lt;span class="nx"&gt;reply&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid username&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&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;return&lt;/span&gt; &lt;span class="nx"&gt;reply&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Age must be a number&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&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;return&lt;/span&gt; &lt;span class="nx"&gt;reply&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid email format&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;// This gets exponentially worse with more fields&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;This approach is hard to scale, easy to forget, and doesn't provide a clear contract of what the data should look like.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Zod (The Developer's Shield)
&lt;/h3&gt;

&lt;p&gt;Zod allows you to define a &lt;strong&gt;Schema&lt;/strong&gt; a single source of truth for your data. If the incoming data doesn't match the schema, Zod catches it before it even touches your business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Simple and Expressive Syntax
&lt;/h2&gt;

&lt;p&gt;With Zod, the messy code above becomes clean and declarative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Defining the blueprint for your data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Single line to validate the entire object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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;body&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Returns a detailed, formatted error object to the client&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reply&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&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;h2&gt;
  
  
  2. Validating Environment Variables
&lt;/h2&gt;

&lt;p&gt;One of the most powerful ways to use Zod is validating &lt;code&gt;.env&lt;/code&gt; files. It ensures the server doesn't even start if a critical configuration (like a Database URL or a JWT Secret) is missing or malformed. This &lt;strong&gt;Fail-Fast&lt;/strong&gt; strategy saves hours of debugging in production environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Coercion: Dealing with Input Strings
&lt;/h2&gt;

&lt;p&gt;Data from HTML forms or URL parameters often arrives as strings, even if they represent numbers or dates. Zod's &lt;strong&gt;Coercion&lt;/strong&gt; feature automatically converts these values during validation:&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="c1"&gt;// Automatically converts the string "42" to the number 42&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&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;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "number"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Should You Care?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fail-Fast&lt;/strong&gt;: Catch errors at the very boundaries of your application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Confidence&lt;/strong&gt;: You can write your logic knowing exactly what shape your data has.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;: Clear, automated error messages for both you and your API consumers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Switching to schema-based validation with Zod isn't just about writing less code; it's about writing *&lt;em&gt;predictable *&lt;/em&gt; code. It shifts your mindset from "I hope this data is correct" to "I know this data is correct."&lt;/p&gt;

&lt;p&gt;I recently started implementing Zod in my professional projects, and the clarity it brings to the architecture is a point of no return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you tried Zod yet, or are you still handling validations manually? Let's discuss in the comments!&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>zod</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From "Student" API to Professional Grade: JWT Auth, Swagger, and Zod</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Sun, 26 Apr 2026 14:50:15 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/from-student-api-to-professional-grade-jwt-auth-swagger-and-3f20</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/from-student-api-to-professional-grade-jwt-auth-swagger-and-3f20</guid>
      <description>&lt;p&gt;Building an API that works on your local machine is just the first step. But what separates a hobby project from a production-ready product? In today's post, I'll show how I transformed my feedback system into a robust, secure, and fully documented application.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 1. The Digital "Key": Implementing JWT
&lt;/h2&gt;

&lt;p&gt;Up until now, anyone who discovered my API URL could read every feedback ever submitted. In a real-world scenario, this is a critical privacy failure.&lt;/p&gt;

&lt;p&gt;To solve this, I implemented &lt;strong&gt;JWT (JSON Web Token)&lt;/strong&gt; using &lt;code&gt;@fastify/jwt&lt;/code&gt;. The flow now works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The admin logs in with secure credentials.&lt;/li&gt;
&lt;li&gt;The API generates a signed token.&lt;/li&gt;
&lt;li&gt;This token must be sent in the header of every request to protected routes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The game-changer here was using a Fastify &lt;code&gt;decorator&lt;/code&gt; to create an &lt;code&gt;authenticate&lt;/code&gt; hook. Now, protecting a route is as simple as adding a single line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  📖 2. Pro Swagger: Documentation that actually works
&lt;/h2&gt;

&lt;p&gt;Documentation isn't enough; it needs to be useful. I was already using Swagger, but the challenge was: &lt;strong&gt;How do I test protected routes without leaving the browser?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I configured &lt;code&gt;securitySchemes&lt;/code&gt; in Swagger to support the &lt;strong&gt;Bearer Auth&lt;/strong&gt; pattern. The result? An "Authorize" button with a lock icon appeared at the top of the page. Now, I can log in through the interface itself, paste the token, and test private endpoints directly. Pure productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛡️ 3. Environment Shielding with Zod
&lt;/h2&gt;

&lt;p&gt;A common mistake is an application failing in production because someone forgot to set a variable in the &lt;code&gt;.env&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;To prevent this, I used &lt;strong&gt;Zod&lt;/strong&gt; to create an environment "validator." I built a schema that checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;JWT_SECRET&lt;/code&gt; exists and is secure.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;DATABASE_URL&lt;/code&gt; is correct.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;PORT&lt;/code&gt; is actually a number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these variables are missing or wrong, the application crashes immediately at startup and tells you exactly what's wrong. This &lt;strong&gt;Fail-fast&lt;/strong&gt; approach is a senior-level concept that brings real peace of mind.&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="c1"&gt;// Validating our environment variables with Zod&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;envSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&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="s1"&gt;test&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="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JWT_SECRET is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_URL is required&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;_env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;envSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&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;_env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&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;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Invalid environment variables!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&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="c1"&gt;// Stop the app if config is wrong&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🏗️ 4. Refactoring and Clean Architecture
&lt;/h3&gt;

&lt;p&gt;As the API grew, the main file (&lt;code&gt;app.js&lt;/code&gt;) started getting cluttered. I applied the "Separation of Concerns" principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routes&lt;/strong&gt;: Specific files to define paths and schemas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Controllers&lt;/strong&gt;: The bridge between HTTP and business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App&lt;/strong&gt;: Only orchestration and plugin registration.&lt;/p&gt;

&lt;p&gt;This organization allowed the project to breathe and made maintenance much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Pro-tip: Handling Git and .env
&lt;/h3&gt;

&lt;p&gt;During the process, I learned (the hard way!) that adding &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; after it's already tracked doesn't work. I had to clear the Git cache (&lt;code&gt;git rm --cached .env&lt;/code&gt;) to ensure my secrets weren't stored in the commit history. Pro-tip: security is never too much!&lt;/p&gt;

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

&lt;p&gt;My feedback API is no longer just code that saves data; it's an ecosystem with &lt;strong&gt;Testing (Vitest), Security (JWT), Interactive Docs (Swagger), and Continuous Deployment (Render)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rj-dev/minimalist-feedback-api" rel="noopener noreferrer"&gt;Link to the project&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What tools are you using to secure your APIs? Let’s talk in the comments! 👇&lt;/p&gt;

</description>
      <category>node</category>
      <category>fastify</category>
      <category>jwt</category>
      <category>cleanarchitecture</category>
    </item>
    <item>
      <title>Sleeping Better at Night: Integration Tests and CI/CD for my Feedback API</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Thu, 23 Apr 2026 19:08:11 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/sleeping-better-at-night-integration-tests-and-cicd-for-my-feedback-api-g7</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/sleeping-better-at-night-integration-tests-and-cicd-for-my-feedback-api-g7</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/renato_silva_71eef0fc385f/defending-your-domain-schema-validation-and-global-error-handling-114m"&gt;last post&lt;/a&gt;, I implemented schema validation to protect my domain. But as the project grows, checking every route manually with Insomnia or Postman becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;Today, I automated that process using &lt;strong&gt;Vitest&lt;/strong&gt; and &lt;strong&gt;Supertest&lt;/strong&gt;, and integrated it into my &lt;strong&gt;CI/CD pipeline&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Goal: Real Confidence
&lt;/h3&gt;

&lt;p&gt;I wanted to ensure that when I call the &lt;code&gt;/feedbacks&lt;/code&gt; endpoint:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Valid data is correctly persisted in the database.&lt;/li&gt;
&lt;li&gt;Invalid data is blocked by my Zod schemas with a proper &lt;code&gt;400 Bad Request&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Setup: Environment Isolation
&lt;/h3&gt;

&lt;p&gt;One golden rule of testing is: &lt;strong&gt;Never test on your production database.&lt;/strong&gt; I adjusted my &lt;code&gt;SqliteFeedbackRepository&lt;/code&gt; to accept a database path, allowing me to use a dedicated &lt;code&gt;database.test.db&lt;/code&gt; during test execution. Using &lt;code&gt;cross-env&lt;/code&gt;, I flag the environment so the app knows which "mode" to run in.&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="c1"&gt;// src/app.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databasePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&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;./database.test.db&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;./database.db&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;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SqliteFeedbackRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;databasePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Test Suite
&lt;/h3&gt;

&lt;p&gt;Using &lt;strong&gt;Vitest&lt;/strong&gt;, I created an E2E (End-to-End) test suite. It simulates real HTTP requests to the Fastify server and asserts the results.&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="c1"&gt;// tests/submit-feedback.test.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterAll&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;supertest&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit Feedback E2E&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should be able to submit a valid feedback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ready&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/feedbacks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&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;This is a valid feedback message from integration test.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should not be able to submit feedback with invalid email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ready&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/feedbacks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid-email&lt;/span&gt;&lt;span class="dl"&gt;"&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;Valid message length&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&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="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;
  
  
  Continuous Integration (CI) with Render
&lt;/h3&gt;

&lt;p&gt;Testing on your local machine is great, but enforcing tests before deployment is better. I updated my **Render **configuration to include the test suite in the build process.&lt;/p&gt;

&lt;p&gt;By changing the &lt;strong&gt;Build Command&lt;/strong&gt; to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install &amp;amp;&amp;amp; npm test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I’ve created a safety net: &lt;strong&gt;If a single test fails, the build fails&lt;/strong&gt;. The broken version will never reach the users, and the previous stable version stays online. This is the essence of &lt;strong&gt;Continuous Integration&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges Overcome: The Windows File Lock
&lt;/h3&gt;

&lt;p&gt;If you are developing on Windows, you might encounter the resource busy or locked error when trying to delete the SQLite test file after the tests. This happens because the process still holds a lock on the file.&lt;/p&gt;

&lt;p&gt;The fix? Properly closing the Fastify instance and the repository connection:&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;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// Ensure the database connection is closed before unlinking the file&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;Now, every time I run &lt;code&gt;git push&lt;/code&gt;, I get instant feedback. Automated tests are not a luxury; they are the foundation of professional software delivery. They give you the freedom to refactor and evolve your code without the fear of breaking what’s already working.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next?&lt;/strong&gt; My API is now validated, tested, and protected by a CI/CD pipeline. To take it to the next level, I'll be looking into &lt;strong&gt;API Documentation with Swagger&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;How do you handle testing in your deployment pipeline? Let's discuss in the comments!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>vitest</category>
      <category>devops</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Defending Your Domain: Schema Validation and Global Error Handling</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Mon, 20 Apr 2026 18:08:05 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/defending-your-domain-schema-validation-and-global-error-handling-114m</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/defending-your-domain-schema-validation-and-global-error-handling-114m</guid>
      <description>&lt;h2&gt;
  
  
  Defending Your Domain: Schema Validation and Global Error Handling
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/renato_silva_71eef0fc385f/architecture-saved-my-project-swapping-an-orm-for-pure-sql-in-minutes-393j"&gt;previous post&lt;/a&gt;, I shared how Clean Architecture allowed me to swap my database layer in minutes. But architecture isn't just about being able to change parts; it's about &lt;strong&gt;protecting your system&lt;/strong&gt; from the outside world.&lt;/p&gt;

&lt;p&gt;Today, I added a "bouncer" to my API: &lt;strong&gt;Schema Validation&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Silent Failures
&lt;/h3&gt;

&lt;p&gt;Without validation, your Use Case might receive an empty name, a fake email, or a message that is too short. This leads to corrupted data in your database and unexpected "Internal Server Errors" (500) that give no clue to the user about what went wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Zod + Global Error Handler
&lt;/h3&gt;

&lt;p&gt;I chose &lt;strong&gt;Zod&lt;/strong&gt; for schema validation because it's type-safe, extremely popular in the modern Node.js ecosystem, and integrates perfectly with JavaScript.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Defining the Contract
&lt;/h4&gt;

&lt;p&gt;Instead of just accepting any object, I defined exactly what a &lt;code&gt;Feedback&lt;/code&gt; should look like. This contract ensures that the data follows our business rules before any processing happens:&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="c1"&gt;// src/use-cases/submit-feedback-validator.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&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;submitFeedbackSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Name must be at least 3 characters long&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid email address&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Message must be at least 10 characters long&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;submitFeedbackSchema&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  2. Future-Proof Error Handling
&lt;/h4&gt;

&lt;p&gt;Libraries evolve fast. During implementation, I noticed that some built-in formatting methods like &lt;code&gt;.format()&lt;/code&gt; or &lt;code&gt;.flatten()&lt;/code&gt; were becoming deprecated or less flexible in the latest versions of Zod.&lt;/p&gt;

&lt;p&gt;Instead of relying on unstable methods, I implemented a custom mapper to extract Zod's raw issues into a clean, developer-friendly response. By using a &lt;strong&gt;Global Error Handler&lt;/strong&gt; in Fastify, I kept my routes clean and avoided repetitive &lt;code&gt;try/catch&lt;/code&gt; blocks.&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="c1"&gt;// src/server.js&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setErrorHandler&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&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;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Catching Zod validation errors&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;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZodError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Map raw issues into a clear field-to-message object&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validationErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;acc&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;return&lt;/span&gt; &lt;span class="nx"&gt;reply&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;send&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="s1"&gt;Validation failed.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validationErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Fallback for unexpected generic errors&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reply&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;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&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="s1"&gt;Internal server error.&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;&lt;strong&gt;The Result: Better UX and Cleaner Code&lt;/strong&gt;&lt;br&gt;
Now, if a user sends invalid data, the API doesn't just crash or return a generic error. It responds with a &lt;strong&gt;400 Bad Request&lt;/strong&gt; and a clear explanation of what needs to be fixed:&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;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Validation failed."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid email address"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Message must be at least 10 characters long"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Key Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fail Fast:&lt;/strong&gt; Stop invalid data at the door before it touches your business logic or your database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Global Handling:&lt;/strong&gt; Centralize your error logic. This makes your application easier to maintain and keeps your controllers focused on the "happy path".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Be Adaptable:&lt;/strong&gt; When library methods get deprecated, don't be afraid to write your own mappers. It gives you more control and makes your code more resilient to library updates.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What's next?&lt;/strong&gt; Now that the API is safe and resilient, the next step is to ensure it stays working as we add new features. My next post will be about &lt;strong&gt;Automated Integration Testing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Have you ever struggled with "dirty data" in your database? Let's talk about it in the comments!&lt;/p&gt;

</description>
      <category>node</category>
      <category>zod</category>
      <category>fastify</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Architecture Saved My Project - Swapping an ORM for Pure SQL in Minutes</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Fri, 17 Apr 2026 19:02:47 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/architecture-saved-my-project-swapping-an-orm-for-pure-sql-in-minutes-393j</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/architecture-saved-my-project-swapping-an-orm-for-pure-sql-in-minutes-393j</guid>
      <description>&lt;p&gt;In the world of software development, we are often told that &lt;strong&gt;Clean Architecture&lt;/strong&gt; and &lt;strong&gt;SOLID principles&lt;/strong&gt; are "over-engineering" for small projects. This week, I proved that theory wrong in the most practical way possible: &lt;strong&gt;I fired my ORM.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Goal
&lt;/h3&gt;

&lt;p&gt;I was building a &lt;strong&gt;Minimalist Feedback API&lt;/strong&gt;. The plan was simple: use &lt;strong&gt;Prisma 6&lt;/strong&gt; with &lt;strong&gt;SQLite&lt;/strong&gt; to persist data. I had already built my Core Domain (Entities) and Business Logic (Use Cases), keeping them isolated from external frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: When Tools Fight Back
&lt;/h3&gt;

&lt;p&gt;As I moved to the infrastructure layer, I integrated Prisma 6. However, I ran into unexpected breaking changes and rigid configuration requirements—specifically the deprecation of certain URL properties in the schema files that were previously standard.&lt;/p&gt;

&lt;p&gt;I had two choices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spend hours debugging a tool's internal configuration and fighting its "opinionated" setup.&lt;/li&gt;
&lt;li&gt;Rely on my architecture to swap the tool entirely.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Pivot: From Prisma to SQLite
&lt;/h3&gt;

&lt;p&gt;Because I used the &lt;strong&gt;Repository Pattern&lt;/strong&gt;, my Use Cases didn't know Prisma existed. They only knew about an abstract interface (or contract) called &lt;code&gt;FeedbackRepository&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I decided to drop the ORM and implement a native &lt;code&gt;SqliteFeedbackRepository&lt;/code&gt; using the standard &lt;code&gt;sqlite3&lt;/code&gt; driver. &lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Repository Swap
&lt;/h4&gt;

&lt;p&gt;I created a new implementation. Note how the logic remains focused only on data persistence, keeping the code clean and predictable:&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="c1"&gt;// src/repositories/sqlite/sqlite-feedback-repository.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sqlite3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqlite3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FeedbackRepository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../feedback-repository&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomUUID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Connect to the database file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./database.db&lt;/span&gt;&lt;span class="dl"&gt;'&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;SqliteFeedbackRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FeedbackRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Persists a new feedback into the SQLite database
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&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;createdAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&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="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO feedbacks (id, name, email, message, createdAt) VALUES (?, ?, ?, ?, ?)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finalize&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="cm"&gt;/**
   * Retrieves all feedbacks from the database
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;listAll&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM feedbacks ORDER BY createdAt DESC&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Dependency Injection in Action
&lt;/h4&gt;

&lt;p&gt;​In my server.js, the only change required was the import line. This is the &lt;strong&gt;Liskov Substitution Principle&lt;/strong&gt; in the real world: the ability to replace an implementation with another without breaking the system.&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="c1"&gt;// server.js&lt;/span&gt;
&lt;span class="c1"&gt;// Swapping the implementation is as simple as changing one line:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SqliteFeedbackRepository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./repositories/sqlite/sqlite-feedback-repository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The rest of the setup remains identical!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SqliteFeedbackRepository&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;submitFeedback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SubmitFeedback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Use Case stays the same!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;​If my database logic had been leaked into my Routes or Use Cases, a change like this would have forced me to rewrite a significant portion of the application.&lt;br&gt;
​Instead, it took me &lt;strong&gt;less than 10 minutes&lt;/strong&gt; to pivot and have a fully working API again.&lt;br&gt;
​&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Software architecture is the art of postponing decisions until you have more data. Or in this case, the art of making tools replaceable."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't be a hostage to your tools:&lt;/strong&gt; If a library is causing more friction than value, be ready to replace it.&lt;br&gt;
​&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Isolate your Domain:&lt;/strong&gt; Keep your business rules pure. They should not care if you are using an ORM, a Cloud DB, or a simple text file.&lt;br&gt;
​&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pragmatism &amp;gt; Hype:&lt;/strong&gt; Sometimes, "boring" technology like native SQL is exactly what you need to keep the project moving forward.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What about you?&lt;/strong&gt; &lt;br&gt;
Have you ever had to swap a core library mid-project? Let's discuss in the comments!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Check out the live API here:&lt;/strong&gt; [&lt;a href="https://https://minimalist-feedback-api.onrender.com/feedbacks" rel="noopener noreferrer"&gt;https://https://minimalist-feedback-api.onrender.com/feedbacks&lt;/a&gt;]&lt;br&gt;
&lt;em&gt;(Note: Since this uses a free-tier ephemeral SQLite, the data resets on every deploy)&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The biggest takeaway from this project was learning how to navigate version incompatibilities between technologies. &lt;/p&gt;

&lt;p&gt;I saw firsthand how a solid architecture acts as a safety net. It turned what could have been a complete project restart into a series of manageable, easy-to-execute changes. Tools will always change, but good architecture ensures your business rules remain constant.&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Beyond the Terminal: Why My Next Project is a Minimalist API</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Tue, 31 Mar 2026 17:53:05 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/beyond-the-terminal-why-my-next-project-is-a-minimalist-api-fp6</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/beyond-the-terminal-why-my-next-project-is-a-minimalist-api-fp6</guid>
      <description>&lt;p&gt;Moving from local tools to network services. Let's talk about the architecture and challenges of building a professional Feedback API.&lt;/p&gt;




&lt;p&gt;In my last posts, I shared the journey of building a CLI tool with a focus on simple architecture and automated testing. It was a success in terms of &lt;strong&gt;local automation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But in the real world, software often needs to live on the network. It needs to be available for multiple users, persist data in a database, and handle concurrent requests.&lt;/p&gt;

&lt;p&gt;That’s why my next project is a &lt;strong&gt;Minimalist Feedback API&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Here is why I’m building it and the "Senior-level" architectural choices I’m making.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Use Case: A Universal Feedback Bridge
&lt;/h2&gt;

&lt;p&gt;Many developers build beautiful static portfolios (using Astro, Next.js, or Hugo). But when it comes to adding a "Contact Me" or "Feedback" form, they hit a wall. They usually end up using a third-party paid service or a complex backend.&lt;/p&gt;

&lt;p&gt;I’m building a lightweight, professional service that any static site can "talk to" to store messages and feedbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architectural Shift: Clean Thinking
&lt;/h2&gt;

&lt;p&gt;For this API, I’m not just throwing everything into a single &lt;code&gt;server.js&lt;/code&gt; file. I’m introducing a simplified version of &lt;strong&gt;Clean Architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Domain First, Framework Second
&lt;/h3&gt;

&lt;p&gt;The "Business Logic" (what defines a valid feedback) should not depend on whether I'm using Express, Fastify, or even if I'm saving to MongoDB or PostgreSQL. The heart of the application will be isolated.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Validation as a First-Class Citizen
&lt;/h3&gt;

&lt;p&gt;In a CLI, you control the input. In an API, the world is a chaotic place. I’ll be using &lt;strong&gt;Zod&lt;/strong&gt; to ensure that only "clean" data enters my system. No validation, no entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dependency Injection
&lt;/h3&gt;

&lt;p&gt;I want to be able to test my logic without actually hitting a real database. By using Dependency Injection, I can swap a "Real Repository" for a "Mock Repository" in my tests instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech Stack for 2024/2025
&lt;/h2&gt;

&lt;p&gt;To keep it modern and fast, I’ve chosen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; (Runtime)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fastify&lt;/strong&gt;: Faster and more modern than Express, with great TypeScript support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite + Prisma&lt;/strong&gt;: For easy setup and type-safe database queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jest&lt;/strong&gt;: Because a professional API without tests is just a disaster waiting to happen.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What to Expect in the Next Posts
&lt;/h2&gt;

&lt;p&gt;I’ll be documenting the &lt;strong&gt;Step-by-Step&lt;/strong&gt; construction of this API. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; Designing the Domain and Entities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; Setting up the HTTP Layer with Fastify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; Database Integration and Migrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; Deploying with Docker/Cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't just to show you "how to code an API," but how to &lt;strong&gt;engineer a service&lt;/strong&gt; that you’d be proud to show in a technical interview.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you build an API, what is your biggest challenge? Authentication, Database design, or Deployment? Let's talk in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>architecture</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Stop Losing Your Documentation Images: Building a Practical CLI with Node.js</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Sun, 29 Mar 2026 15:58:22 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/stop-losing-your-documentation-images-building-a-practical-cli-with-nodejs-1de4</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/stop-losing-your-documentation-images-building-a-practical-cli-with-nodejs-1de4</guid>
      <description>&lt;p&gt;External images in Markdown are a risk. I built a tool to solve this, focusing on simple architecture and testability.&lt;/p&gt;




&lt;p&gt;Have you ever opened an old GitHub repository or a blog post only to find "broken image" icons everywhere? &lt;/p&gt;

&lt;p&gt;It happens because we rely on external hosts. If the host goes down, your documentation dies. &lt;/p&gt;

&lt;p&gt;In my last articles, I talked about &lt;strong&gt;Senior Mindset&lt;/strong&gt; and &lt;strong&gt;Simple Architecture&lt;/strong&gt;. Today, I’m putting those words into practice. I built &lt;strong&gt;MIL (Markdown Image Localizer)&lt;/strong&gt;: a CLI tool that finds remote images in your files, downloads them locally, and updates your links automatically.&lt;/p&gt;

&lt;p&gt;Here is how I built it and why the "how" matters more than the code itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The Fragility of the Web
&lt;/h2&gt;

&lt;p&gt;We often use direct links for images in Markdown. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dependency:&lt;/strong&gt; If the source site changes, your image breaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline Access:&lt;/strong&gt; You can't read your docs properly without internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual Labor:&lt;/strong&gt; Downloading 20 images and updating 20 links manually is a waste of an engineer's time.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Solution: MIL (Markdown Image Localizer)
&lt;/h2&gt;

&lt;p&gt;I designed this tool following three core principles: &lt;strong&gt;Simplicity, Separation of Concerns, and Testability.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Simple Architecture
&lt;/h3&gt;

&lt;p&gt;Instead of one giant file, I split the logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Parser&lt;/code&gt;: Pure logic to find URLs using Regex.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Downloader&lt;/code&gt;: Handles the I/O and HTTP requests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Processor&lt;/code&gt;: Orchestrates the flow.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLI&lt;/code&gt;: The entry point that talks to the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Because if I want to turn this into a VS Code extension or a Web API tomorrow, the "Core" logic is already isolated.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reliability through Testing
&lt;/h3&gt;

&lt;p&gt;I didn't just "hope" the Regex worked. I implemented unit tests using &lt;strong&gt;Jest&lt;/strong&gt;. &lt;br&gt;
Before the first commit, I ensured that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It ignores local images.&lt;/li&gt;
&lt;li&gt;It handles duplicate URLs (no redundant downloads).&lt;/li&gt;
&lt;li&gt;It returns empty results when no images exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;"Code without tests is just a rumor."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  See the Code
&lt;/h2&gt;

&lt;p&gt;You can check the full source code, the folder structure, and the tests here:&lt;br&gt;
👉 &lt;a href="https://github.com/rj-dev/markdown-image-localizer" rel="noopener noreferrer"&gt;GitHub: markdown-image-localizer&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to run it:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
git clone [https://github.com/rj-dev/markdown-image-localizer](https://github.com/rj-dev/markdown-image-localizer)
npm install
node index.js your-file.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>node</category>
      <category>opensource</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Art of Finishing: Why a Working "Simple" Project is Worth More Than a Perfect "Unfinished" One</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Sun, 29 Mar 2026 11:21:40 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/the-art-of-finishing-why-a-working-simple-project-is-worth-more-than-a-perfect-unfinished-one-3h7j</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/the-art-of-finishing-why-a-working-simple-project-is-worth-more-than-a-perfect-unfinished-one-3h7j</guid>
      <description>&lt;p&gt;We all have a folder full of unfinished projects. Here is why finishing a small, simple tool is the best thing you can do for your career right now.&lt;/p&gt;




&lt;p&gt;We’ve all been there. &lt;/p&gt;

&lt;p&gt;You have a "revolutionary" idea for a SaaS. You spend three days picking the perfect CSS library. Two days setting up a complex Docker environment. Another week debating which state management tool to use.&lt;/p&gt;

&lt;p&gt;And then... you stop. &lt;/p&gt;

&lt;p&gt;The project stays in your &lt;code&gt;/dev&lt;/code&gt; folder, gathering digital dust. &lt;/p&gt;

&lt;p&gt;In the professional world, &lt;strong&gt;an unfinished "perfect" architecture is worth zero.&lt;/strong&gt; A simple, working, and tested script that solves a real problem is worth everything.&lt;/p&gt;

&lt;p&gt;As I prepare to launch my first practical projects here on DEV.to, I want to talk about the "Mindset of Done."&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The "Scope Creep" Trap
&lt;/h2&gt;

&lt;p&gt;The reason we don't finish things is that we let the scope grow too fast. &lt;br&gt;
&lt;em&gt;"It needs a login!"&lt;/em&gt;&lt;br&gt;
&lt;em&gt;"It needs a dashboard!"&lt;/em&gt;&lt;br&gt;
&lt;em&gt;"It needs to be mobile-responsive!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Senior Approach:&lt;/strong&gt; Cut everything until you have the &lt;strong&gt;Minimum Viable Value.&lt;/strong&gt; If you are building a CLI tool to rename files, just make it rename files. Everything else is a distraction for "Version 2."&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Shipping is a Feature
&lt;/h2&gt;

&lt;p&gt;Shipping—actually putting your code on GitHub with a README and a release—is a skill. &lt;/p&gt;

&lt;p&gt;When you finish a project, you deal with the "unfun" parts that teach you the most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixing that last annoying bug.&lt;/li&gt;
&lt;li&gt;Writing the installation guide.&lt;/li&gt;
&lt;li&gt;Setting up a simple CI/CD.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recruiters don't look for "Genius in Progress." They look for &lt;strong&gt;"Engineers who Deliver."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The "90/10" Rule of Software
&lt;/h2&gt;

&lt;p&gt;The first 90% of a project is fun. The last 10% (documentation, edge cases, final polish) is where the real engineering happens. &lt;/p&gt;

&lt;p&gt;Most developers quit at 90%. If you can push through that last 10%, you are already ahead of the majority of the market.&lt;/p&gt;




&lt;h2&gt;
  
  
  How AI Helps You Cross the Finish Line
&lt;/h2&gt;

&lt;p&gt;I use AI specifically to kill the "Last 10%" friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"The Boring Stuff":&lt;/strong&gt; I ask AI to generate the &lt;code&gt;package.json&lt;/code&gt; or &lt;code&gt;requirements.txt&lt;/code&gt; based on my imports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"README Architect":&lt;/strong&gt; I describe my project and ask: &lt;em&gt;"Write a clear 'How to Install' section for a beginner."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Edge Case Hunter":&lt;/strong&gt; I share my logic and ask: &lt;em&gt;"What is the one thing that will make this crash when a user runs it for the first time?"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI is my "Finishing Assistant." It handles the friction so I can focus on the core value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next Week: The First Commit
&lt;/h2&gt;

&lt;p&gt;I’m done talking about theory. &lt;/p&gt;

&lt;p&gt;Next week, I’ll be sharing the first repository of this series: &lt;strong&gt;A CLI Tool built with Simple Architecture.&lt;/strong&gt; It’s not going to be a 10,000-line monster. It’s going to be a clean, tested, and &lt;strong&gt;finished&lt;/strong&gt; piece of software that solves a specific problem. I’m doing this to show you that "Simple and Done" is the ultimate senior flex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many unfinished projects do you have in your folders right now? Let's be honest in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>opensource</category>
      <category>career</category>
    </item>
    <item>
      <title>The Seniority Gap: Why Companies Hire Solutions, Not Just Syntax</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Thu, 19 Mar 2026 19:37:49 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/the-seniority-gap-why-companies-hire-solutions-not-just-syntax-1l1e</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/the-seniority-gap-why-companies-hire-solutions-not-just-syntax-1l1e</guid>
      <description>&lt;p&gt;Technical skills are the baseline. But what actually gets you hired at high-level positions? Let's talk about the gap between coding and engineering.&lt;/p&gt;

&lt;p&gt;I’ve interviewed hundreds of developers over the last decade. &lt;/p&gt;

&lt;p&gt;Many of them were brilliant at solving LeetCode puzzles. They knew every obscure feature of their favorite language. &lt;/p&gt;

&lt;p&gt;But when I asked them: &lt;em&gt;"How would this code impact the business if the requirements changed tomorrow?"&lt;/em&gt;... they went silent.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;"Seniority Gap."&lt;/strong&gt; Companies aren't looking for people who can just type code. They are looking for Engineers who can bridge the gap between a business problem and a sustainable technical solution.&lt;/p&gt;

&lt;p&gt;Before I share my upcoming practical projects on GitHub, I want to talk about the three pillars that actually make a developer "Hirable" in today's market.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Context Over Syntax
&lt;/h2&gt;

&lt;p&gt;A junior developer focuses on &lt;em&gt;how&lt;/em&gt; to write a loop. A senior developer focuses on &lt;em&gt;why&lt;/em&gt; that loop is necessary and if there’s a way to avoid it entirely.&lt;/p&gt;

&lt;p&gt;When you show a project to a recruiter, don't just show the code. Explain the &lt;strong&gt;context&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What problem were you solving?&lt;/li&gt;
&lt;li&gt;Why did you choose this specific approach over others?&lt;/li&gt;
&lt;li&gt;What were the constraints?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. The "Maintainability" Mindset
&lt;/h2&gt;

&lt;p&gt;Code is written once, but read and modified a thousand times. &lt;/p&gt;

&lt;p&gt;If your project is "clever" but impossible to understand six months later, it’s a liability, not an asset. &lt;/p&gt;

&lt;p&gt;In the professional world, &lt;strong&gt;clarity is more valuable than brilliance.&lt;/strong&gt; High-level developers write code that looks "obvious" because they’ve done the hard work of simplifying the complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ownership and "The Big Picture"
&lt;/h2&gt;

&lt;p&gt;Professional engineering is about taking ownership. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s about knowing how your code is deployed. &lt;/li&gt;
&lt;li&gt;It’s about understanding if your API is slow for the end-user. &lt;/li&gt;
&lt;li&gt;It’s about ensuring that your tests actually protect the business logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you demonstrate that you care about the &lt;strong&gt;entire lifecycle&lt;/strong&gt; of the software, you stop being a "task-taker" and start being a "partner."&lt;/p&gt;




&lt;h2&gt;
  
  
  How AI Helps You Close the Gap
&lt;/h2&gt;

&lt;p&gt;AI is the ultimate tool for expanding your perspective beyond the code editor. Use it to simulate "Business Thinking":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Roleplay as a Product Manager":&lt;/strong&gt; Feed your project idea to the AI and ask: &lt;em&gt;"What are the 3 biggest risks for this project from a business perspective?"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Critique my Architecture":&lt;/strong&gt; Ask: &lt;em&gt;"If I need to scale this to 10x the users, where will it break first?"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Documentation for Humans":&lt;/strong&gt; Use AI to help you write a README that speaks to both developers (technical) and managers (value-driven).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI handles the "how," which leaves you free to master the "why."&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought: Setting the Stage
&lt;/h2&gt;

&lt;p&gt;In my next posts, I will finally open my GitHub for the projects I’ve been talking about. &lt;/p&gt;

&lt;p&gt;But I won't just dump code there. I’ll be applying everything we’ve discussed: &lt;strong&gt;Simplicity, Testing, and Business Value.&lt;/strong&gt; I want to show you that a "Senior Portfolio" isn't about how many stars you have, but about how clearly you demonstrate your ability to solve real problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are you ready to stop just "coding" and start "engineering"? What’s the biggest non-technical challenge you face at work? Let’s discuss below!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>softwaredevelopment</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mistakes I Made as a Developer (And What They Taught Me)</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Sun, 15 Mar 2026 09:17:50 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/mistakes-i-made-as-a-developer-and-what-they-taught-me-4l94</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/mistakes-i-made-as-a-developer-and-what-they-taught-me-4l94</guid>
      <description>&lt;p&gt;If you stay long enough in software development, you will make mistakes.&lt;/p&gt;

&lt;p&gt;Not small ones.&lt;/p&gt;

&lt;p&gt;Real ones.&lt;/p&gt;

&lt;p&gt;Bugs in production.&lt;br&gt;&lt;br&gt;
Bad architectural decisions.&lt;br&gt;&lt;br&gt;
Features that had to be rewritten from scratch.&lt;/p&gt;

&lt;p&gt;When I started my career, I thought good developers avoided mistakes.&lt;/p&gt;

&lt;p&gt;Now I understand something different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Good developers learn faster from them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here are some mistakes I made during my journey — and what they taught me.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Trying to Know Everything
&lt;/h2&gt;

&lt;p&gt;Early in my career I believed I needed to understand everything.&lt;/p&gt;

&lt;p&gt;Every framework.&lt;br&gt;&lt;br&gt;
Every new tool.&lt;br&gt;&lt;br&gt;
Every programming language trend.&lt;/p&gt;

&lt;p&gt;The result?&lt;/p&gt;

&lt;p&gt;Constant context switching.&lt;/p&gt;

&lt;p&gt;I was learning many things, but mastering none.&lt;/p&gt;

&lt;p&gt;What I eventually realized is that depth matters more than breadth.&lt;/p&gt;

&lt;p&gt;It's better to deeply understand a few tools than to superficially know many.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Writing “Perfect” Code Too Early
&lt;/h2&gt;

&lt;p&gt;Another mistake I made was trying to write perfect code from the beginning.&lt;/p&gt;

&lt;p&gt;Spending too much time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refactoring early&lt;/li&gt;
&lt;li&gt;overthinking architecture&lt;/li&gt;
&lt;li&gt;worrying about edge cases that never happened&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But software development is iterative.&lt;/p&gt;

&lt;p&gt;Often the best approach is:&lt;/p&gt;

&lt;p&gt;Write something simple → make it work → improve it later.&lt;/p&gt;

&lt;p&gt;Perfection too early slows progress.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Avoiding Asking Questions
&lt;/h2&gt;

&lt;p&gt;For a long time I hesitated to ask questions.&lt;/p&gt;

&lt;p&gt;I thought it might make me look inexperienced.&lt;/p&gt;

&lt;p&gt;So instead I would spend hours stuck on problems that could have been clarified in minutes.&lt;/p&gt;

&lt;p&gt;Eventually I realized something important:&lt;/p&gt;

&lt;p&gt;Good teams respect thoughtful questions.&lt;/p&gt;

&lt;p&gt;Asking for help is not weakness.&lt;/p&gt;

&lt;p&gt;It's efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Underestimating the Importance of Communication
&lt;/h2&gt;

&lt;p&gt;At first I believed programming was mostly about writing good code.&lt;/p&gt;

&lt;p&gt;But over time I learned that communication is just as important.&lt;/p&gt;

&lt;p&gt;Explaining decisions.&lt;br&gt;&lt;br&gt;
Discussing trade-offs.&lt;br&gt;&lt;br&gt;
Aligning with teammates.&lt;/p&gt;

&lt;p&gt;Great developers are not only good coders.&lt;/p&gt;

&lt;p&gt;They are also good communicators.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Ignoring the Bigger Picture
&lt;/h2&gt;

&lt;p&gt;When you're early in your career, it's easy to focus only on the code in front of you.&lt;/p&gt;

&lt;p&gt;But software exists inside larger systems.&lt;/p&gt;

&lt;p&gt;Understanding things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;system architecture&lt;/li&gt;
&lt;li&gt;user impact&lt;/li&gt;
&lt;li&gt;scalability&lt;/li&gt;
&lt;li&gt;product goals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;changes the way you approach problems.&lt;/p&gt;

&lt;p&gt;Coding becomes more intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Waiting Too Long to Build Personal Projects
&lt;/h2&gt;

&lt;p&gt;For a long time, most of my coding happened only at work.&lt;/p&gt;

&lt;p&gt;But personal projects teach things that professional environments sometimes don't.&lt;/p&gt;

&lt;p&gt;They give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full ownership&lt;/li&gt;
&lt;li&gt;freedom to experiment&lt;/li&gt;
&lt;li&gt;the chance to try new ideas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even small projects can expand your thinking as a developer.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Not Realizing How Long the Journey Is
&lt;/h2&gt;

&lt;p&gt;In the beginning, I thought becoming a strong developer would happen quickly.&lt;/p&gt;

&lt;p&gt;But this field is a long-term journey.&lt;/p&gt;

&lt;p&gt;There is always more to learn.&lt;/p&gt;

&lt;p&gt;New tools.&lt;br&gt;&lt;br&gt;
New patterns.&lt;br&gt;&lt;br&gt;
New ways to solve problems.&lt;/p&gt;

&lt;p&gt;And that's actually one of the best parts of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Mistakes are not the opposite of progress.&lt;/p&gt;

&lt;p&gt;They are part of it.&lt;/p&gt;

&lt;p&gt;Every developer has a list of things they would do differently today.&lt;/p&gt;

&lt;p&gt;The important thing is to keep learning from those experiences and keep moving forward.&lt;/p&gt;

&lt;p&gt;Because in software development, improvement is continuous.&lt;/p&gt;

&lt;p&gt;And that's what makes the journey interesting.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Why I’m Choosing Simplicity for My Next GitHub Projects</title>
      <dc:creator>Renato Silva</dc:creator>
      <pubDate>Sun, 15 Mar 2026 09:10:12 +0000</pubDate>
      <link>https://dev.to/renato_silva_71eef0fc385f/why-im-choosing-simplicity-for-my-next-github-projects-je8</link>
      <guid>https://dev.to/renato_silva_71eef0fc385f/why-im-choosing-simplicity-for-my-next-github-projects-je8</guid>
      <description>&lt;p&gt;I’m starting a series of practical projects to demonstrate that clean, simple architecture is the best way to build software. Here is the plan.&lt;/p&gt;




&lt;p&gt;In my last post, I talked about why &lt;strong&gt;Simple Architecture&lt;/strong&gt; is a superpower. &lt;/p&gt;

&lt;p&gt;But talk is cheap. Code is what matters.&lt;/p&gt;

&lt;p&gt;To prove that you don't need overengineered systems to build high-quality software, I’m starting a series of small, real-world projects that I’ll be sharing on GitHub. &lt;/p&gt;

&lt;p&gt;My goal is twofold: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To show that a clean, well-tested "boring" stack is often better than a complex trendy one.&lt;/li&gt;
&lt;li&gt;To help other developers see how to structure projects that are easy to maintain and scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the "Mental Framework" I’m using to choose my tools and why this matters for your career too.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The "Boring Technology" Rule
&lt;/h2&gt;

&lt;p&gt;For these projects, I’m avoiding the "flavor of the week" frameworks. &lt;/p&gt;

&lt;p&gt;I’m choosing technologies that are stable, have great documentation, and a strong community. Whether it's Node.js, Python, or Go, the focus isn't on the language itself, but on how I solve problems with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Because companies don't want a developer who experiments with their production code. They want someone who delivers reliable solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Focus on "Core Logic" first
&lt;/h2&gt;

&lt;p&gt;In the upcoming projects, you will notice that the business logic is separated from the framework. &lt;/p&gt;

&lt;p&gt;If I’m building a &lt;strong&gt;CLI Tool&lt;/strong&gt; (my first project!), the logic that calculates a task shouldn't care if it's running in a terminal or a web server. This is the heart of simple architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. README as a First-Class Citizen
&lt;/h2&gt;

&lt;p&gt;A project without a good README is like a book without a cover. &lt;/p&gt;

&lt;p&gt;For every repo I share, I’m focusing on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The "Why":&lt;/strong&gt; What problem does this solve?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "How":&lt;/strong&gt; Clear installation and usage instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Tests":&lt;/strong&gt; Showing that the code works as expected.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How AI is Helping Me Prototype Faster
&lt;/h2&gt;

&lt;p&gt;I'm using AI to speed up the "setup" phase of these projects. Here’s how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Boilerplate Generation":&lt;/strong&gt; Instead of spending an hour setting up a folder structure, I ask AI to generate a clean, modular structure based on my architectural rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Documentation Drafts":&lt;/strong&gt; I feed my code to the AI and ask it to write the first draft of the README. I then refine it to ensure it has my "voice" and technical accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI allows me to focus on the &lt;strong&gt;decisions&lt;/strong&gt;, while it handles the &lt;strong&gt;repetition&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Coming Next?
&lt;/h2&gt;

&lt;p&gt;The first project will be a &lt;strong&gt;CLI Tool&lt;/strong&gt; designed to solve a common developer pain point. It will be small, fast, and 100% open-source.&lt;/p&gt;

&lt;p&gt;I want to show you that a professional repository isn't about the number of files, but the quality of the decisions inside them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do you value most when you look at someone's GitHub? The complexity of the code or the clarity of the documentation? Let's talk below!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>opensource</category>
      <category>architecture</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
