<?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: Peace Melodi</title>
    <description>The latest articles on DEV Community by Peace Melodi (@peacemelodi).</description>
    <link>https://dev.to/peacemelodi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1143603%2Fb49d7887-db17-4016-8ab1-6a546cdf938b.jpeg</url>
      <title>DEV Community: Peace Melodi</title>
      <link>https://dev.to/peacemelodi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peacemelodi"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Tue, 11 Aug 2026 00:33:54 +0000</pubDate>
      <link>https://dev.to/peacemelodi/-50hl</link>
      <guid>https://dev.to/peacemelodi/-50hl</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/peacemelodi/what-really-happens-between-a-request-and-a-response-in-nestjs-nfj" class="crayons-story__hidden-navigation-link"&gt;What Really Happens Between a Request and a Response in NestJS&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/peacemelodi" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1143603%2Fb49d7887-db17-4016-8ab1-6a546cdf938b.jpeg" alt="peacemelodi profile" class="crayons-avatar__image" width="608" height="1080"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/peacemelodi" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Peace Melodi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Peace Melodi
                
              
              &lt;div id="story-author-preview-content-4364146" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/peacemelodi" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1143603%2Fb49d7887-db17-4016-8ab1-6a546cdf938b.jpeg" class="crayons-avatar__image" alt="" width="608" height="1080"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Peace Melodi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/peacemelodi/what-really-happens-between-a-request-and-a-response-in-nestjs-nfj" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 11&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/peacemelodi/what-really-happens-between-a-request-and-a-response-in-nestjs-nfj" id="article-link-4364146"&gt;
          What Really Happens Between a Request and a Response in NestJS
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nestjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nestjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/node"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;node&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/peacemelodi/what-really-happens-between-a-request-and-a-response-in-nestjs-nfj" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/peacemelodi/what-really-happens-between-a-request-and-a-response-in-nestjs-nfj#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Tue, 11 Aug 2026 00:05:12 +0000</pubDate>
      <link>https://dev.to/peacemelodi/-2nih</link>
      <guid>https://dev.to/peacemelodi/-2nih</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/peacemelodi/why-i-chose-nestjs-and-never-looked-back-1mon" class="crayons-story__hidden-navigation-link"&gt;Why I Chose NestJS and Never Looked Back&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/peacemelodi" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1143603%2Fb49d7887-db17-4016-8ab1-6a546cdf938b.jpeg" alt="peacemelodi profile" class="crayons-avatar__image" width="608" height="1080"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/peacemelodi" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Peace Melodi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Peace Melodi
                
              
              &lt;div id="story-author-preview-content-4363504" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/peacemelodi" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1143603%2Fb49d7887-db17-4016-8ab1-6a546cdf938b.jpeg" class="crayons-avatar__image" alt="" width="608" height="1080"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Peace Melodi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/peacemelodi/why-i-chose-nestjs-and-never-looked-back-1mon" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 10&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/peacemelodi/why-i-chose-nestjs-and-never-looked-back-1mon" id="article-link-4363504"&gt;
          Why I Chose NestJS and Never Looked Back
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nestjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nestjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/node"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;node&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/peacemelodi/why-i-chose-nestjs-and-never-looked-back-1mon" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/peacemelodi/why-i-chose-nestjs-and-never-looked-back-1mon#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>What Really Happens Between a Request and a Response in NestJS</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Tue, 11 Aug 2026 00:02:31 +0000</pubDate>
      <link>https://dev.to/peacemelodi/what-really-happens-between-a-request-and-a-response-in-nestjs-nfj</link>
      <guid>https://dev.to/peacemelodi/what-really-happens-between-a-request-and-a-response-in-nestjs-nfj</guid>
      <description>&lt;p&gt;Have you ever wondered what actually happens in the few seconds between clicking a button on a website and seeing something appear on your screen? It feels instant, almost like magic. But there is a real journey happening behind that moment, and once you understand it, a lot of what feels confusing about NestJS starts to make sense.&lt;/p&gt;

&lt;p&gt;Think about how a letter travels through the mail. It gets picked up, sorted, checked to make sure it is addressed correctly, handled by the right person, and eventually delivered, with a reply sometimes making its way back to you. Every step exists for a reason, even though you never see most of it happen.&lt;/p&gt;

&lt;p&gt;Something similar happens every time you ask an app to do something, even something as small as clicking a button to log in. That click is really you asking the app a question, and what appears afterward is really its answer coming back to you. NestJS is the system responsible for guiding that journey properly, from your ask all the way to the answer.&lt;/p&gt;

