<?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: siyadhkc</title>
    <description>The latest articles on DEV Community by siyadhkc (@siyadhkc).</description>
    <link>https://dev.to/siyadhkc</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3868439%2F5c0b2e2e-0a7c-406f-9d99-1d810624972c.png</url>
      <title>DEV Community: siyadhkc</title>
      <link>https://dev.to/siyadhkc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/siyadhkc"/>
    <language>en</language>
    <item>
      <title>I built a multi-tenant food delivery platform alone. Here's what nobody tells you about that.</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Sun, 17 May 2026 16:40:10 +0000</pubDate>
      <link>https://dev.to/siyadhkc/i-built-a-multi-tenant-food-delivery-platform-alone-heres-what-nobody-tells-you-about-that-104c</link>
      <guid>https://dev.to/siyadhkc/i-built-a-multi-tenant-food-delivery-platform-alone-heres-what-nobody-tells-you-about-that-104c</guid>
      <description>&lt;p&gt;Four user roles. One payment gateway that lied to me. Three rewrites of the same feature. And a GPS bug that nearly flooded my database. This is the real story behind Savor.&lt;/p&gt;




&lt;h2&gt;
  
  
  I was tired of building things that felt fake
&lt;/h2&gt;

&lt;p&gt;At some point every developer hits a wall with tutorial projects. You've built the weather app. You've done the blog CRUD. You've cloned Twitter in a weekend. And none of it actually &lt;em&gt;teaches&lt;/em&gt; you what it feels like when four different types of users are all touching the same data at the same time, and you have to keep their worlds completely separate.&lt;/p&gt;

&lt;p&gt;I wanted something that would break me a little. Something with real business logic, real money, real state synchronization problems. Food delivery checked every single box.&lt;/p&gt;

&lt;p&gt;Customers order food. Restaurants fulfill it. Delivery agents pick it up. Admins oversee everything. Four completely different actors, all interacting with overlapping data, all in real time. That's not a tutorial problem — that's a systems problem. I called it &lt;strong&gt;Savor&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Building this was the first time I genuinely didn't know if something would work until I ran it. That was terrifying and that was the point."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  These aren't exciting choices. They're correct ones.
&lt;/h2&gt;

