DEV Community

Cover image for From Backend to Breach-Aware Part 0: The Router That Was Built
Keelen Carrera
Keelen Carrera

Posted on

From Backend to Breach-Aware Part 0: The Router That Was Built

Dine Flow was a six-person cohort project, a restaurant inventory and order-management platform, built in about six weeks. Six people means six sets of hands in the codebase, and I want to be specific about which hands built what, rather than let a team project blur into a vague personal credit. My piece was the Express/API backend: the routers and the backend logic for stock, menu items, and orders. The strongest, most fully-owned piece of that work was the stock router, so that's what this is actually about.

What the stock router does

The stock router (Express + TypeScript, backed by Prisma/PostgreSQL) manages the inventory that everything else in the app depends on; every ingredient a kitchen has on hand, tied to expiration dates, suppliers, and the orders that consume it.

GET / returns the full ingredient/stock list with nested details — everything a kitchen manager needs to see current inventory at a glance. GET /: id returns a single stock item plus its associated ingredient name. POST / is where the real logic lives: it creates a new stock order and calculates an expiration date from the ingredient's shelf life, then branches depending on whether that ingredient has a supplier API URL on file. If it doesn't, the route creates the stock record and a matching expense record directly. That branch was part of my work. If it does, the route fires an automated reorder request to the supplier's API instead.

Inventory doesn't just sit static, either. Placing an order through the orders router automatically decrements the relevant stock, and stock can also be adjusted manually outside the order flow through the same router.

A bug that changed how I read "it's fixed"

Early on, the GET /: id endpoint had a bug that's stuck with me since: requesting a single stock item was returning the entire stock table instead of the one item asked for. The response wasn't actually an error; it was a 200 with data in it, which is exactly the kind of result that's easy to glance at and assume is fine. Tracing it back, the route wasn't actually referencing the ID variable it should have been keying off of; once I mapped it to the correct field (Prisma's findUnique against the actual stock ID), it returned exactly what was requested. It's a small bug, but it's the first time I really internalized that a response coming back successfully and a response coming back correct are two different claims, and only one of them is worth trusting without a second look. That instinct has followed me directly into the security-focused work I do now.

Why PUT creates a new row instead of updating one

The one design decision in this router: the PUT /:ingredientId doesn't update an existing stock record; instead, it deliberately creates a new one. Every delivery is its own batch with its own expiration date, and collapsing multiple batches into a single mutable row would mean losing the ability to track which batch is aging out first. A restaurant that can't tell its oldest chicken from its newest chicken has a food-safety problem, not just a data-modeling one. Treating each delivery as an immutable, append-only record was the right call for that reason, even though "just update the row" would have been the simpler-looking choice.

What I verified, and what I didn't

Testing here was manual, hitting endpoints through VS Code's Thunder Client and watching browser console output during integration testing with the frontend. That's a full gap, and it's narrower on purpose: this piece is an honest account of the backend logic that was written.

One more honest note, since the whole point of this post is not trusting a clean-looking response: the fix I described above only covers the wrong-ID bug. GET /:id still has a commented-out 404 check as of today. Request an ID that doesn't exist, and it returns a 200 with null data instead of an error. Same failure shape as the bug this post is about; it is still sitting there to be fixed later.

Why I went back

That GET /: id bug is the actual reason this became a series instead of a single post. A clean 200 hiding the wrong data is a very specific kind of lie, not necessarily an error. It's a result that looks fine until you check what's actually in it. Months later, I went back into this exact codebase and asked a different version of the same question: "Is this actually secure?" and "If I were to place scanning methods throughout the development process, what would it return?" A scanner reporting zero findings is the same shape of claim as an endpoint returning a 200, technically true, and not automatically the same thing as correct, or safe. That's where this series actually goes next: I wired dependency, static-analysis, and secrets scanning into this app's CI pipeline, then caught the pipeline itself lying to me differently. That will be Part 1.


Dine Flow: github.com/allaboutmike/smart-kitchen-mgmt (original team repo). My fork, with later scan/remediation work: github.com/Keelen-Carrera/smart-kitchen-mgmt.

Top comments (0)