&lt;p&gt;Your ask first reaches a part of the app whose only job is to receive it and send it to the right place, like a mail carrier delivering a letter to the correct department. From there, it is often checked to make sure it is allowed to proceed, much like a letter being confirmed safe before it goes further inside. Once it clears that check, it reaches the part that does the real work, quietly preparing an answer behind the scenes. And finally, that answer makes its way back out to you, showing up simply as something appearing on your screen.&lt;/p&gt;

&lt;p&gt;In case you are curious what developers call these two moments, your ask is called a request, and the answer coming back is called a response. That is really all those two words mean, your question going in, and the answer coming back out.&lt;/p&gt;

&lt;p&gt;Here is the lesson worth taking from this, even outside of code. Anything that feels instant on the surface usually has quiet, careful steps happening underneath it, steps that exist to keep things organized and safe, not to slow things down. The more clearly each step knows its one job, the smoother the whole journey becomes.&lt;/p&gt;

&lt;p&gt;You do not need to memorize any technical terms today. You only need to know that every ask you send an app goes on a real journey before an answer comes back. Next time something loads instantly, you can smile a little, knowing there was a quiet, organized journey happening behind that speed.&lt;/p&gt;

&lt;p&gt;I write these thoughts as Peace Melodi, a backend software engineer who cares deeply about building things that hold up under real pressure, real users, and real growth. If any of this resonated with you, I would love to connect.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>You Can Actually Understand NestJS, Here Is Where to Start</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Mon, 10 Aug 2026 22:56:51 +0000</pubDate>
      <link>https://dev.to/peacemelodi/you-can-actually-understand-nestjs-here-is-where-to-start-lfo</link>
      <guid>https://dev.to/peacemelodi/you-can-actually-understand-nestjs-here-is-where-to-start-lfo</guid>
      <description>&lt;p&gt;If you have ever opened a NestJS project for the first time and felt a small wave of panic looking at all the folders and unfamiliar words on your screen, that feeling does not mean you are behind. It just means you are looking at order you do not understand yet, and that fades faster than you think.&lt;/p&gt;

&lt;p&gt;Here is the one idea to hold onto before anything else. NestJS exists because growing projects naturally turn messy if nothing holds them together, so it gives every piece of your app a clear, dedicated place to live instead of letting everything pile up in one tangled mess.&lt;/p&gt;

&lt;p&gt;Think of a NestJS project like a well organized house. A module is like a room in that house, a kitchen, a bedroom, a living room, each one holding the things that belong together instead of everything being scattered across the floor. A controller is like the front door, it is the part that answers when someone knocks, listens to what they want, and decides where inside the house they need to go. A service is like the kitchen, it is where the real work actually happens, quietly, behind the scenes, after the front door has let the request in. And a guard is like the lock on the door, it checks whether the person knocking is even allowed inside before anything else is allowed to happen.&lt;/p&gt;

&lt;p&gt;You do not need to memorize any of this today. You just need to know these four pieces exist, and that each one has its own clear job instead of everything doing everything.&lt;/p&gt;

&lt;p&gt;So here is where I actually want you to start. The next time you open a NestJS project, do not try to understand everything at once. Just look for these four things, a room that groups things together, a door that receives requests, a kitchen where the real work happens, and a lock that protects access. That is it. That is enough for today.&lt;/p&gt;

&lt;p&gt;You do not need to feel ready to master NestJS right now. You only need to start noticing the order that is already there, and trust that understanding follows people who pay attention, not people who rush.&lt;/p&gt;

&lt;p&gt;I write these thoughts as Peace Melodi, a backend software engineer who cares deeply about building things that hold up under real pressure, real users, and real growth. If any of this resonated with you, I would love to connect.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Why I Chose NestJS and Never Looked Back</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Mon, 10 Aug 2026 21:19:04 +0000</pubDate>
      <link>https://dev.to/peacemelodi/why-i-chose-nestjs-and-never-looked-back-1mon</link>
      <guid>https://dev.to/peacemelodi/why-i-chose-nestjs-and-never-looked-back-1mon</guid>
      <description>&lt;p&gt;A few years ago, I was just another developer trying to figure out how to build things that actually work, not just things that run once and fall apart the moment real users touch them. I tried a few paths. I read a lot. I broke a lot of things. And somewhere along that road, I found NestJS.&lt;/p&gt;

&lt;p&gt;At first, it looked like just another tool. Another framework to learn, another thing to add to my resume. But the more I used it, the more I realized something. NestJS wasn't just teaching me how to build backend systems. It was teaching me how to think like someone who builds things meant to last.&lt;/p&gt;

&lt;p&gt;I did not choose NestJS because it was trendy. I chose it because it made me feel organized in a way nothing else had. It gave structure to ideas that used to feel messy in my head. It made me feel like a professional, not just someone typing code and hoping it works.&lt;/p&gt;