&lt;p&gt;Django REST Framework on the backend. React 19 + Vite on the frontend. PostgreSQL. I didn't pick these to be cool. I picked them because the problem needed a mature ORM, ACID-compliant transactions, and a frontend that wouldn't fight me on state management.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;th&gt;Why it mattered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Django 5.1 + DRF&lt;/td&gt;
&lt;td&gt;Atomic transactions, battle-tested ORM, signals for cross-model side effects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React 19 + Vite&lt;/td&gt;
&lt;td&gt;Fast HMR, modern concurrent features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;SimpleJWT + rotation&lt;/td&gt;
&lt;td&gt;Silent 60-min access token rotation, 7-day refresh — no mid-session logouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;ACID compliance. Nested financial data cannot partially save.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Razorpay&lt;/td&gt;
&lt;td&gt;Localized processing, webhook verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Media&lt;/td&gt;
&lt;td&gt;Cloudinary CDN&lt;/td&gt;
&lt;td&gt;Edge delivery. Serving images from Django would be career-ending performance-wise.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS 4.0&lt;/td&gt;
&lt;td&gt;Zero runtime. Design tokens kept four portals visually coherent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animations&lt;/td&gt;
&lt;td&gt;Framer Motion&lt;/td&gt;
&lt;td&gt;Micro-interactions. A premium feel isn't just design — it's movement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mapping&lt;/td&gt;
&lt;td&gt;Leaflet.js&lt;/td&gt;
&lt;td&gt;Live GPS plotting on the customer tracking view&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one decision I'd revisit: React Context API over Zustand. Context was fine for the scope but I started feeling prop drilling pain by week four. If you're building something this complex, just start with Zustand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Each portal was its own distinct problem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The customer app&lt;/strong&gt; was where I started because it's the most tangible — you can see it, click it, feel it. The discovery architecture has two layers: global cuisines (Arabian, South Indian, Desserts) and restaurant-internal categories (Must Try, Specials). Getting that hierarchy right in the serializers took four iterations. The cart was where I first hit real business logic — you can't add items from two different restaurants to the same cart. I built persistent local state that checks restaurant ID on every cart mutation. Sounds simple. It wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The restaurant partner portal&lt;/strong&gt; was the one I underestimated the most. I assumed it'd be a dashboard with some CRUD. What I got was the most complex portal in the system. Restaurant owners are power users. The order lifecycle — Pending → Accept → Preparing → Ready for Delivery — cascades into downstream state changes across three other portals simultaneously. I used Django signals for this. Required very careful thinking about idempotency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The delivery agent interface&lt;/strong&gt; is the one I'm most proud of technically. Geo-fencing logic is implemented at the queryset manager level, not the view level. A Kochi agent cannot see a Mumbai dispatch — and this isn't a frontend filter someone can bypass with a crafted request. The data simply doesn't exist in the response. Getting the earnings calculation to be atomic and accurate on the &lt;code&gt;Delivered&lt;/code&gt; status transition took three rewrites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The admin console&lt;/strong&gt; could have been an afterthought. I didn't let it be. With 3,000+ menu items seeded from the Kerala restaurant dataset, server-side pagination was non-negotiable. I implemented cursor-based pagination — offset pagination on PostgreSQL becomes unusably slow past 1,000 rows.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faci92qhdruwgwaytbbid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faci92qhdruwgwaytbbid.png" alt="website" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Live demo: &lt;a href="https://food-delivery-seven-bice.vercel.app" rel="noopener noreferrer"&gt;food-delivery-seven-bice.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The struggles nobody writes about in project writeups
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;01 — Multi-tenancy took three complete rewrites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first version filtered at the serializer level. That works until a restaurant owner in tenant A briefly sees an order from tenant B during a race condition. The second version moved filters to the view layer — better, still bypassable. The third version put tenant scoping into the queryset manager. That's where it needed to be from the start. Two full days of debugging a bug that shouldn't have existed if I'd thought it through properly the first time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;02 — Razorpay webhook verification in development is a trap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Webhooks need a publicly accessible endpoint. In development, you don't have one. I spent a full day debugging a signature verification failure that turned out to be a timezone mismatch in how I was constructing the HMAC payload. I ended up building a local webhook simulator specifically for dev testing, stripped out before production. This is not in any Razorpay tutorial I found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;03 — GPS telemetry nearly destroyed my database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Early testing: delivery agents broadcast coordinates on every geolocation API event. The browser's &lt;code&gt;watchPosition&lt;/code&gt; fires multiple times per second. At 10 concurrent test agents, that was hundreds of database writes per minute for a field that only needs to update every few seconds. I added a frontend debounce and a backend timestamp guard that rejects updates more frequent than 3 seconds. The database survived. My nerves less so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;04 — CSS across four portals with four different visual languages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The customer app is warm and premium. The delivery interface is utilitarian and fast. The admin console is dense and data-heavy. The partner portal is somewhere between all three. Tailwind's design token system helped, but by week three the tokens were drifting in ways that required a full audit to fix. CSS architecture is genuinely hard at scale and nobody talks about the multi-portal version of this problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;05 — The cart constraint bug that appeared in production and nowhere else&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cart state was using localStorage with a restaurant ID key. In production, the Vercel edge occasionally delivered a cached app version mid-session with a stale cart key. The constraint check passed because the key comparison was against an old restaurant ID. Never reproduced locally. Fixed by adding a session-scoped cart version hash alongside the restaurant ID. The worst kind of bug: requires production traffic to surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one decision that saved the whole system
&lt;/h2&gt;

&lt;p&gt;Early on I had a choice: enforce multi-tenancy at the UI layer or the data layer. The UI layer is easier — you just filter what you show. The data layer is harder — you modify how querysets are constructed at the model level so filtered-out data is never even fetched.&lt;/p&gt;

&lt;p&gt;I chose the data layer. Every restaurant-scoped query goes through a custom queryset manager that injects the tenant ID before the SQL hits PostgreSQL. Bypassing tenant isolation requires compromising the Django application layer itself — not just crafting a request with different parameters.&lt;/p&gt;

