<?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: Akhil Baraik</title>
    <description>The latest articles on DEV Community by Akhil Baraik (@akhilbaraik).</description>
    <link>https://dev.to/akhilbaraik</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%2F4172605%2F869f70fe-db44-4981-a07a-ef8d643e2fdd.jpg</url>
      <title>DEV Community: Akhil Baraik</title>
      <link>https://dev.to/akhilbaraik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akhilbaraik"/>
    <language>en</language>
    <item>
      <title>What Building a Multi-Outlet Pizza Ordering App Taught Me About Location, Time Zones and Never Blocking the User</title>
      <dc:creator>Akhil Baraik</dc:creator>
      <pubDate>Mon, 12 Oct 2026 04:38:07 +0000</pubDate>
      <link>https://dev.to/akhilbaraik/what-building-a-multi-outlet-pizza-ordering-app-taught-me-about-location-time-zones-and-never-1hdd</link>
      <guid>https://dev.to/akhilbaraik/what-building-a-multi-outlet-pizza-ordering-app-taught-me-about-location-time-zones-and-never-1hdd</guid>
      <description>&lt;p&gt;Food ordering apps look simple from the outside. You open the app, pick a pizza, pay, and wait for the doorbell. When my team at Crework Tech started work on the SUSI Pizza platform, I thought the hard parts would be payments and push notifications. They turned out to be the easy parts. The real work was in deciding which store serves a customer, what time it is for that store, and how to say "no" to a customer without making them close the app.&lt;/p&gt;

&lt;p&gt;This post is a walk through the engineering decisions that mattered most. If you are building anything with multiple physical locations, delivery zones or opening hours, I hope some of it saves you a few late nights.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;SUSI Pizza is a multi-outlet pizza and cake chain. Each outlet has its own delivery radius, its own opening hours and its own menu. Before the app, ordering was mostly manual and phone based. Our scope covered three pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A customer mobile app built in React Native, with Redux for state.&lt;/li&gt;
&lt;li&gt;A React admin panel for the product catalogue, per-store menus and pricing, banners, orders, store settings and staff roles.&lt;/li&gt;
&lt;li&gt;A Node.js and Express REST API on Firebase Firestore, with Firebase Auth (Google Sign-In plus mobile OTP), role-based access for super admins and store admins, and push notifications.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A separate team built the store app and the delivery-partner app. Both talk to the same backend, so our API had to be the single source of truth for every outlet. Payments run through Razorpay for UPI, cards and wallets, with Cash on Delivery as a fallback. Indian customers still like having that choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: Which store serves this customer?
&lt;/h2&gt;

&lt;p&gt;Given a customer's GPS position, the backend has to find the nearest store that is both in range and open right now. That sounds like one query. In practice it is three different answers that the app must treat differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A store covers you and it is open.&lt;/strong&gt; Show the menu, allow checkout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A store covers you but it is closed right now.&lt;/strong&gt; Tell the customer exactly that, and when possible, when it opens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No store covers your location at all.&lt;/strong&gt; Say so honestly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The early version collapsed the last two into one generic error. From the customer's side, "we don't deliver here" and "come back at 11" are very different messages. Splitting them made the app feel much more honest, and it also gave the client clearer data about where demand was coming from.&lt;/p&gt;

&lt;p&gt;We also found a subtle bug in the fallback path. When the customer's location could not be resolved cleanly, one branch of code quietly used a store's own coordinates in place of the customer's. Distance from a store to itself is zero, so an out-of-range store could be labelled as deliverable. Nothing crashed. The numbers just looked plausible. The fix was simple once we saw it: never substitute a store location for a customer location, and treat "location unknown" as its own state rather than borrowing a value from somewhere else.&lt;/p&gt;

&lt;p&gt;My takeaway: when a function can return "no", make it say &lt;em&gt;why&lt;/em&gt;. An enum of reasons is far easier to debug and to design UI around than a boolean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: What time is it, really?
&lt;/h2&gt;