&lt;p&gt;Here is the lesson I want you to take from this, even if you never write a single line of NestJS code. Anything you build, whether it is software, a business, or even your own life, lasts longer when it has structure. Not rules for the sake of rules, but structure that makes room for growth without everything falling apart. That is what NestJS taught me first, before it taught me anything technical. Organize your thinking, and the work becomes easier to carry.&lt;/p&gt;

&lt;p&gt;Today, when people ask me why I still use NestJS after all this time, my answer is simple. It is not just a tool I use. It is the reason I became confident in what I do. And once you experience that kind of confidence in your work, it is very hard to walk away from it.&lt;/p&gt;

&lt;p&gt;I write these thoughts as Peace Melodi, a backend software engineer who cares deeply about building things that hold up under real pressure, real users, and real growth. If any of this resonated with you, I would love to connect.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>node</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Created a NestJS Delivery Dispatch Backend with Concurrency Control &amp; Dockerized Postgres</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Sat, 08 Aug 2026 13:51:53 +0000</pubDate>
      <link>https://dev.to/peacemelodi/i-created-a-nestjs-delivery-dispatch-backend-with-concurrency-control-dockerized-postgres-dec</link>
      <guid>https://dev.to/peacemelodi/i-created-a-nestjs-delivery-dispatch-backend-with-concurrency-control-dockerized-postgres-dec</guid>
      <description>&lt;p&gt;I built this delivery dispatch service to practice and master core backend engineering fundamentals in NestJS. Instead of just building a basic CRUD API, I wanted to understand how real-world dispatch systems manage state transitions, handle route assignments, and keep data consistent under concurrent requests.&lt;/p&gt;

&lt;p&gt;The backend is engineered with NestJS, TypeORM, and PostgreSQL running inside Docker containers. To handle state protection, I implemented concurrency guards that intercept duplicate route acceptance requests and immediately throw an HTTP 409 Conflict exception before any invalid state mutation reaches the database.&lt;/p&gt;

&lt;p&gt;To streamline my testing workflow without manually dropping database schemas, I also built a Node.js automation script (scripts/clear-db.js) that pipes dynamic SQL queries straight into the Docker container via IPC streams.&lt;/p&gt;

&lt;p&gt;In the video, I walk through the route acceptance logic, demonstrate how the HTTP 409 conflict guard blocks duplicate claims, and run a live demo of the automated database reset in action.&lt;/p&gt;

&lt;p&gt;Check out the video below to watch the full walkthrough in action. If you enjoy the demo, please give the video a like and leave a comment with your thoughts!&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>node</category>
      <category>docker</category>
      <category>postgres</category>
    </item>
    <item>
      <title>How a Double Entry Ledger Works, Built From Scratch in NestJS</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Fri, 07 Aug 2026 17:06:20 +0000</pubDate>
      <link>https://dev.to/peacemelodi/how-a-double-entry-ledger-works-built-from-scratch-in-nestjs-443n</link>
      <guid>https://dev.to/peacemelodi/how-a-double-entry-ledger-works-built-from-scratch-in-nestjs-443n</guid>
      <description>&lt;p&gt;I built Parity Ledger to solve a problem most backend systems ignore. Most systems track money with a single balance column, a number sits on an account record, it goes up when money comes in, it goes down when money goes out. That works fine until two requests hit the same account at the same time, or until someone needs to know what actually happened to an account months later and there is nothing but today's number staring back.&lt;/p&gt;

&lt;p&gt;Parity Ledger is a double entry bookkeeping engine built with NestJS and Postgres. Instead of storing a balance, every movement of money is written as a permanent, immutable entry. Nothing gets updated, nothing gets deleted. Every transaction writes two entries at once, a debit and a credit, inside a single atomic database transaction, so money can never leave one account without landing in another.&lt;/p&gt;

&lt;p&gt;To keep things correct under real concurrent pressure, the ledger uses row level locking at the exact point where two requests could touch the same account balance at once. I built a live dashboard to actually demonstrate this, firing two transfer requests at the same account at the same moment and watching the lock force one to wait its turn.&lt;/p&gt;

&lt;p&gt;In the video I walk through how it all works, the reasoning behind ditching the balance column, how the debit/credit pairing is enforced, and a live demo of the concurrency lock in action.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>security</category>
      <category>backend</category>
      <category>typescript</category>
    </item>
    <item>
      <title>NestJS and the Discipline It Takes to Never Let a Bank's Numbers Lie to You</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:28:07 +0000</pubDate>
      <link>https://dev.to/peacemelodi/nestjs-and-the-discipline-it-takes-to-never-let-a-banks-numbers-lie-to-you-5oc</link>
      <guid>https://dev.to/peacemelodi/nestjs-and-the-discipline-it-takes-to-never-let-a-banks-numbers-lie-to-you-5oc</guid>
      <description>&lt;p&gt;A number that represents money is only trustworthy if it can never quietly change without leaving a trace. That sounds obvious once it is said out loud, yet it is one of the easiest rules to break by accident. A support agent fixes a customer's balance directly in the database after a complaint. A backend job updates an account total to correct a bug. A well meaning developer runs a one off script to patch a number that looked wrong. Every one of these actions feels reasonable in the moment, and every one of them destroys the one thing a financial system actually depends on, the ability to prove, later, exactly how a number became what it is.&lt;/p&gt;