&lt;p&gt;For financial data, I used &lt;code&gt;@transaction.atomic&lt;/code&gt; everywhere that touched nested profile updates. A restaurant's payout configuration and platform commission rate update in a single atomic operation. Either both succeed or neither does. This is not optional when you're dealing with money.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The architecture that survives isn't the one that looks cleanest in a diagram. It's the one that fails gracefully when things go wrong — and they will go wrong."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  You only understand failure modes by creating them
&lt;/h2&gt;

&lt;p&gt;You can read about multi-tenancy. You can read about JWT rotation. You can understand atomic transactions in the abstract. But there's a kind of understanding you only get from tracing a data leakage bug through four layers of abstraction at 11pm, finding the one queryset that forgot to filter by &lt;code&gt;restaurant_id&lt;/code&gt;, and sitting with the fact that your first two architectural decisions were wrong.&lt;/p&gt;

&lt;p&gt;Building Savor taught me that system design isn't about choosing the right technologies. It's about understanding the failure modes of every interface between them. The Razorpay integration fails if my webhook handler is slow. GPS tracking floods my database if the frontend isn't debounced. Multi-tenant isolation breaks if I filter at the wrong layer. None of these are things you learn by reading. You learn them by breaking them, badly, and having to fix it.&lt;/p&gt;

&lt;p&gt;If you're in the middle of a solo project right now and it feels like the complexity is winning — it probably is, temporarily. That's what week three feels like. The tangle is the work. Push through it.&lt;/p&gt;




&lt;p&gt;Live demo: &lt;a href="https://food-delivery-seven-bice.vercel.app" rel="noopener noreferrer"&gt;food-delivery-seven-bice.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Backend API: &lt;a href="https://food-delivery-backend-c4pe.onrender.com/api" rel="noopener noreferrer"&gt;food-delivery-backend-c4pe.onrender.com/api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github: &lt;a href="https://github.com/siyadhkc/savor" rel="noopener noreferrer"&gt;food-delivery-github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Postman collection at &lt;code&gt;backend/postman.json&lt;/code&gt; — all auth rotation, order status, and multi-tenant profile endpoints pre-configured.&lt;/p&gt;




</description>
      <category>django</category>
      <category>react</category>
      <category>fullstack</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a JSON CLI tool with Bun and TypeScript — here's what actually happened</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Mon, 11 May 2026 01:31:57 +0000</pubDate>
      <link>https://dev.to/siyadhkc/i-built-a-json-cli-tool-with-bun-and-typescript-heres-what-actually-happened-28ek</link>
      <guid>https://dev.to/siyadhkc/i-built-a-json-cli-tool-with-bun-and-typescript-heres-what-actually-happened-28ek</guid>
      <description>&lt;p&gt;I wanted a tool that could flatten JSON, filter it, colorize output, and fetch from a URL — all from&lt;br&gt;
the terminal. Something between &lt;code&gt;jq&lt;/code&gt; and &lt;code&gt;gron&lt;/code&gt; but with a cleaner, more intuitive API. So I built&lt;br&gt;
&lt;code&gt;jray&lt;/code&gt;. This is the honest post-mortem of how it went.&lt;/p&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/siyadhkc/jray" rel="noopener noreferrer"&gt;github.com/siyadhkc/jray&lt;/a&gt;&lt;br&gt;
Website:&lt;a href="https://siyadhkc.github.io/Jray" rel="noopener noreferrer"&gt;siyadhkc.github.io/Jray&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Every time I'm debugging an API response or poking around some nested config file, I reach for &lt;code&gt;jq&lt;/code&gt;.&lt;br&gt;
And every time, I spend the first five minutes googling the syntax again. &lt;code&gt;jq&lt;/code&gt; is powerful — genuinely&lt;br&gt;
— but it's its own language, and unless you're writing complex transformations daily, that overhead&lt;br&gt;
adds up.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gron&lt;/code&gt; is the other tool I kept coming back to. It flattens JSON into greppable dot-notation, which&lt;br&gt;
is exactly what you want when you're trying to find where a deeply nested key lives. But &lt;code&gt;gron&lt;/code&gt; is&lt;br&gt;
read-only. You can't filter and get structured output back. You can't fetch from a URL. The color&lt;br&gt;
situation is nonexistent.&lt;/p&gt;