&lt;p&gt;Every store has opening hours in Indian Standard Time. Servers and cloud functions often do not run in IST. If you write &lt;code&gt;new Date().getHours()&lt;/code&gt; on a server set to UTC, your "open at 11 AM" check is off by five and a half hours, and the half hour makes it even easier to miss in testing.&lt;/p&gt;

&lt;p&gt;This bug class came back more than once in different corners of the code, so we stopped fixing it case by case. Store hours logic now always converts to the IST time zone explicitly before comparing, rather than trusting the machine's local time. If you are in India and deploy to any global cloud region, I would make this a rule from day one. Also test the edges: just before opening, just after closing, and stores that close after midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: Products are not one size fits all
&lt;/h2&gt;

&lt;p&gt;Pizzas have sizes. Cakes have weights. Drinks have serving counts. Some items have spice levels. Hardcoding a "size" field would have broken the moment the client added a new category.&lt;/p&gt;

&lt;p&gt;We built an admin-configurable variant type system. An admin defines a variant type such as Size, Weight, Serving, Volume or Spice Level, and each product can have options under it. Every option carries its own price, discount price, stock and SKU. The customer app reads this generically, so product cards and the cart render correctly without new app releases for every new kind of product.&lt;/p&gt;

&lt;p&gt;The lesson here is the usual one, but it is worth repeating: if the client will keep adding things, make the &lt;em&gt;shape&lt;/em&gt; configurable, not just the values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 4: Copying menus across stores
&lt;/h2&gt;

&lt;p&gt;With multiple outlets each carrying dozens of products, assigning and pricing items store by store was slow, repetitive work for the admin team. We added a "copy from another store" option right inside the assign flow, and a bulk tool that clones an entire menu with full pricing to another store. In one case that meant cloning 90 products in a single action instead of entering them one by one.&lt;/p&gt;

&lt;p&gt;Admin tooling rarely makes it into launch announcements, but it decides whether the client actually keeps their data up to date. If updating the menu is painful, the menu goes stale, and then the customer app is wrong no matter how good the code is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 5: Never block browsing
&lt;/h2&gt;

&lt;p&gt;This became a product principle across the whole app. A closed store, an out-of-range address or an unresolved location should never fully lock the customer out. They can always browse the menu and add items to the cart. Only checkout is gated on real delivery eligibility.&lt;/p&gt;

&lt;p&gt;It sounds obvious, but it touched a lot of code: the store selection flow, cart validation, and the order calculation on the server. The server always re-validates price and eligibility at checkout, so the client app can be relaxed about browsing without ever trusting itself on money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Promotions as rules, not campaigns
&lt;/h2&gt;

&lt;p&gt;The client wanted offers like Buy X Get Y, combos, minimum order value discounts and free delivery above a threshold. Instead of writing code for each campaign, we built a rule-based promotion engine that evaluates whatever rules the admin configures. It also shows "locked" offers, so a customer can see what they need to add to unlock a deal. That one small UI choice turns a promotion from a hidden discount into a nudge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small things that add up
&lt;/h2&gt;

&lt;p&gt;A few other items that took real time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate limiting on auth endpoints, because OTP endpoints attract abuse.&lt;/li&gt;
&lt;li&gt;React Native touch responder and scroll performance fixes, so long menus stay smooth on budget Android phones.&lt;/li&gt;
&lt;li&gt;Live order tracking through Received, Preparing, Out for Delivery and Delivered, which stays visible as the customer moves around the app.&lt;/li&gt;
&lt;li&gt;One-tap reorder from order history, since most food orders are repeats.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And one habit I now push on every project: for every location or pricing bug fix, we verified the result against live production data before closing it, instead of shipping on faith because the unit test passed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell another team
&lt;/h2&gt;