&lt;p&gt;This is the real discipline behind a trustworthy financial backend, and it has very little to do with clever code and everything to do with refusing to ever let a balance be edited directly. It is also one of the least glamorous parts of building financial software, which is exactly why it gets skipped so often. Nobody sets out to build a system that lies about money. It happens gradually, one convenient shortcut at a time, until the system has quietly drifted into a place where nobody can fully explain how a number got to be what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a stored balance cannot be the source of truth
&lt;/h2&gt;

&lt;p&gt;Most systems that are not built with this discipline store an account balance as a single number and update it in place whenever money moves. The moment that number can be changed directly, by a script, an admin panel, or a rushed bug fix, it stops being a fact and becomes an opinion, something that reflects whatever the last write happened to be, with no way to reconstruct how it got there. If a customer disputes their balance six months from now, or a regulator asks how a number was reached, a system built this way has no real answer beyond trusting that everything along the way was done correctly.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical concern. Financial disputes are common, regulators do ask these questions, and the answer a bank gives in that moment either comes from a system that can reconstruct its own history with certainty, or from a system that is essentially asking everyone to take its word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treating every change as a new fact, never an edit
&lt;/h2&gt;

&lt;p&gt;The discipline that fixes this is refusing to ever update a balance directly. Instead, every single change to an account is recorded as its own permanent entry, and the balance itself is only ever calculated by adding up every entry that has ever happened. Nothing is ever overwritten. A correction is not an edit to history, it is a new entry that says exactly what was corrected and why, sitting right alongside everything that came before it.&lt;/p&gt;

&lt;p&gt;In NestJS, this discipline lives naturally inside the service layer, where a balance is never something you fetch and mutate, it is something you compute fresh, every time, from the full history of entries tied to that account. The service becomes the one place this rule is actually enforced, rather than something every developer has to remember on their own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getAccountBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;result&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;dataSource&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUM(CASE WHEN entry.type = 'CREDIT' THEN entry.amount ELSE -entry.amount END)&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;balance&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="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LedgerEntry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entry&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entry.accountId = :accountId&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;accountId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRawOne&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;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0000&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;There is no balance column being read here, and nothing being written to correct one either. The number is simply the honest sum of everything that has ever happened to that account, which means it cannot drift away from reality without every single entry behind it also being wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when a mistake genuinely needs fixing
&lt;/h2&gt;

&lt;p&gt;This discipline does not mean mistakes never get corrected, they absolutely do. What changes is how a correction is made. Instead of reaching into the database and changing an existing entry, a correction is made by adding a brand new entry that reverses or adjusts the original one, clearly labeled as a correction, tied back to whatever it is fixing. If a customer was charged the wrong amount, the fix is not deleting the wrong entry and quietly typing in the right number. The fix is leaving the original entry exactly as it happened, and adding a second entry beside it that corrects the balance going forward while preserving an honest record of what actually occurred, including the mistake itself.&lt;/p&gt;

&lt;p&gt;This might feel slower or more cautious than simply editing a number, and it is, deliberately so. The extra step is what makes the difference between a system that can fully explain itself later and one that can only ever hope nobody asks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it structurally hard to cheat
&lt;/h2&gt;

&lt;p&gt;The other half of this discipline is making sure nothing in the system can bypass it, even under pressure. That means the database itself should never allow a ledger entry to be updated or deleted once it has been created, only ever inserted. Combined with the fact that the balance is always computed rather than stored, there is simply no direct path left for a rushed fix or a one off script to quietly rewrite history, because there is no editable number sitting anywhere to rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual value of this discipline
&lt;/h2&gt;

&lt;p&gt;None of this is about distrusting the people who work on the system. It is about removing the need to trust anyone's good intentions in the first place. A bank does not want to be in a position where the honesty of its numbers depends on nobody ever taking a shortcut under deadline pressure. Building the discipline directly into how the backend is structured, immutable entries, computed balances, corrections instead of edits, means the numbers stay honest by default, not because everyone remembered to be careful, but because the system never gave anyone the option not to be.&lt;/p&gt;

&lt;p&gt;If you are working on a system where the numbers need to hold up under real scrutiny, not just in a demo, I would be glad to talk through how to structure it this way from the start.&lt;/p&gt;