&lt;p&gt;So I scoped out what I actually needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flatten JSON to dot-notation paths (&lt;code&gt;a.b.c = value&lt;/code&gt;) so I can see structure at a glance&lt;/li&gt;
&lt;li&gt;Filter by key pattern without learning a query language&lt;/li&gt;
&lt;li&gt;Fetch directly from a URL so I don't have to &lt;code&gt;curl | jray&lt;/code&gt; every time&lt;/li&gt;
&lt;li&gt;Color output by default on TTY&lt;/li&gt;
&lt;li&gt;Fast enough that it doesn't feel like overhead when I'm in the middle of something&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bun was the obvious runtime choice. Fast cold start, native TypeScript support, a built-in test&lt;br&gt;
runner, and &lt;code&gt;bun build --compile&lt;/code&gt; that bundles everything into a single self-contained binary. No&lt;br&gt;
Node.js version headaches, no separate install step for the runtime.&lt;/p&gt;


&lt;h2&gt;
  
  
  How it's structured
&lt;/h2&gt;

&lt;p&gt;I kept the architecture intentionally flat. No frameworks, minimal dependencies. The core logic is&lt;br&gt;
split into focused modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flatten.ts&lt;/code&gt; — recursive flatten and unflatten. Takes a nested object and produces a map of
dot-notation paths to primitive values. Unflatten goes the other direction.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filter.ts&lt;/code&gt; — glob-style pattern matching on the flattened keys. &lt;code&gt;user.*&lt;/code&gt; gives you everything
under &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;**.id&lt;/code&gt; matches any &lt;code&gt;id&lt;/code&gt; at any depth.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;color.ts&lt;/code&gt; — ANSI color output. Keys in one color, values type-aware (strings, numbers, booleans
each get their own).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetch.ts&lt;/code&gt; — thin wrapper around &lt;code&gt;Bun.fetch&lt;/code&gt; that handles the request, checks content type, and
pipes the response into the flatten pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cli.ts&lt;/code&gt; — argument parsing and wiring. Reads from stdin, a file path, or a URL depending on what
flags you pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing compiles down to a single binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun build ./src/cli.ts &lt;span class="nt"&gt;--compile&lt;/span&gt; &lt;span class="nt"&gt;--outfile&lt;/span&gt; jray
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No runtime dependency on Bun after that. You ship the binary, it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problems I actually ran into
&lt;/h2&gt;

&lt;h3&gt;
  
  
  npm name conflict
&lt;/h3&gt;

&lt;p&gt;First thing I did after writing the initial working version was try to publish as &lt;code&gt;jray&lt;/code&gt;. Already&lt;br&gt;
taken — and not squatted. An actual published package with actual users. So I had to switch to a&lt;br&gt;
scoped package: &lt;code&gt;@siyadkc/jray&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That sounds like a small change but it cascades. The README, the install command in all the&lt;br&gt;
examples, the binary name in &lt;code&gt;package.json&lt;/code&gt;, the GitHub Actions publish step, the Dev.to article&lt;br&gt;
draft I'd already half-written — all of it needed updating. It also changes how users install it&lt;br&gt;
slightly, since scoped packages require the &lt;code&gt;@&lt;/code&gt; prefix. Not a dealbreaker, but check npm before you&lt;br&gt;
name your CLI.&lt;/p&gt;
&lt;h3&gt;
  
  
  Package size bloat
&lt;/h3&gt;

&lt;p&gt;My first publish was heavier than it needed to be. Bun's compiled binary includes the runtime,&lt;br&gt;
which is expected and fine. But the npm package itself had no &lt;code&gt;files&lt;/code&gt; field, so it was packaging&lt;br&gt;
everything — source maps, test files, the &lt;code&gt;node_modules&lt;/code&gt; from dev, local configs. None of that&lt;br&gt;
belongs in a published package.&lt;/p&gt;

&lt;p&gt;Fix was straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dist/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"README.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single field cut the published package weight significantly. Always define &lt;code&gt;files&lt;/code&gt;. The npm&lt;br&gt;
registry doesn't know what's dev and what's production unless you tell it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Windows compatibility
&lt;/h3&gt;

&lt;p&gt;I had a &lt;code&gt;postinstall&lt;/code&gt; script that ran &lt;code&gt;chmod +x&lt;/code&gt; on the binary. Works perfectly on Linux and macOS.&lt;br&gt;
On Windows it throws and the install fails. I wrapped it in a try-catch and updated the docs to note&lt;br&gt;
that Windows users should either run the binary path directly or use WSL. Not the cleanest solution,&lt;br&gt;
but honest about the platform situation. Full Windows support is on the roadmap.&lt;/p&gt;
&lt;h3&gt;
  
  
  GitHub Actions for CI and publishing
