<?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: Harry Nongomin</title>
    <description>The latest articles on DEV Community by Harry Nongomin (@harry_nongomin_code).</description>
    <link>https://dev.to/harry_nongomin_code</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%2F4171926%2F956fbb8a-c857-4ebf-b473-34f6fe806ae9.jpg</url>
      <title>DEV Community: Harry Nongomin</title>
      <link>https://dev.to/harry_nongomin_code</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harry_nongomin_code"/>
    <language>en</language>
    <item>
      <title>Building an HR Management API with NestJS and Prisma: Lessons From Real-World Database Problems</title>
      <dc:creator>Harry Nongomin</dc:creator>
      <pubDate>Thu, 08 Oct 2026 18:44:22 +0000</pubDate>
      <link>https://dev.to/harry_nongomin_code/building-an-hr-management-api-with-nestjs-and-prisma-lessons-from-real-world-database-problems-2bo0</link>
      <guid>https://dev.to/harry_nongomin_code/building-an-hr-management-api-with-nestjs-and-prisma-lessons-from-real-world-database-problems-2bo0</guid>
      <description>&lt;p&gt;uilding a backend application is very different from simply following a tutorial.&lt;/p&gt;

&lt;p&gt;When you are building a real system, you eventually encounter problems that don't have a simple "copy this code" solution.&lt;/p&gt;

&lt;p&gt;Recently, I've been working on an HR management API using:&lt;/p&gt;

&lt;p&gt;NestJS&lt;br&gt;
TypeScript&lt;br&gt;
Prisma&lt;br&gt;
MySQL&lt;br&gt;
Swagger&lt;/p&gt;

&lt;p&gt;One of the modules I worked on was an HR query-management system.&lt;/p&gt;

&lt;p&gt;The system needs to allow authorised HR users to create queries, associate them with employees and organisations, record responses, resolve queries, and maintain an audit trail of important actions.&lt;/p&gt;

&lt;p&gt;What I initially expected to be straightforward quickly became an interesting lesson in database relationships, application architecture, and debugging.&lt;/p&gt;

&lt;p&gt;The architecture&lt;/p&gt;

&lt;p&gt;At a simplified level, the application looks like this:&lt;/p&gt;

&lt;p&gt;Client&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
Controller&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
Service&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
Prisma Client&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
MySQL Database&lt;/p&gt;

&lt;p&gt;Each layer has a responsibility.&lt;/p&gt;

&lt;p&gt;The controller handles incoming HTTP requests.&lt;/p&gt;

&lt;p&gt;The service contains the business logic.&lt;/p&gt;

&lt;p&gt;Prisma provides the interface between the application and the database.&lt;/p&gt;

&lt;p&gt;MySQL stores the actual data.&lt;/p&gt;

&lt;p&gt;This separation is useful, but it also means that a problem in one layer can affect another.&lt;/p&gt;

&lt;p&gt;The first important lesson: database relationships matter&lt;/p&gt;

&lt;p&gt;One of the problems I encountered involved creating a record that had several relationships.&lt;/p&gt;

&lt;p&gt;For example, a query could be associated with:&lt;/p&gt;

&lt;p&gt;an organisation&lt;br&gt;
an employee&lt;br&gt;
an infraction type&lt;br&gt;
the user who issued the query&lt;/p&gt;

&lt;p&gt;It is tempting to think:&lt;/p&gt;

&lt;p&gt;"I have the IDs, so I'll just pass the IDs when creating the record."&lt;/p&gt;

&lt;p&gt;But Prisma's generated types may expect a relationship to be represented through a nested relation depending on how the schema is defined.&lt;/p&gt;

&lt;p&gt;Conceptually, you might have something like:&lt;/p&gt;