&lt;p&gt;If you are building a multi-location ordering product, here is my short list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Model "why not" explicitly. Out of range and closed are different states.&lt;/li&gt;
&lt;li&gt;Never trust server local time. Convert to the store's time zone every time.&lt;/li&gt;
&lt;li&gt;Make product variants configurable from the admin side.&lt;/li&gt;
&lt;li&gt;Invest in bulk admin tools early.&lt;/li&gt;
&lt;li&gt;Let users browse freely, and gate only the action that truly needs it.&lt;/li&gt;
&lt;li&gt;Validate prices on the server, always.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can read the full &lt;a href="https://creworktech.in/case-studies/susi-pizza-app" rel="noopener noreferrer"&gt;SUSI Pizza case study&lt;/a&gt; on our site, including the platform breakdown and the customer journey. If you are a business in Jharkhand thinking about an app of your own, we also have a page on our &lt;a href="https://creworktech.in/mobile-app-development-in-ranchi" rel="noopener noreferrer"&gt;mobile app development work in Ranchi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would love to hear how other teams handle store hours and delivery zones. Drop your approach in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Akhil Baraik is the founder of Crework Tech, an IT and digital marketing agency based in Ranchi, Jharkhand. &lt;a href="https://www.linkedin.com/in/akhil-baraik-09613b109/" rel="noopener noreferrer"&gt;Connect with him on LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>node</category>
      <category>firebase</category>
      <category>mobile</category>
    </item>
    <item>
      <title>How Small Businesses in India Can Choose the Right Software Development Company</title>
      <dc:creator>Akhil Baraik</dc:creator>
      <pubDate>Mon, 12 Oct 2026 04:07:35 +0000</pubDate>
      <link>https://dev.to/akhilbaraik/how-small-businesses-in-india-can-choose-the-right-software-development-company-1272</link>
      <guid>https://dev.to/akhilbaraik/how-small-businesses-in-india-can-choose-the-right-software-development-company-1272</guid>
      <description>&lt;p&gt;I run a small IT and digital marketing agency in Ranchi, and I have sat on both sides of the table: pitching for software projects and hiring outside developers. In my experience, most failed projects do not fail because the code was bad. They fail because the business and the developer never agreed on what was being built, who owned it, or what would happen after launch.&lt;/p&gt;

&lt;p&gt;If you run a small business in India and plan to get custom software built, this is what I wish every client read first. It applies whether you hire locally, in a metro, or abroad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start by Defining Your Requirements
&lt;/h2&gt;

&lt;p&gt;Before you speak to any vendor, write down what you need. A few pages in plain language are enough. Answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What problem are you solving, and for whom?&lt;/li&gt;
&lt;li&gt;Who will use the software? Staff, customers, field technicians, managers?&lt;/li&gt;
&lt;li&gt;What are the five or six things the software must do on day one?&lt;/li&gt;
&lt;li&gt;What can wait for a second phase?&lt;/li&gt;
&lt;li&gt;Does it need to connect with anything you already use, such as Tally, a payment gateway, WhatsApp or an existing website?&lt;/li&gt;
&lt;li&gt;What is your rough budget and your ideal launch date?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Separating "must have" from "nice to have" keeps the first version smaller and cheaper, and gives you a fair way to compare quotes. If two quotes differ widely, check whether both companies understood the same scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixed Price vs Time and Material
&lt;/h2&gt;

&lt;p&gt;You will usually be offered one of two pricing models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixed price&lt;/strong&gt; means you agree on a defined scope and a set cost. It works well when your requirements are clear and unlikely to change. The risk is that every change after signing becomes a "change request" with extra cost, and some vendors pad the quote to protect themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time and material&lt;/strong&gt; means you pay for the hours or days actually worked, usually at an agreed rate. It suits projects where you expect to learn and adjust as you go. The risk is that costs can grow if nobody is watching the scope.&lt;/p&gt;

&lt;p&gt;For many small businesses, a mix works best. Fix the price for a clearly defined first version, then move to time and material for improvements later. Whichever model you choose, ask for payment to be linked to milestones you can actually see and test, not just to dates on a calendar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Portfolios and Case Studies Properly
&lt;/h2&gt;