&lt;/h3&gt;

&lt;p&gt;The pipeline itself wasn't complicated — install Bun, run tests, publish to npm on a version tag.&lt;br&gt;
But there's one specific thing that tripped me up: not using &lt;code&gt;--frozen-lockfile&lt;/code&gt; in CI.&lt;/p&gt;

&lt;p&gt;Without it, &lt;code&gt;bun install&lt;/code&gt; in CI can silently update the lockfile, which means your CI might be&lt;br&gt;
testing against different dependency versions than what you developed against. Always use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oven-sh/setup-bun@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bun-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bun install --frozen-lockfile&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bun test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publishing to npm from Actions requires setting &lt;code&gt;NPM_TOKEN&lt;/code&gt; as a repository secret and passing it&lt;br&gt;
through to the publish step. Once that's wired, releases are a tag push away.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
bun add &lt;span class="nt"&gt;-g&lt;/span&gt; @siyadkc/jray

&lt;span class="c"&gt;# Flatten JSON from stdin&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"user":{"name":"Eliot","age":24}}'&lt;/span&gt; | jray
&lt;span class="c"&gt;# user.name = "Eliot"&lt;/span&gt;
&lt;span class="c"&gt;# user.age = 24&lt;/span&gt;

&lt;span class="c"&gt;# Flatten a local file&lt;/span&gt;
jray data.json

&lt;span class="c"&gt;# Filter by key pattern&lt;/span&gt;
jray &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"user.*"&lt;/span&gt; data.json
&lt;span class="c"&gt;# user.name = "Eliot"&lt;/span&gt;
&lt;span class="c"&gt;# user.age = 24&lt;/span&gt;

&lt;span class="c"&gt;# Fetch from a URL and flatten&lt;/span&gt;
jray &lt;span class="nt"&gt;--fetch&lt;/span&gt; https://api.example.com/users/1

&lt;span class="c"&gt;# Disable color (for piping into other tools)&lt;/span&gt;
jray &lt;span class="nt"&gt;--no-color&lt;/span&gt; data.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filter uses glob-style matching against the full dot-path of each key. So &lt;code&gt;user.*&lt;/code&gt; matches one&lt;br&gt;
level under &lt;code&gt;user&lt;/code&gt;, while &lt;code&gt;**.id&lt;/code&gt; would match any &lt;code&gt;id&lt;/code&gt; at any depth in the document. It's&lt;br&gt;
intentionally simpler than jq's filter syntax — the goal is something you can remember without&lt;br&gt;
looking it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it sits vs the competition
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gron&lt;/code&gt; is the closest thing and the most direct comparison. It also flattens to dot-notation and&lt;br&gt;
it's what made me realize there was a gap to fill. But &lt;code&gt;gron&lt;/code&gt; has no color output, no URL fetching,&lt;br&gt;
and no NDJSON streaming support. &lt;code&gt;jray&lt;/code&gt; has all three.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; is in a different category. It's a transformation tool — you write expressions, you reshape&lt;br&gt;
data, you do complex operations. &lt;code&gt;jray&lt;/code&gt; is an inspection tool. You throw data at it and you&lt;br&gt;
understand the structure immediately. They're complementary more than they're competing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fx&lt;/code&gt; is another one that comes up. Interactive, powerful, but it's a TUI. If you're in a script or&lt;br&gt;
piping between tools, you don't want a TUI. &lt;code&gt;jray&lt;/code&gt; is designed to be pipeline-friendly from the&lt;br&gt;
start.&lt;/p&gt;

&lt;p&gt;The longer-term vision is broader than just JSON. Supporting YAML and TOML with the same flatten&lt;br&gt;
and filter interface makes it a universal structured-data CLI. There's also TOON (Token Oriented&lt;br&gt;
Object Notation), which is an emerging format that's genuinely useful for LLM developer workflows&lt;br&gt;
since it's optimized for tokenization. That's a real differentiator if the format gets traction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The launch
&lt;/h2&gt;