&lt;p&gt;I am Peace Melodi, a backend software engineer. If you want your business to scale big, comfortably handling millions of users without breaking, with strong scalability and security in place, feel free to reach out.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>api</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>What a Bank's Payment Flow Actually Needs Underneath, and Why NestJS Was Built for Exactly That</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Fri, 24 Jul 2026 10:17:16 +0000</pubDate>
      <link>https://dev.to/peacemelodi/what-a-banks-payment-flow-actually-needs-underneath-and-why-nestjs-was-built-for-exactly-that-4a2</link>
      <guid>https://dev.to/peacemelodi/what-a-banks-payment-flow-actually-needs-underneath-and-why-nestjs-was-built-for-exactly-that-4a2</guid>
      <description>&lt;p&gt;Moving money between two accounts sounds like a small task. Take an amount from one place, add it to another, done. In reality, a payment flow that actually holds up in production has to get several things right at the same time, every single time, without exception. It has to check that the numbers are actually valid before touching anything. It has to make sure that if any part of the operation fails partway through, nothing is left in a broken, half finished state. And it has to survive the same request arriving more than once, since networks retry, users double click, and providers resend, whether anyone plans for it or not.&lt;/p&gt;

&lt;p&gt;None of these requirements are unique to any one bank or any one payment provider. They are the same three requirements every payment system on earth has to satisfy, and they are exactly the kind of structure NestJS was designed around from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the validation right before anything is trusted
&lt;/h2&gt;

&lt;p&gt;A payment flow cannot afford to trust the shape of incoming data. An amount that is missing, negative, or malformed has to be rejected before it ever reaches a service or touches a database. NestJS handles this through DTOs paired with a global validation pipe, so a request is checked and shaped correctly before a single line of business logic even runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransferDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;fromAccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;toAccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsNumberString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small piece of code, but the value is not in the code itself, it is in where it sits. Validation happens at the edge of the system, automatically, on every single request that reaches this route, rather than depending on a developer remembering to check it manually somewhere deep inside a service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sure nothing is ever left half done
&lt;/h2&gt;

&lt;p&gt;The second requirement is atomicity. If a transfer touches two accounts, and something fails after the first account has already been updated, the second account cannot be left waiting forever in an inconsistent state. NestJS, through TypeORM's query runner, gives you a clean way to wrap the entire operation so it either fully succeeds or fully rolls back, with nothing in between.&lt;/p&gt;

&lt;p&gt;The pattern is straightforward once it is in place, start a transaction, perform every write that belongs to that one operation, and either commit everything together or roll everything back together the moment anything goes wrong. What matters is that this pattern lives in one place, structured the same way every time a transfer happens, instead of being reinvented, or forgotten, in every new feature that touches money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Surviving a request that arrives more than once
&lt;/h2&gt;

&lt;p&gt;The third requirement is the one most systems get wrong first. A slow network, a retried request, a user clicking twice, all of these can cause the exact same payment instruction to arrive at the backend more than once. Without protection, that means the same transfer gets executed twice. NestJS gives you a natural place to check for this, a guard or an interceptor sitting in front of the actual transfer logic, checking an idempotency key against previous requests before anything is allowed to proceed a second time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IdempotencyGuard&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CanActivate&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;idempotencyService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IdempotencyService&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;canActivate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switchToHttp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getRequest&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;key&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idempotency-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&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="nx"&gt;idempotencyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wasAlreadyProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this sits in front of a route, a duplicate request simply stops before it can do any damage, without the rest of the payment logic needing to know or care that it almost ran twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why NestJS fits this so naturally
&lt;/h2&gt;

&lt;p&gt;None of these three requirements are ideas NestJS invented. Validation, atomic operations, and duplicate protection are basic requirements of any serious payment system, in any language, on any framework. What NestJS actually offers is a clear, consistent place to put each one, a pipe for validation, a guard for duplicate protection, a service layer for the atomic transaction itself, so a team of developers all end up enforcing the same discipline the same way, instead of every new payment feature reinventing these protections from memory and inevitably forgetting one of them under a deadline.&lt;/p&gt;

&lt;p&gt;That consistency is the real reason NestJS keeps showing up underneath serious financial backends. Not because it is the only framework capable of handling money safely, but because its structure makes the safe way also the easy way to build it.&lt;/p&gt;

&lt;p&gt;If your team is building or reworking a payment flow and wants that discipline built in from the start rather than patched in after something goes wrong, I would be glad to talk through it.&lt;/p&gt;

