<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Nazmul Khalid]]></title><description><![CDATA[Nazmul Khalid]]></description><link>https://technonazmul.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Nazmul Khalid</title><link>https://technonazmul.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 12:19:58 GMT</lastBuildDate><atom:link href="https://technonazmul.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Enforcing Multi-Tenant Isolation Structurally, Not by Convention]]></title><description><![CDATA[Originally published at nazmulkhalid.com.
TL;DR: Relying on every query to remember WHERE schoolId = ... doesn't survive a real codebase. Bicitra EMS injects the tenant scope automatically with a Pris]]></description><link>https://technonazmul.hashnode.dev/enforcing-multi-tenant-isolation-structurally-not-by-convention</link><guid isPermaLink="true">https://technonazmul.hashnode.dev/enforcing-multi-tenant-isolation-structurally-not-by-convention</guid><category><![CDATA[SaaS]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Security]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Nazmul Khalid]]></dc:creator><pubDate>Fri, 16 Oct 2026 13:24:05 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published at <a href="https://nazmulkhalid.com/ideas/enforcing-multi-tenant-isolation-structurally">nazmulkhalid.com</a>.</em></p>
<p><strong>TL;DR:</strong> Relying on every query to remember <code>WHERE schoolId = ...</code> doesn't survive a real codebase. Bicitra EMS injects the tenant scope automatically with a Prisma Client Extension, but a nested <code>include</code> still bypassed it and leaked one school's data through seven endpoints. The fix was structural: a central registry of tenant-scoped foreign keys, validated at write time, so new endpoints are protected by default.</p>
<p>Bicitra EMS is a multi-tenant education management system: one shared platform, any number of schools, each school's data supposed to be fully invisible to every other school. Tenant isolation is the one property a multi-tenant SaaS cannot get partially right — a single leaked row is a data breach, not a bug ticket.</p>
<h2>The convention that almost worked</h2>
<p>Every tenant-scoped table in the system carries a <code>schoolId</code> column. The straightforward way to enforce isolation is convention: every query that touches a tenant table includes a <code>WHERE schoolId = ...</code> clause, and every engineer remembers to add it.</p>
<p>That convention doesn't scale past the first missed line. So instead of trusting each call site, isolation is applied by a Prisma Client Extension that intercepts queries and injects the tenant scope automatically — a caller doesn't get the chance to forget the clause, because the clause isn't theirs to write.</p>
<h2>Where the convention still had a gap</h2>
<p>Automated testing is what actually found the problem: 225 API tests running against a real Postgres instance, as part of a 338-test suite that also covered browser and mobile flows. Tenant isolation is exactly the kind of property a mock can't validate — a mocked database can't tell you whether your real query actually stayed inside its tenant boundary.</p>
<p>The tests found a defect in code that had already been marked done: a <strong>nested Prisma <code>include</code> on a relation is evaluated as part of its parent query and never gets routed back through the extension that owns tenant scoping.</strong> A fee invoice, an attendance record, or a salary row referencing another school's student ID would pull that other school's guardian details and QR gate-scan credential straight back out — reachable through seven different endpoints, none of which looked wrong in isolation.</p>
<h2>Fixing the class of bug, not the seven instances</h2>
<p>Patching each of the seven endpoints individually would have left the same gap open for the eighth endpoint someone writes next month. Instead, the fix is structural: every inbound tenant-scoped foreign key is declared in one central registry and validated at write time, inside the same extension that already owns tenant scoping. A new endpoint gets the protection automatically, because the registry — not the endpoint author's memory — is what enforces it.</p>
<h2>Why this matters beyond one bug</h2>
<p>"We enforce tenant isolation with an ORM extension" sounds airtight until you remember that ORMs have edge cases — relation includes, raw queries, batch operations — that don't always route through the same code path as a normal query. The lesson isn't "use a query extension." It's narrower and more useful than that: <strong>whatever mechanism you use to enforce a security invariant, assume it has at least one code path that bypasses it, and go looking for that path with real tests before an attacker — or another tenant — finds it for you.</strong></p>
<p>The same test pass caught two other real defects: a double-payment race condition from two simultaneous "pay now" taps, and a gate console accidentally exposing password hashes to the lowest-privilege role in the building. None of the three were found by code review. All three were found by tests that exercised real concurrency and real data, not mocks.</p>
<hr />
<p><em>I'm Nazmul Khalid, a lead software engineer. I build multi-tenant SaaS products and review existing codebases for founders. The Bicitra EMS case study is <a href="https://nazmulkhalid.com/work/bicitra-ems">here</a>, and more on how I review code is <a href="https://nazmulkhalid.com/services/technical-leadership">here</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to Hire a Remote Developer Without Getting Burned]]></title><description><![CDATA[Originally published at nazmulkhalid.com.
Most remote hires that go wrong don't fail because the developer couldn't code. They fail because of things nobody checked before the contract was signed.
The]]></description><link>https://technonazmul.hashnode.dev/how-to-hire-a-remote-developer-without-getting-burned</link><guid isPermaLink="true">https://technonazmul.hashnode.dev/how-to-hire-a-remote-developer-without-getting-burned</guid><category><![CDATA[startup]]></category><category><![CDATA[hiring]]></category><category><![CDATA[Freelancing]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Nazmul Khalid]]></dc:creator><pubDate>Sun, 11 Oct 2026 16:29:12 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published at <a href="https://nazmulkhalid.com/ideas/how-to-hire-a-remote-developer-without-getting-burned">nazmulkhalid.com</a>.</em></p>
<p>Most remote hires that go wrong don't fail because the developer couldn't code. They fail because of things nobody checked before the contract was signed.</p>
<p>The founder finds out three months in that the developer only worked on a small part of the portfolio project. Or the code sits in the developer's own GitHub, and the server runs on their credit card. Or the invoices kept coming while the product never quite got to a state anyone could try.</p>
<p>I'm writing this from the other side of the table. I've spent more than 10 years building software for clients, most recently as lead engineer on products like DocBee and BD Doctors Directory. These are the five things I'd check if I were hiring someone like me.</p>
<h2>1. Find out which part they actually built</h2>
<p>A portfolio shows you a finished product. It doesn't tell you who did what.</p>
<p>Pick one project and ask them to walk you through a single decision they made on it, and why. You don't need to follow every technical detail. You're listening for whether they can explain it from the inside.</p>
<p>For example, if you asked me about DocBee, a telemedicine app I was lead engineer on, I'd tell you why the booking system <a href="https://nazmulkhalid.com/ideas/preventing-double-booking-in-a-real-time-appointment-system">lets the database refuse a second booking for the same slot</a> instead of trusting the app to check. I'd also tell you what goes wrong if you do it the other way. That's the kind of answer you're looking for: specific, and clearly theirs.</p>
<p>A good answer sounds like "I designed the API and built both mobile apps." A vague one sounds like "we built it", with nothing more when you ask.</p>
<h2>2. Ask how they'd stop your worst bug</h2>
<p>Every product has one thing that must never go wrong. For a booking app, it's two customers getting the same slot. For software sold to many businesses, it's one customer seeing another customer's data. For anything that takes payments, it's charging someone twice.</p>
<p>Ask the developer what your worst bug is and how they'd prevent it.</p>
<p>The answer you want sounds like "the database won't allow it" or "there's a test that tries to break it on every change". The answer to worry about is "we'll be careful".</p>
<p>Being careful doesn't hold up. On a <a href="https://nazmulkhalid.com/work/bicitra-ems">school management system</a> I built, I wrote 338 automated tests for code I had already called finished. They found six real bugs. One of them let a school read another school's data. Careful wasn't enough, and I was the one being careful.</p>
<h2>3. Own the code and the accounts from day one</h2>
<p>This is the one I'd push hardest on, and it's the one that gets skipped most.</p>
<p>Before any work starts, set these up in your own name and invite the developer in:</p>
<ul>
<li><strong>The code repository.</strong> Create a GitHub or GitLab organisation for your company and add the developer to it. Don't let the only copy of your code live in their personal account.</li>
<li><strong>The cloud or hosting account.</strong> Your name, your card. They get access, not ownership.</li>
<li><strong>The domain.</strong> Registered to you, at a registrar you can log in to.</li>
<li><strong>App store accounts.</strong> Apple and Google developer accounts in your company's name.</li>
<li><strong>Third-party services.</strong> Payment gateways, email providers, maps and so on, with your company as the account holder.</li>
</ul>
<p>Then get the contract to say that the rights to the work pass to you once it's paid for. Add an NDA if the idea needs one. I'm not a lawyer, so for anything unusual, have a lawyer look at the contract.</p>
<p>If the code lives in the developer's account and the server runs on their card, you don't really own your product. You're renting it. And you usually find that out at the worst moment, when the relationship ends.</p>
<h2>4. Pay for milestones you can click through</h2>
<p>Hourly billing with no clear milestones is how budgets drift. Nobody is doing anything wrong, and the product still never quite gets finished.</p>
<p>Split the work into milestones you can check yourself:</p>
<ul>
<li>a login that works</li>
<li>a booking you can actually make</li>
<li>a payment that shows up in the admin panel</li>
<li>the app installed on your own phone from a test link</li>
</ul>
<p>Pay when each one works, not because a month has gone by. For fixed-price work, a deposit of 30–50% upfront with the rest paid by milestone is normal. That's how I work too, and it protects both sides.</p>
<p>If you're unsure about a developer, start with something small and paid: a <a href="https://nazmulkhalid.com/hire">scoping plan or a review of your existing code</a>. You'll learn how they think, how they write things down and how they handle your questions, before you commit to a big build.</p>
<h2>5. Agree how you'll hear from them</h2>
<p>Time zone overlap matters less than people think. Updates matter more.</p>
<p>Before you start, agree what a normal week looks like:</p>
<ul>
<li><strong>When you'll talk live.</strong> One or two calls a week in your overlap hours is usually enough.</li>
<li><strong>What you'll get in writing on the other days.</strong> A short update: what's done, what's next, and anything blocking.</li>
<li><strong>Where you can see progress.</strong> A test version of the app you can open any time, not just screenshots.</li>
<li><strong>What happens when they're stuck.</strong> You want to hear about a problem the day it shows up, not at the next deadline.</li>
</ul>
<p>Being far apart can help. I work 05:00–23:00 Bangladesh time, which covers the whole UK, European and Gulf working day. For US teams, we talk in their morning and the work moves forward overnight.</p>
<h2>A quick checklist before you sign</h2>
<ul>
<li>They walked you through one real decision on a past project, in their own words.</li>
<li>They named your worst bug and explained how they'd stop it.</li>
<li>The repo, cloud account, domain and app store accounts are in your name.</li>
<li>The contract gives you the rights to the work once it's paid for.</li>
<li>Payment is tied to milestones you can click through.</li>
<li>You've agreed when you'll talk and how you'll get updates.</li>
</ul>
<p>None of this is complicated. It's just easy to skip when you're excited to get started. A good developer won't mind any of it. The good ones would rather you checked.</p>
<hr />
<p><em>I'm Nazmul Khalid, a lead software engineer. I build SaaS products and MVPs for founders. How I work, and what it costs, is on <a href="https://nazmulkhalid.com/hire">my hire page</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Preventing Double-Booking in a Real-Time Appointment System]]></title><description><![CDATA[Originally published at nazmulkhalid.com.
TL;DR: Checking a slot is free in application code and then writing the booking breaks when two requests arrive together. In DocBee, a telemedicine app I was ]]></description><link>https://technonazmul.hashnode.dev/preventing-double-booking-in-a-real-time-appointment-system</link><guid isPermaLink="true">https://technonazmul.hashnode.dev/preventing-double-booking-in-a-real-time-appointment-system</guid><category><![CDATA[MongoDB]]></category><category><![CDATA[Databases]]></category><category><![CDATA[System Design]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Nazmul Khalid]]></dc:creator><pubDate>Fri, 02 Oct 2026 13:56:00 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published at <a href="https://nazmulkhalid.com/ideas/preventing-double-booking-in-a-real-time-appointment-system">nazmulkhalid.com</a>.</em></p>
<p><strong>TL;DR:</strong> Checking a slot is free in application code and then writing the booking breaks when two requests arrive together. In DocBee, a telemedicine app I was lead engineer on, the database enforces it instead. A unique index on doctor + date + slot refuses the second hold, a TTL index clears abandoned holds, and one transaction covers the booking, the payment ledger entry and removing the hold.</p>
<p>A telemedicine marketplace lives or dies on one guarantee: when a patient taps "book," that slot is theirs. If two patients can end up holding the same doctor's slot at the same time, the product doesn't work — a doctor shows up to a double-booked consultation, or a patient pays for a slot that's already gone.</p>
<p>The obvious first approach is to check-then-write in application code: read the slot, confirm it's free, then create the booking. That works until two requests arrive close enough together that both read "free" before either writes. Under real traffic, "close enough together" happens more often than people expect, and a race condition that only shows up under concurrent load is one of the worst kinds of bug to chase — it won't reproduce in a manual test, and it won't show up in a demo.</p>
<h2>Push the constraint into the database</h2>
<p>DocBee doesn't try to prevent the race in application code at all. When a patient picks a slot, the API creates a hold document with a MongoDB unique compound index on <code>doctor</code>, <code>date</code>, and <code>slot</code>. If a second, simultaneous hold attempt targets that exact combination, the database rejects the write outright — it fails at the storage layer, not inside application logic that has to remember to check correctly every single time.</p>
<p>This is a meaningfully different guarantee than an application-level check. A unique index is enforced by the database itself, on every write, regardless of which code path created it, how many API instances are running, or whether a future engineer forgets to add the check to a new endpoint. The invariant lives in the schema, not in someone's memory.</p>
<h2>Cleaning up abandoned holds without a cron job</h2>
<p>Locking a slot the instant a patient starts checkout raises an obvious follow-up problem: what happens if they abandon the flow? The slot can't stay locked forever.</p>
<p>Rather than run a scheduled job to sweep for stale holds, the hold document carries a MongoDB TTL index. The document expires and is removed automatically once its window passes — no cleanup process to write, deploy, monitor, or forget to run. The slot frees itself.</p>
<h2>Making the booking atomic, and making it visible</h2>
<p>Confirming a booking touches more than one piece of state: the booking record itself, a payment ledger entry, and the removal of the hold. All three happen inside a single MongoDB transaction, so there's no window where a booking exists without its payment record, or a hold lingers after a booking is confirmed.</p>
<p>The last piece is making the result visible immediately to everyone else looking at that doctor's calendar. Once the transaction commits, Socket.IO broadcasts the update, so a slot disappears from other patients' screens the instant it's taken — instead of them finding out only when their own booking attempt fails against a database constraint they never saw coming.</p>
<h2>The general lesson</h2>
<p>Application code is a bad place to enforce an invariant that absolutely cannot be violated. Database constraints (unique indexes, TTL indexes, transactions) exist precisely because they're enforced consistently, under concurrency, regardless of which code path or how many server instances are involved. Where a business rule is truly non-negotiable — "this slot has exactly one owner" — push it as close to the data as the database will let you.</p>
<hr />
<p><em>I'm Nazmul Khalid, a lead software engineer. I build multi-tenant SaaS products and MVPs for founders. The full DocBee case study is <a href="https://nazmulkhalid.com/work/docbee">here</a>, and more write-ups like this one are on <a href="https://nazmulkhalid.com/ideas">my site</a>.</em></p>
]]></content:encoded></item></channel></rss>