&lt;p&gt;Published to npm, wrote this article, posted on X . Also shared in the Bun&lt;br&gt;
Discord since the community there is small enough that people actually read it.&lt;/p&gt;

&lt;p&gt;The GitHub Pages site is live. It's getting redesigned right now — moving toward a flatter, more&lt;br&gt;
minimal aesthetic with an interactive playground section where you can paste JSON directly in the&lt;br&gt;
browser and see the flatten and filter output in real time. No install required to try it. That&lt;br&gt;
single feature will probably do more for adoption than any amount of posting.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;I'd define the &lt;code&gt;files&lt;/code&gt; field before the first publish, not after. I'd also check npm for name&lt;br&gt;
conflicts before I was emotionally attached to the name. And I'd set up the CI pipeline before&lt;br&gt;
the first push to &lt;code&gt;main&lt;/code&gt; so I wasn't debugging GitHub Actions at the same time I was trying to&lt;br&gt;
ship.&lt;/p&gt;

&lt;p&gt;The architecture decisions held up fine. Bun was the right call. The modular structure meant I&lt;br&gt;
could add &lt;code&gt;fetch.ts&lt;/code&gt; and &lt;code&gt;color.ts&lt;/code&gt; without touching the core flatten logic. That's about as much&lt;br&gt;
as you can ask from a first project's structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun add &lt;span class="nt"&gt;-g&lt;/span&gt; @siyadkc/jray
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/siyadhkc/jray" rel="noopener noreferrer"&gt;github.com/siyadhkc/jray&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're working on something similar with Bun, or you have opinions on the filter API design,&lt;br&gt;
drop a comment. Always curious how other people are solving the same problems.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is my first article writing on this platform so if i made any narration mistake or anything feel free to tell me, i will improve my writing skill and the way of telling this&lt;br&gt;
-- let's get connected --&lt;/em&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>From Hardware to Hacking: How Understanding Systems Changed My Approach to Security</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Wed, 15 Apr 2026 15:56:13 +0000</pubDate>
      <link>https://dev.to/siyadhkc/from-hardware-to-hacking-how-understanding-systems-changed-my-approach-to-security-4dh4</link>
      <guid>https://dev.to/siyadhkc/from-hardware-to-hacking-how-understanding-systems-changed-my-approach-to-security-4dh4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I didn’t start my journey in cybersecurity. I started with hardware and networking, learning how systems actually function at a low level — how machines communicate, how configurations impact behavior, and how failures happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift to Development
&lt;/h2&gt;

&lt;p&gt;Later, I moved into development, working with Python and full-stack technologies. Building applications helped me understand how software is structured, but it also revealed something important:&lt;/p&gt;

&lt;p&gt;Most applications are not built with security as a priority.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point
&lt;/h2&gt;