&lt;p&gt;I am Peace Melodi, a backend software engineer. If you want your business to scale big, comfortably handling millions of users without breaking, with strong scalability and security in place, feel free to reach out.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>webdev</category>
      <category>api</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Quiet Risk in How Most Banks Store Customer Documents, and How NestJS Handles It Properly</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Thu, 23 Jul 2026 22:08:50 +0000</pubDate>
      <link>https://dev.to/peacemelodi/the-quiet-risk-in-how-most-banks-store-customer-documents-and-how-nestjs-handles-it-properly-oop</link>
      <guid>https://dev.to/peacemelodi/the-quiet-risk-in-how-most-banks-store-customer-documents-and-how-nestjs-handles-it-properly-oop</guid>
      <description>&lt;p&gt;A compliance officer at a bank was preparing for an upcoming audit when she asked a question nobody on the engineering team had a clean answer to. Every customer who opened an account had uploaded a government ID and a proof of address. Where exactly were those files sitting, who could actually open them, and could the bank prove that. The honest answer was uncomfortable. The files were sitting in a storage bucket, reachable by a plain public link, generated once at upload time and never expiring. Anyone who ever got hold of one of those links, through a shared screenshot, a browser history, a support ticket, could open a customer's ID months or years later. Nothing had leaked yet. But nobody could say with any confidence that it never would.&lt;/p&gt;

&lt;p&gt;This is not a rare mistake. It is what happens by default when file upload is treated as a simple feature instead of something that needs the same discipline as handling money itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this risk hides so well
&lt;/h2&gt;

&lt;p&gt;Uploading a document and storing a link to it works, in the sense that the feature functions and nobody notices a problem in testing. The risk is invisible until the exact moment it is not, a leaked link, a support agent who should never have seen a customer's ID, a storage bucket accidentally left open to the public. By the time it is noticed, the damage is already done, and for a bank, a document like a government ID or a bank statement is not something you get to quietly walk back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Never storing documents with a permanent public link
&lt;/h2&gt;

&lt;p&gt;The first fix is making sure a document is never reachable through a link that lasts forever. Instead of a public URL, the file should sit in private storage, and access should only be granted through a short lived, signed link generated on demand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetObjectCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getSignedUrl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/s3-request-presigner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentAccessService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;s3Client&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;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&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="nf"&gt;generateAccessUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;command&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;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&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;getSignedUrl&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="nx"&gt;s3Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A link like this stops working after a few minutes. If it ever leaks, whoever finds it has already missed the window to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcing who is allowed to even ask for a document
&lt;/h2&gt;

&lt;p&gt;Generating a short lived link is only half the fix. The endpoint that generates it has to strictly confirm the requester is actually allowed to see that specific document, not just that they are logged in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CanActivate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ForbiddenException&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentOwnershipGuard&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CanActivate&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;documentsService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DocumentsService&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;canActivate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switchToHttp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getRequest&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;requestingUserId&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;user&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;documentId&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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&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;documentsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentId&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="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ownerId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;requestingUserId&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;ForbiddenException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You do not have access to this document&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guard sits in front of the route, so nobody, including a support agent using their own account, can pull up a document that does not belong to the customer they are actually assisting, unless that access is explicitly modeled and permitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording every time a document is accessed
&lt;/h2&gt;

&lt;p&gt;The compliance officer's real question was whether the bank could prove who accessed a document and when. That means every single access needs to be logged, not just allowed or denied.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PrimaryGeneratedColumn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CreateDateColumn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typeorm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document_access_logs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentAccessLog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;PrimaryGeneratedColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;accessedByUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;CreateDateColumn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;accessedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&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;With this in place, an audit question like who looked at this customer's ID and why becomes something the bank can answer with a query, not a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;NestJS does not know anything about compliance requirements on its own. What it gives you is a place to put each layer of this properly, a service that only ever issues short lived signed access instead of permanent links, a guard that checks real ownership before anything is generated, and a log that records every access without exception. None of these pieces are complicated individually. What matters is that the structure makes it hard to skip any of them under deadline pressure, since that is usually how a customer's sensitive documents end up sitting behind a link that never expires in the first place.&lt;/p&gt;

&lt;p&gt;If your team is storing anything sensitive, documents, IDs, statements, and you are not fully sure who could actually access them today, I would be glad to talk through how to tighten that up.&lt;/p&gt;

&lt;p&gt;I am Peace Melodi, a backend software engineer. If you want your business to scale big, comfortably handling millions of users without breaking, with strong scalability and security in place, feel free to reach out.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>security</category>
      <category>fintech</category>
      <category>backend</category>
    </item>
    <item>
      <title>A Bank Wanted to Use AI to Approve Loans Faster, Here Is What I Would Build Around It in NestJS</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Thu, 23 Jul 2026 21:44:33 +0000</pubDate>
      <link>https://dev.to/peacemelodi/a-bank-wanted-to-use-ai-to-approve-loans-faster-here-is-what-i-would-build-around-it-in-nestjs-5fb</link>
      <guid>https://dev.to/peacemelodi/a-bank-wanted-to-use-ai-to-approve-loans-faster-here-is-what-i-would-build-around-it-in-nestjs-5fb</guid>
      <description>&lt;p&gt;A loan officer at a small digital bank was excited about the new model the data team had built. It could score a loan application in under a second, based on income, spending patterns, and repayment history, and approve or reject it automatically. The first week it ran, it worked beautifully. The second week, the model provider had an outage in the middle of the afternoon. Every loan application submitted during that window just hung, waiting on a response that was never coming. Some customers refreshed the page and submitted twice. A few applications that should have been rejected outright ended up approved, because a fallback path someone had written months earlier, and never tested properly, defaulted to approving anything it could not score.&lt;/p&gt;