&lt;p&gt;Almost every company has a portfolio page. The trick is to look past the screenshots. When you review past work, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was this project similar to mine in size and type?&lt;/li&gt;
&lt;li&gt;What exactly did this company build? The whole system, or just the design?&lt;/li&gt;
&lt;li&gt;Can I see a live version or a demo?&lt;/li&gt;
&lt;li&gt;Can I speak to a past client?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good case study explains the problem, the approach and the technology used. As one example from my own work, we wrote up a &lt;a href="https://creworktech.in/case-studies/seekoji-electric-erp" rel="noopener noreferrer"&gt;custom after-sales service management system we built for Seekoji Electric&lt;/a&gt;, which used Next.js, Node.js, Express and MongoDB, with JWT authentication and role-based access so different staff saw only what they needed. I mention it only to show the kind of detail you should expect. If a company cannot tell you what they used and why, be careful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask Questions About the Tech Stack
&lt;/h2&gt;

&lt;p&gt;You do not need to be a programmer to ask sensible questions. The goal is software that will not be hard to maintain or move later. Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which languages and frameworks will you use, and why are they right for this project?&lt;/li&gt;
&lt;li&gt;Are these technologies widely used, so that another developer could take over if needed?&lt;/li&gt;
&lt;li&gt;Where will the software be hosted, and who pays for the hosting?&lt;/li&gt;
&lt;li&gt;How will you handle security, logins and user permissions?&lt;/li&gt;
&lt;li&gt;Will the software work well on mobile phones?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be wary of anyone who insists on an obscure or in-house framework without a clear reason. Popular, well-documented tools make it much easier to find help in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Sure You Own the Code and IP
&lt;/h2&gt;

&lt;p&gt;This is the point small businesses most often overlook. If you are paying for custom software, your contract should clearly state that the source code, designs, database structure and related intellectual property belong to you once you have paid.&lt;/p&gt;

&lt;p&gt;Also ask for practical access, not just legal ownership:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code should live in a repository, such as GitHub or GitLab, that you control or can be added to.&lt;/li&gt;
&lt;li&gt;Hosting, domain and cloud accounts should ideally be registered in your business name.&lt;/li&gt;
&lt;li&gt;You should receive all passwords, admin credentials and documentation at handover.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have seen businesses locked out of their own software because a freelancer registered everything personally and then became unreachable.&lt;/p&gt;

&lt;h2&gt;
  
  
  NDAs and Contracts
&lt;/h2&gt;

&lt;p&gt;If you are sharing business ideas, customer data or internal processes, it is reasonable to ask for a non-disclosure agreement before detailed discussions. Most professional companies will sign one without fuss.&lt;/p&gt;

&lt;p&gt;The main contract should cover at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The scope of work, ideally with your requirements document attached&lt;/li&gt;
&lt;li&gt;Milestones, deliverables and payment terms&lt;/li&gt;
&lt;li&gt;Ownership of code and IP&lt;/li&gt;
&lt;li&gt;How change requests are priced and approved&lt;/li&gt;
&lt;li&gt;Warranty or bug-fixing period after launch&lt;/li&gt;
&lt;li&gt;Confidentiality and handling of your data&lt;/li&gt;
&lt;li&gt;What happens if either side wants to end the project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are unsure about any clause, a lawyer's review costs far less than a dispute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communication and Timezone
&lt;/h2&gt;

&lt;p&gt;Good communication matters as much as good coding. Before signing, find out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who will be your single point of contact?&lt;/li&gt;
&lt;li&gt;How often will you get updates, and in what form?&lt;/li&gt;
&lt;li&gt;Which tools will be used, such as WhatsApp, email, Google Meet or a project board?&lt;/li&gt;
&lt;li&gt;Can you see progress regularly, for example a working demo every week or two?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Timezone matters too. A team in India shares your working hours, which makes quick calls easy. If you hire offshore, agree on overlapping meeting hours. You should also be able to discuss your needs in a language you are comfortable with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan for Post-Launch Support
&lt;/h2&gt;

&lt;p&gt;Software is never really finished at launch. Users will find bugs, phones and browsers will update, and you will want small changes. Ask each company:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long is the free bug-fixing period after launch?&lt;/li&gt;
&lt;li&gt;What does a support or maintenance plan include, and what does it cost?&lt;/li&gt;
&lt;li&gt;How fast will they respond to a critical problem?&lt;/li&gt;
&lt;li&gt;Who handles server updates, backups and security patches?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get these answers in writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Red Flags to Watch For
&lt;/h2&gt;