&lt;p&gt;model QueryRecord {&lt;br&gt;
  id              BigInt &lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt; &lt;a class="mentioned-user" href="https://dev.to/default"&gt;@default&lt;/a&gt;(autoincrement())&lt;br&gt;
  clientId        String&lt;/p&gt;

&lt;p&gt;organisation    Organisation @relation(&lt;br&gt;
    fields: [clientId],&lt;br&gt;
    references: [clientId]&lt;br&gt;
  )&lt;/p&gt;

&lt;p&gt;employeeRecordId BigInt&lt;br&gt;
  employee         Employee @relation(&lt;br&gt;
    fields: [employeeRecordId],&lt;br&gt;
    references: [id]&lt;br&gt;
  )&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The important thing here is that there are two related concepts:&lt;/p&gt;

&lt;p&gt;Foreign Key&lt;br&gt;
     ↓&lt;br&gt;
clientId&lt;br&gt;
     ↓&lt;br&gt;
Organisation&lt;/p&gt;

&lt;p&gt;The foreign key is the value stored in the database.&lt;/p&gt;

&lt;p&gt;The relation is how Prisma represents the connection between models.&lt;/p&gt;

&lt;p&gt;Understanding that distinction makes Prisma errors much easier to reason about.&lt;/p&gt;

&lt;p&gt;Don't blindly trust the error — understand what it means&lt;/p&gt;

&lt;p&gt;One of the most useful things I've learned is that fixing errors isn't just about making the red message disappear.&lt;/p&gt;

&lt;p&gt;Suppose Prisma reports that a required relation is missing.&lt;/p&gt;

&lt;p&gt;The first instinct might be:&lt;/p&gt;

&lt;p&gt;"What syntax do I need to make this error go away?"&lt;/p&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;p&gt;"Why does Prisma think this relationship is required?"&lt;/p&gt;

&lt;p&gt;That leads you back through the chain:&lt;/p&gt;

&lt;p&gt;Database&lt;br&gt;
     ↓&lt;br&gt;
Prisma schema&lt;br&gt;
     ↓&lt;br&gt;
Generated Prisma Client&lt;br&gt;
     ↓&lt;br&gt;
Service implementation&lt;/p&gt;

&lt;p&gt;If the Prisma schema says a relation is required, the generated client will enforce that expectation.&lt;/p&gt;

&lt;p&gt;So the error can actually be useful information about the architecture.&lt;/p&gt;

&lt;p&gt;Database schema and application schema must agree&lt;/p&gt;

&lt;p&gt;Another important lesson was dealing with differences between the database and the Prisma schema.&lt;/p&gt;

&lt;p&gt;Imagine your Prisma model contains:&lt;/p&gt;

&lt;p&gt;updatedAt DateTime &lt;a class="mentioned-user" href="https://dev.to/map"&gt;@map&lt;/a&gt;("updated_at")&lt;/p&gt;

&lt;p&gt;but the actual database table doesn't contain:&lt;/p&gt;

&lt;p&gt;updated_at&lt;/p&gt;

&lt;p&gt;Your application can fail even though your TypeScript code looks perfectly reasonable.&lt;/p&gt;

&lt;p&gt;The problem isn't necessarily the service.&lt;/p&gt;

&lt;p&gt;The problem is that the application's understanding of the database doesn't match the actual database.&lt;/p&gt;

&lt;p&gt;This gives us another useful model:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Prisma Schema
          │
          │ should match
          ▼
    MySQL Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When those two disagree, strange runtime errors can appear.&lt;/p&gt;

&lt;p&gt;This is why database migrations, schema synchronization, and generated clients are important parts of backend development.&lt;/p&gt;

&lt;p&gt;API design matters too&lt;/p&gt;

&lt;p&gt;The module also required endpoints for different stages of an HR query.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;PUT /queries/:referenceId/response&lt;/p&gt;

&lt;p&gt;could be used to submit a response.&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;PUT /queries/:referenceId/resolve&lt;/p&gt;

&lt;p&gt;could be used to resolve the query.&lt;/p&gt;

&lt;p&gt;These endpoints aren't just about making HTTP requests work.&lt;/p&gt;

&lt;p&gt;They represent actual business actions.&lt;/p&gt;

&lt;p&gt;That means the backend needs to consider things such as:&lt;/p&gt;

&lt;p&gt;Who is allowed to perform the action?&lt;br&gt;
Which employee does the query belong to?&lt;br&gt;
What state is the query currently in?&lt;br&gt;
Can an already-resolved query be resolved again?&lt;br&gt;
What should be recorded in the audit log?&lt;br&gt;
What happens if the reference number doesn't exist?&lt;/p&gt;

&lt;p&gt;This is where backend development becomes more than CRUD.&lt;/p&gt;

&lt;p&gt;You're implementing business rules.&lt;/p&gt;

&lt;p&gt;Authorization is part of the feature&lt;/p&gt;

&lt;p&gt;For an HR system, not every authenticated user should be able to perform every action.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;User&lt;br&gt;
 │&lt;br&gt;
 ├── Authentication&lt;br&gt;
 │       ↓&lt;br&gt;
 │    "Who are you?"&lt;br&gt;
 │&lt;br&gt;
 └── Authorization&lt;br&gt;
         ↓&lt;br&gt;
      "What are you allowed to do?"&lt;/p&gt;

&lt;p&gt;A user being logged in doesn't automatically mean they should be able to resolve an HR query.&lt;/p&gt;

&lt;p&gt;This is why role-based authorization is important in business applications.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;HR Admin&lt;br&gt;
   ├── Create query&lt;br&gt;
   ├── Respond&lt;br&gt;
   ├── Resolve&lt;br&gt;
   └── View audit history&lt;/p&gt;

&lt;p&gt;Regular Employee&lt;br&gt;
   └── View relevant information&lt;/p&gt;

&lt;p&gt;The exact permissions depend on the application's requirements, but the principle remains the same.&lt;/p&gt;

&lt;p&gt;Swagger became useful during development&lt;/p&gt;

&lt;p&gt;Another tool I found useful during the process was Swagger.&lt;/p&gt;

&lt;p&gt;Instead of testing every endpoint manually from the frontend, Swagger provides an interface where the API can be tested directly.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;PUT /queries/{referenceId}/response&lt;/p&gt;

&lt;p&gt;Request:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "responseText": "Response from employee"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This makes it easier to determine whether a problem is coming from:&lt;/p&gt;

&lt;p&gt;the frontend&lt;br&gt;
the API request&lt;br&gt;
validation&lt;br&gt;
the service&lt;br&gt;
Prisma&lt;br&gt;
or the database&lt;/p&gt;

&lt;p&gt;That separation can save a lot of debugging time.&lt;/p&gt;

&lt;p&gt;What I learned&lt;/p&gt;

&lt;p&gt;The biggest lesson wasn't a particular NestJS command or Prisma syntax.&lt;/p&gt;

&lt;p&gt;It was learning to think about the entire system.&lt;/p&gt;

&lt;p&gt;When something breaks, I now try to trace it through the architecture:&lt;/p&gt;

&lt;p&gt;Request&lt;br&gt;
  ↓&lt;br&gt;
Controller&lt;br&gt;
  ↓&lt;br&gt;
Validation&lt;br&gt;
  ↓&lt;br&gt;
Service&lt;br&gt;
  ↓&lt;br&gt;
Prisma&lt;br&gt;
  ↓&lt;br&gt;
Database&lt;/p&gt;

&lt;p&gt;Then I ask:&lt;/p&gt;

&lt;p&gt;Where does the expectation stop matching reality?&lt;/p&gt;

&lt;p&gt;That question is often more useful than simply searching for the error message.&lt;/p&gt;

&lt;p&gt;Final thoughts&lt;/p&gt;

&lt;p&gt;Working on real software has changed the way I think about programming.&lt;/p&gt;

&lt;p&gt;Tutorials can teach you how to create a controller, define a Prisma model, or write a database query.&lt;/p&gt;

&lt;p&gt;Real projects teach you how those pieces interact.&lt;/p&gt;

&lt;p&gt;You encounter:&lt;/p&gt;

&lt;p&gt;unexpected database constraints&lt;br&gt;
relationship problems&lt;br&gt;
schema mismatches&lt;br&gt;
authorization requirements&lt;br&gt;
business rules&lt;br&gt;
API design decisions&lt;br&gt;
debugging problems that don't have a one-line solution&lt;/p&gt;

&lt;p&gt;And that's where a lot of the actual learning happens.&lt;/p&gt;

&lt;p&gt;I'm continuing to improve my backend engineering skills and to build systems that solve real business problems.&lt;/p&gt;

&lt;p&gt;There is still a lot to learn, but every difficult bug is another opportunity to understand the system better.&lt;/p&gt;

&lt;h1&gt;
  
  
  NestJS #TypeScript #Prisma #MySQL #BackendDevelopment #SoftwareEngineering #WebDevelopment #Programming
&lt;/h1&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>nestjs</category>
      <category>prisma</category>
    </item>
  </channel>
</rss>