&lt;p&gt;Nobody on the data team had done anything wrong with the model itself. The model was accurate. What was missing was everything around it, the part that decides what happens when the model is slow, wrong, unavailable, or simply not something you should trust blindly with a decision this big.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem is never the model
&lt;/h2&gt;

&lt;p&gt;A bank does not really have an AI problem when it adopts a model for something like loan approval or fraud scoring. It has an integration problem. The model is a service you call, and like any service, it can be slow, it can fail, and it can be confidently wrong. The question that matters is not whether the model is smart. It is what the backend does the moment the model does not behave the way everyone assumed it would.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcing a timeout so a slow model cannot freeze the whole flow
&lt;/h2&gt;

&lt;p&gt;The first guardrail is making sure a call to the model can never hang indefinitely. If the model provider is slow or down, the request should fail fast and fall back to a safe default, not leave the customer staring at a loading screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;firstValueFrom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;catchError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoanScoringService&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;httpService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpService&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;scoreApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;firstValueFrom&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="nx"&gt;httpService&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;https://ai-provider.example.com/score&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;catchError&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Scoring service unavailable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;HttpStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SERVICE_UNAVAILABLE&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="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;Notice that the fallback here is an explicit error, not a silent approval. Whatever happens next, defaulting to approving a loan because a service call failed is never an acceptable behavior on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deciding what fail safe actually means for a loan decision
&lt;/h2&gt;

&lt;p&gt;Once a timeout or failure is caught, something has to happen next, and for a decision this significant, that something should never be an automatic approval. A sensible fail safe path is to route the application to a manual review queue instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handleScoringFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;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;reviewQueueService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scoring_service_unavailable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requiresManualReview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending_review&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="s1"&gt;Your application is being reviewed manually.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the customer informed honestly, and it makes sure a technical failure never quietly turns into a financial decision nobody actually made on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging every decision so it can be explained later
&lt;/h2&gt;

&lt;p&gt;A bank will eventually need to explain why a specific customer was approved, rejected, or flagged, sometimes months later, sometimes to a regulator. That means every scoring decision, along with the model's output and the data it was based on, needs to be recorded, not just acted on and discarded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PrimaryGeneratedColumn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CreateDateColumn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typeorm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loan_decisions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoanDecision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;PrimaryGeneratedColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;applicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;modelInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;modelOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;finalDecision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;requiredManualReview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;CreateDateColumn&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="nb"&gt;Date&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;With this in place, a decision made in under a second can still be fully reconstructed and explained later, which matters far more in banking than in almost any other kind of application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping a human in the loop for the decisions that matter most
&lt;/h2&gt;

&lt;p&gt;Not every decision needs a person reviewing it, but the ones with real consequences, a large loan amount, a borderline score, a customer disputing a rejection, should never be finalized by the model alone. NestJS gives you a clean place to enforce that gate, a check that routes certain outcomes to a review queue instead of directly to the customer, based on rules the bank actually agrees with, rather than whatever the model happened to output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;NestJS does not make an AI model more accurate, and it never should try to. What it gives you is the structure around the model, a place to enforce timeouts, a place to decide what fail safe actually means, a place to log every decision so it can be explained later, and a place to insist a person reviews the decisions that deserve one. That structure is what actually makes an AI model safe enough to use for something as consequential as approving a loan.&lt;/p&gt;

&lt;p&gt;If your team is bringing AI into a process that touches real money or real customers, I would be glad to talk through how to build the guardrails around it properly.&lt;/p&gt;