&lt;p&gt;Take these warning signs seriously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A quote given before they have understood your requirements&lt;/li&gt;
&lt;li&gt;A price far lower than every other quote, with no clear explanation&lt;/li&gt;
&lt;li&gt;Reluctance to put ownership of code in writing&lt;/li&gt;
&lt;li&gt;No real portfolio, or work they cannot explain in detail&lt;/li&gt;
&lt;li&gt;Pressure to pay most of the amount upfront&lt;/li&gt;
&lt;li&gt;Vague answers about who will actually work on your project&lt;/li&gt;
&lt;li&gt;No mention of testing, documentation or support&lt;/li&gt;
&lt;li&gt;Promises that sound too good, such as a complex app in a week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One red flag is not always a deal breaker, but two or three together usually are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local vs Metro vs Offshore Agencies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local agencies&lt;/strong&gt; in your own city or state can meet you in person, understand regional customers and often work at reasonable rates. Smaller cities now have capable teams, but check their experience carefully, because quality varies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metro agencies&lt;/strong&gt; in places like Bengaluru, Mumbai, Delhi NCR or Pune may have larger teams and more specialists. They can also be more expensive, and a small project may not get their senior people's attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offshore or foreign agencies&lt;/strong&gt; can bring different experience, but timezone gaps, higher costs and less familiarity with Indian business practices, such as GST invoicing or local payment methods, can make things harder for a small Indian business.&lt;/p&gt;

&lt;p&gt;Judge the team, not the location. A focused small team that understands your business can serve you better than a big name that treats your project as a minor account.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Short Checklist
&lt;/h2&gt;

&lt;p&gt;Before you sign, make sure you can tick these boxes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I have a written list of requirements, split into must have and later.&lt;/li&gt;
&lt;li&gt;I have compared at least two or three quotes for the same scope.&lt;/li&gt;
&lt;li&gt;I understand whether the price is fixed or time and material.&lt;/li&gt;
&lt;li&gt;I have seen real, relevant past work and ideally spoken to a past client.&lt;/li&gt;
&lt;li&gt;I know which technologies will be used and why.&lt;/li&gt;
&lt;li&gt;The contract says I own the code and IP after payment.&lt;/li&gt;
&lt;li&gt;Code, hosting and domain accounts will be in my control.&lt;/li&gt;
&lt;li&gt;An NDA is signed if I am sharing sensitive information.&lt;/li&gt;
&lt;li&gt;Payments are tied to milestones I can test.&lt;/li&gt;
&lt;li&gt;I know who my contact is and how often I will get updates.&lt;/li&gt;
&lt;li&gt;A bug-fixing period and support plan are agreed in writing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Choosing a software development company is less about finding a perfect vendor and more about asking the right questions early.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Akhil Baraik is the founder of Crework Tech, an IT and digital marketing agency based in Ranchi, Jharkhand.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>business</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building an After-Sales Service System for an Electrical Manufacturer with Next.js, Express and MongoDB</title>
      <dc:creator>Akhil Baraik</dc:creator>
      <pubDate>Fri, 09 Oct 2026 06:12:06 +0000</pubDate>
      <link>https://dev.to/akhilbaraik/building-an-after-sales-service-system-for-an-electrical-manufacturer-with-nextjs-express-and-2dkl</link>
      <guid>https://dev.to/akhilbaraik/building-an-after-sales-service-system-for-an-electrical-manufacturer-with-nextjs-express-and-2dkl</guid>
      <description>&lt;p&gt;A lot of our work at Crework Tech is marketing-facing: websites, SEO and campaigns. So when Seekoji Electric, an electrical manufacturing and service company here in Ranchi, asked us for a system to run their after-sales service, it was a different kind of project. Nobody would see it on Google. Its whole job was to keep products, customers, technicians and paperwork from drifting out of sync.&lt;/p&gt;

&lt;p&gt;This post walks through how we thought about it and a few patterns that I think are useful for anyone building a similar ERP-style app. The code below is simplified and generic. It shows the shape of the approach, not a copy of the production codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the system needed to do
&lt;/h2&gt;