&lt;p&gt;This realization pushed me toward cybersecurity, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web application security&lt;/li&gt;
&lt;li&gt;API security&lt;/li&gt;
&lt;li&gt;Cloud platforms (AWS, Azure, GCP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of just building systems, I began analyzing how they can be exploited.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Different Way of Thinking
&lt;/h2&gt;

&lt;p&gt;The biggest shift was in mindset:&lt;/p&gt;

&lt;p&gt;From:&lt;br&gt;
“How do I build this system?”&lt;/p&gt;

&lt;p&gt;To:&lt;br&gt;
“How can this system fail or be attacked?”&lt;/p&gt;

&lt;p&gt;This dual perspective — builder and attacker — is critical in modern cybersecurity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;I’ll be sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Practical security insights&lt;/li&gt;
&lt;li&gt;Real-world learning experiences&lt;/li&gt;
&lt;li&gt;Breakdowns of vulnerabilities and misconfigurations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in understanding systems deeply — not just building them, but also testing their limits — follow along.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>career</category>
    </item>
    <item>
      <title>I built a modern alternative to gron using Bun and TypeScript</title>
      <dc:creator>siyadhkc</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:16:36 +0000</pubDate>
      <link>https://dev.to/siyadhkc/i-built-a-modern-alternative-to-gron-using-bun-and-typescript-3358</link>
      <guid>https://dev.to/siyadhkc/i-built-a-modern-alternative-to-gron-using-bun-and-typescript-3358</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Working with JSON in the terminal is painful. &lt;code&gt;jq&lt;/code&gt; is powerful &lt;br&gt;
but requires learning a whole query language. &lt;code&gt;grep&lt;/code&gt; works but &lt;br&gt;
matches values too — not just paths.&lt;/p&gt;

&lt;p&gt;For example if I search for "name" in my JSON:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grep matches &lt;code&gt;json.user.name&lt;/code&gt; ✅ (correct)&lt;/li&gt;
&lt;li&gt;grep also matches &lt;code&gt;json.bio = "my name is Alice"&lt;/code&gt; ❌ (wrong)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;strong&gt;jray&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What jray does
&lt;/h2&gt;

&lt;p&gt;jray flattens any JSON into one line per value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;jray data.json
json.organization.name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Acme Corporation"&lt;/span&gt;
json.users[0].name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Alice Pemberton"&lt;/span&gt;
json.users[0].role &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
json.users[0].active &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;json.billing.plan &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"enterprise"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can grep it, awk it, pipe it — using tools you already know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four commands that cover everything
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Flatten with color output&lt;/span&gt;
jray data.json

&lt;span class="c"&gt;# Fetch a URL directly — no curl needed&lt;/span&gt;
jray https://jsonplaceholder.typicode.com/users/1

&lt;span class="c"&gt;# Filter by path only (never matches values)&lt;/span&gt;
jray data.json &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"billing"&lt;/span&gt;

&lt;span class="c"&gt;# Extract any subtree as clean JSON&lt;/span&gt;
jray data.json &lt;span class="nt"&gt;--select&lt;/span&gt; &lt;span class="s2"&gt;"billing"&lt;/span&gt;

&lt;span class="c"&gt;# Print just the values&lt;/span&gt;
jray data.json &lt;span class="nt"&gt;--values&lt;/span&gt;

&lt;span class="c"&gt;# Reconstruct original JSON&lt;/span&gt;
jray data.json | jray &lt;span class="nt"&gt;--ungron&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The comparison with existing tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;jray&lt;/th&gt;
&lt;th&gt;jq&lt;/th&gt;
&lt;th&gt;gron&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flatten to lines&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconstruct JSON&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter by path&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extract subtree&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fetch from URLs&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color output&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No query language&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript / modern&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I learned building it
&lt;/h2&gt;

&lt;p&gt;This was my first open source CLI tool. Key lessons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Separate your CLI from your logic&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;cli.ts&lt;/code&gt; handles only I/O — reading files, printing output.&lt;br&gt;
&lt;code&gt;flatten.ts&lt;/code&gt;, &lt;code&gt;filter.ts&lt;/code&gt; etc. know nothing about terminals.&lt;br&gt;
This makes everything testable in isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use &lt;code&gt;unknown&lt;/code&gt; not &lt;code&gt;any&lt;/code&gt; in TypeScript&lt;/strong&gt;&lt;br&gt;
JSON is dynamic but that doesn't mean &lt;code&gt;any&lt;/code&gt;. Using &lt;code&gt;unknown&lt;/code&gt; &lt;br&gt;
forces you to check types before using values — much safer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Recursive functions are perfect for JSON&lt;/strong&gt;&lt;br&gt;
JSON is a tree. Walking a tree with recursion is the &lt;br&gt;
cleanest pattern — each call handles one node and &lt;br&gt;
delegates children back to itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Bun is genuinely fast and simple&lt;/strong&gt;&lt;br&gt;
Native TypeScript, built-in fetch, fast startup. &lt;br&gt;
For a CLI tool, Bun is a great choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The README matters as much as the code&lt;/strong&gt;&lt;br&gt;
Nobody uses a tool they can't understand in 30 seconds.&lt;br&gt;
Writing clear examples with real output is as important &lt;br&gt;
as writing the code itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install and try it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @siyadkc/jray
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jray https://jsonplaceholder.typicode.com/users/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/siyadhkc/Jray" rel="noopener noreferrer"&gt;github.com/siyadhkc/Jray&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;for more info Website: &lt;a href="https://siyadhkc.github.io/Jray/" rel="noopener noreferrer"&gt;https://siyadhkc.github.io/Jray/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This is my first open source tool. Feedback on the code &lt;br&gt;
quality is very welcome — especially from experienced &lt;br&gt;
TypeScript developers!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>bunjs</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