&lt;p&gt;I am Peace Melodi, a backend software engineer. If you want your business to scale big, comfortably handling millions of users without breaking, with strong scalability and security in place, feel free to reach out.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>ai</category>
      <category>fintech</category>
      <category>backend</category>
    </item>
    <item>
      <title>The First Question I Would Ask a Bank Before Using NestJS to Make Sure Their Money Is Always Accounted For</title>
      <dc:creator>Peace Melodi</dc:creator>
      <pubDate>Thu, 23 Jul 2026 21:30:41 +0000</pubDate>
      <link>https://dev.to/peacemelodi/the-first-question-i-would-ask-a-bank-before-using-nestjs-to-make-sure-their-money-is-always-51fa</link>
      <guid>https://dev.to/peacemelodi/the-first-question-i-would-ask-a-bank-before-using-nestjs-to-make-sure-their-money-is-always-51fa</guid>
      <description>&lt;p&gt;A finance officer at a mid sized bank was closing out the month when the numbers refused to line up. Customer accounts held a few cents more, in total, than the bank's own settlement records said they should. She ran the reconciliation again, certain it was her mistake. Same result. She pulled in an engineer, who spent two days going line by line through the transaction logic looking for the missing bug. There was no missing transaction. There was no attacker. There was no single line of broken code anyone could point to. The gap had been forming quietly for months, a fraction of a cent at a time, every single time interest was calculated or a fee was split between accounts.&lt;/p&gt;

&lt;p&gt;Nobody had done anything wrong. The mistake had been made much earlier, before either of them had joined the company, in a decision about how money would be represented inside the system in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first question I would ask
&lt;/h2&gt;

&lt;p&gt;Before I would agree to use NestJS to build or touch any part of a bank's money handling, the first question I would ask is simple. How are amounts stored and calculated internally, as ordinary numbers, or as something built specifically to represent money exactly.&lt;/p&gt;

&lt;p&gt;It sounds like a small detail. It is the single decision that determines whether the numbers can ever quietly drift apart from reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why plain numbers cannot be trusted with money
&lt;/h2&gt;

&lt;p&gt;JavaScript, like most languages, represents regular numbers using floating point. Floating point is excellent for scientific calculations and graphics, where being off by a tiny fraction almost never matters. It is not designed to represent decimal currency amounts exactly. A value like zero point one, which looks perfectly ordinary to a person, cannot always be stored exactly in that format. Individually, the error is too small to notice. Add, subtract, split, and multiply that number millions of times across a real banking system, and those tiny errors accumulate into amounts that are large enough to matter, and impossible to trace back to a single obvious bug.&lt;/p&gt;

&lt;p&gt;This is exactly why the reconciliation report in the opening story slowly drifted. Nothing was ever technically wrong with the logic. The numbers themselves were never being represented with the precision the business rules assumed they had.&lt;/p&gt;

&lt;h2&gt;
  
  
  How NestJS keeps money precise
&lt;/h2&gt;

&lt;p&gt;This is where the actual engineering discipline comes in, and it has to be enforced at every layer, not just trusted in one place.&lt;/p&gt;

&lt;p&gt;The first layer is validation at the edge of the system, the DTO. Before an amount is even accepted, it should be validated as a properly formatted decimal string, not a loose number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Matches&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateTransactionDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(\.\d{1,4})?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;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;amount must be a valid decimal string with up to four decimal places&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;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;Accepting the amount as a string, rather than a number, means it arrives exactly as written, with no floating point conversion happening before validation even runs.&lt;/p&gt;

&lt;p&gt;The second layer is the database schema itself. The column that stores the amount should use a fixed precision decimal type, not a floating point column, so the database itself refuses to silently round or approximate the value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PrimaryGeneratedColumn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typeorm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ledger_entries&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LedgerEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;PrimaryGeneratedColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third layer is arithmetic itself. Any place in a NestJS service where amounts are added, subtracted, or compared should never rely on plain JavaScript math on those values directly. Instead, the calculation should happen through a library built for exact decimal arithmetic, so precision is preserved through every step, not only at storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Decimal&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decimal.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;amounts&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;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Decimal&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;Put together, these three layers mean an amount is validated as exact the moment it enters the system, stored as exact the moment it is saved, and calculated as exact every time it is touched. There is no point in the chain where a silent rounding error can slip in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;NestJS will not automatically protect a bank from this mistake. Nothing in the framework forces a developer to use decimal strings instead of plain numbers, or a fixed precision column instead of a floating point one. What NestJS gives you is a clean, enforceable place to put each of these decisions, a validation rule at the DTO layer, a column definition at the entity layer, and a consistent calculation approach at the service layer, so the whole team follows the same discipline instead of everyone reinventing it, or forgetting it, feature by feature.&lt;/p&gt;

&lt;p&gt;If your team is dealing with numbers that quietly do not add up, or you are building something from scratch and want the precision handled correctly from day one, I would be glad to talk through how to structure it.&lt;/p&gt;

&lt;p&gt;I am Peace Melodi, a backend software engineer. If you want your business to scale big, comfortably handling millions of users without breaking, with strong scalability and security in place, feel free to reach out.&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/melodi-peace-406494368" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/melodi-peace-406494368&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/PeaceMelodi" rel="noopener noreferrer"&gt;https://github.com/PeaceMelodi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>fintech</category>
      <category>typescript</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