&lt;p&gt;The scope came down to ten modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dashboard and analytics&lt;/li&gt;
&lt;li&gt;Customer management&lt;/li&gt;
&lt;li&gt;Service request management&lt;/li&gt;
&lt;li&gt;Repair workflow&lt;/li&gt;
&lt;li&gt;Product testing workflow (assign products for testing and update results)&lt;/li&gt;
&lt;li&gt;Technician assignment&lt;/li&gt;
&lt;li&gt;User and role management (RBAC)&lt;/li&gt;
&lt;li&gt;Reports and analytics&lt;/li&gt;
&lt;li&gt;Status tracking&lt;/li&gt;
&lt;li&gt;Search and filters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every product that comes in for service moves through six statuses: &lt;strong&gt;pending, under testing, received, under repair, repaired&lt;/strong&gt; and &lt;strong&gt;delivered&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The stack was Next.js and Tailwind CSS on the front end, Node.js and Express.js for REST APIs, MongoDB for storage, and JWT authentication with role-based access control.&lt;/p&gt;

&lt;p&gt;If you look at that module list, almost all of it hangs off one thing: the status of a repair. The dashboard counts by status. Reports group by status. Technicians filter by status. Customers ask "what's the status?" So that is where I'll start.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Treat status as a state machine, not a dropdown
&lt;/h2&gt;

&lt;p&gt;The easiest way to build a status field is a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; that lets anyone pick any value. The easiest way to break a service workflow is the same thing. A product jumps from "pending" to "delivered", and now your history says something that never happened.&lt;/p&gt;

&lt;p&gt;The fix is to define the allowed transitions in one place on the server and refuse everything else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/repairStatus.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STATUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;PENDING&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;UNDER_TESTING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;under_testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;RECEIVED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;UNDER_REPAIR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;under_repair&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;REPAIRED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repaired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;DELIVERED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delivered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Example transition map. The real edges are a business decision,&lt;/span&gt;
&lt;span class="c1"&gt;// so agree on them with the people who run the service desk.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TRANSITIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PENDING&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNDER_TESTING&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNDER_TESTING&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RECEIVED&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RECEIVED&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNDER_REPAIR&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNDER_REPAIR&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REPAIRED&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REPAIRED&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DELIVERED&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DELIVERED&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canTransition&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;to&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="nx"&gt;TRANSITIONS&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="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TRANSITIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canTransition&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things I like about keeping this as plain data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The front end can import the same map (or fetch it from an endpoint) and only show the buttons that are valid for the current status. The UI stops offering mistakes.&lt;/li&gt;
&lt;li&gt;When the business changes its process, you change one object and the tests that cover it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Checking &lt;code&gt;canTransition&lt;/code&gt; in a route is not enough on its own, though. If two people update the same record at nearly the same moment, a read-then-write can let both through. Put the expected current status in the update filter so MongoDB does the check atomically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// services/repairService.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Repair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../models/Repair&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;canTransition&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lib/repairStatus&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;changeStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repairId&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;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;canTransition&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;to&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&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;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Cannot move from &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="s2"&gt; to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Repair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOneAndUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;repairId&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="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;          &lt;span class="c1"&gt;// only if still in "from"&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;to&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;$push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;history&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;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;at&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;Date&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="na"&gt;new&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&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;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status changed by someone else. Reload and try again.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;history&lt;/code&gt; push in the same update is what gives you a complete service history for free. Every status change records who made it and when, and it can never get out of step with the status itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. JWT authentication plus RBAC middleware in Express
&lt;/h2&gt;

&lt;p&gt;A service system has very different users. Someone at the front desk creates service requests. A technician updates test results and repairs. An admin manages users and roles. Role-based access control keeps each of them to what their job needs.&lt;/p&gt;

&lt;p&gt;I prefer two small middlewares: one that verifies the token, and one that checks permissions. Mapping roles to permissions (instead of checking role names inside routes) keeps things flexible when a new role shows up later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware/auth.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonwebtoken&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;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer &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;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&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="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid or expired token&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="c1"&gt;// Example roles and permissions. Adjust to the client's real org chart.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PERMISSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users:manage&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;repairs:read&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;repairs:write&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;repairs:assign&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;repairs:status&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;reports:read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:read&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;repairs:write&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;repairs:assign&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;repairs:status&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;reports:read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;technician&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:read&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;repairs:test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;required&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;granted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PERMISSIONS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&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;granted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Forbidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Signing the token at login is the usual &lt;code&gt;jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '8h' })&lt;/code&gt;. Then routes read almost like documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/repairs.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authorize&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../middleware/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../controllers/repairs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&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;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:id/assign&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:assign&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignTechnician&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:id/test-result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recordTestResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/:id/status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repairs:status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeStatus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;One note: route-level permissions are the first gate, not the only one. Inside &lt;code&gt;changeStatus&lt;/code&gt; it is worth also checking that, for example, a technician can only move repairs assigned to them. Row-level rules like that live in the service layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A MongoDB schema sketch for repairs
&lt;/h2&gt;

&lt;p&gt;Here is the rough shape of a repair document. Customers and users live in their own collections and are referenced by id. Test results and history are embedded, because they belong to one repair and are always read with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// models/Repair.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Types&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongoose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;STATUS&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lib/repairStatus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TestResultSchema&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;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&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="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pass&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;fail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;testedBy&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;testedAt&lt;/span&gt;&lt;span class="p"&gt;:&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="nb"&gt;Date&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_id&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HistorySchema&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;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&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="nb"&gt;Date&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_id&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RepairSchema&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;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;ticketNo&lt;/span&gt;&lt;span class="p"&gt;:&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="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Customer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;serialNo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;complaint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&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="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="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PENDING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;index&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="na"&gt;assignedTechnician&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;testResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TestResultSchema&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;HistorySchema&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Supports the common "my open jobs" and dashboard queries&lt;/span&gt;
&lt;span class="nx"&gt;RepairSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;assignedTechnician&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Supports the search box&lt;/span&gt;
&lt;span class="nx"&gt;RepairSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ticketNo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&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;product.serialNo&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;text&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;product.model&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;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Repair&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RepairSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Design your indexes from the screens, not the other way round. Search and filters, the technician's work queue and the dashboard each turn into a query, and each query should hit an index.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Dashboard numbers from one aggregation
&lt;/h2&gt;

&lt;p&gt;Once status is reliable, the dashboard is mostly a &lt;code&gt;$group&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Repair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Next.js dashboard calls a REST endpoint that returns this, and Tailwind takes care of turning it into readable cards. Reports are the same idea with a date range and more grouping keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  The outcome, in plain terms
&lt;/h2&gt;

&lt;p&gt;The benefits we set out for this project are all about the day-to-day work: faster repair processing, centralized service records, better technician productivity, a complete service history, real-time status tracking, less manual paperwork, better operational visibility and an improved experience for their customers. We've written up the project in more detail in our &lt;a href="https://creworktech.in/case-studies/seekoji-electric-erp" rel="noopener noreferrer"&gt;Seekoji Electric ERP case study&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd pass on
&lt;/h2&gt;

&lt;p&gt;If you are building something similar for a manufacturer or service business:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model the workflow before the screens.&lt;/strong&gt; Sit with the people who handle the products and write the status transitions down. That object becomes the spine of the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce transitions on the server, atomically.&lt;/strong&gt; The UI can guide people, but the database filter is what keeps data honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write history in the same update as the status.&lt;/strong&gt; You will need it the first time a customer disputes what happened to their product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use permissions, not role checks, in routes.&lt;/strong&gt; New roles stop being a refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index for the screens people actually use.&lt;/strong&gt; Search, filters and work queues should never scan the whole collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is new or clever. It is just the stuff that is easy to skip when a project starts as "a simple form to track repairs", and painful to add later.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Akhil Baraik is the founder of Crework Tech, a digital marketing and IT agency based in Ranchi, Jharkhand. You can find him on &lt;a href="https://www.linkedin.com/in/akhil-baraik-09613b109/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>mongodb</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
