<?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: Ramananda Panda</title>
    <description>The latest articles on DEV Community by Ramananda Panda (@rp01).</description>
    <link>https://dev.to/rp01</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%2F2853941%2Fedd9a1f5-d8ee-420a-8278-79fc631f4ad1.jpeg</url>
      <title>DEV Community: Ramananda Panda</title>
      <link>https://dev.to/rp01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rp01"/>
    <language>en</language>
    <item>
      <title>I put my API collection in a folder of curl scripts, and it finally behaved</title>
      <dc:creator>Ramananda Panda</dc:creator>
      <pubDate>Mon, 05 Oct 2026 07:59:46 +0000</pubDate>
      <link>https://dev.to/rp01/i-put-my-api-collection-in-a-folder-of-curl-scripts-and-it-finally-behaved-1fb7</link>
      <guid>https://dev.to/rp01/i-put-my-api-collection-in-a-folder-of-curl-scripts-and-it-finally-behaved-1fb7</guid>
      <description>&lt;h2&gt;
  
  
  Chapter 1: The JSON file that ate Friday
&lt;/h2&gt;

&lt;p&gt;It was a Friday evening. The kind where you push one last fix and promise yourself you'll close the laptop in five minutes.&lt;/p&gt;

&lt;p&gt;Then a teammate pinged me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, can you review my change to the API collection?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I opened the pull request. It was a single file, &lt;code&gt;Petstore.postman_collection.json&lt;/code&gt;. The diff was 1,400 lines. Somewhere in that wall of &lt;code&gt;"id"&lt;/code&gt;, &lt;code&gt;"_postman_id"&lt;/code&gt; and re-ordered keys, one header had changed. Probably. I approved it the way you accept cookie banners: quickly, and with mild guilt.&lt;/p&gt;

&lt;p&gt;Later the CI job failed, because the CI runner needed a different tool, a different login and a different copy of the environment. My "five minutes" turned into an evening of archaeology.&lt;/p&gt;

&lt;p&gt;That night I asked a dangerous question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if an API request was just... a &lt;code&gt;curl&lt;/code&gt; command in a file?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Chapter 2: The humble &lt;code&gt;.sh&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;Every backend developer already speaks &lt;code&gt;curl&lt;/code&gt;. We paste it into Slack, into bug reports and into README files. It is the lingua franca of HTTP.&lt;/p&gt;

&lt;p&gt;So I wrote one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/pets"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked. It was readable. It diffed beautifully. But it couldn't answer the questions a test suite needs to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did it return the status I expected?&lt;/li&gt;
&lt;li&gt;Is the body actually right?&lt;/li&gt;
&lt;li&gt;How do I pass the token from the login call to the next one?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't want a new language for that. Comments, however, are free. Every shell already ignores them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 3: Comments that do real work
&lt;/h2&gt;

&lt;p&gt;Here is the same request, with a few annotations on top. This is the actual login file from the Sankh petstore example:&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="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# @name Log in&lt;/span&gt;
&lt;span class="c"&gt;# @tags smoke&lt;/span&gt;
&lt;span class="c"&gt;# @expect status 200&lt;/span&gt;
&lt;span class="c"&gt;# @expect json .token exists&lt;/span&gt;
&lt;span class="c"&gt;# @capture TOKEN=.token&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/login"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$PET_USER&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$PET_PASSWORD&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it like a sentence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@name&lt;/code&gt;&lt;/strong&gt; gives the request a human name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@tags smoke&lt;/code&gt;&lt;/strong&gt; puts it in the smoke suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@expect status 200&lt;/code&gt;&lt;/strong&gt; means anything else is a failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@expect json .token exists&lt;/code&gt;&lt;/strong&gt; checks the body with a jq expression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@capture TOKEN=.token&lt;/code&gt;&lt;/strong&gt; saves the token for every request that comes after.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here is the best part: to the shell, all of that is just comments. The file still runs on its own, with or without Sankh:&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="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; environments/dev.env&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; +a
sh auth/01-login.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No lock-in, no export button, no proprietary format. If Sankh vanished tomorrow, you'd still own a folder of perfectly good curl commands. That's my favourite feature, and I didn't even have to write code for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 4: A collection is just a folder
&lt;/h2&gt;

&lt;p&gt;Put a few of those files together and you have a collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;petstore/
  sankh.toml            # optional: name, default env, timeout
  .env.example          # what secrets you need (no values)
  .env.local            # your actual secrets (gitignored)
  environments/
    dev.env
    ci.env
  auth/
    01-login.sh
  pets/
    01-list.sh
    02-create.sh
    03-get.sh
    04-delete.sh
    05-get-deleted.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The numeric prefixes set the run order. &lt;code&gt;environments/dev.env&lt;/code&gt; holds the boring, shareable values:&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;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4010
&lt;span class="nv"&gt;PET_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the password? It goes in &lt;code&gt;.env.local&lt;/code&gt;, which is gitignored. Secrets in git are like glitter: you never fully get rid of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 5: Chaining, or "the token relay race"
&lt;/h2&gt;

&lt;p&gt;APIs are rarely one call. You log in, create something, fetch it, delete it, then check it's really gone. Each step hands something to the next, like runners passing a baton.&lt;/p&gt;

&lt;p&gt;The create step catches two batons at once, the new pet's id from the body and a request id from a response header:&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="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# @name Create pet&lt;/span&gt;
&lt;span class="c"&gt;# @description Creates a pet and stores its id for the next steps.&lt;/span&gt;
&lt;span class="c"&gt;# @tags smoke&lt;/span&gt;
&lt;span class="c"&gt;# @expect status 201&lt;/span&gt;
&lt;span class="c"&gt;# @expect json .name == "Rex"&lt;/span&gt;
&lt;span class="c"&gt;# @expect json .tags contains "good-boy"&lt;/span&gt;
&lt;span class="c"&gt;# @capture PET_ID=.id&lt;/span&gt;
&lt;span class="c"&gt;# @capture REQUEST_ID=header X-Request-Id&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/pets"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"Rex","tags":["good-boy"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next request just uses &lt;code&gt;$PET_ID&lt;/code&gt; like any shell variable:&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="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# @name Get pet&lt;/span&gt;
&lt;span class="c"&gt;# @expect status 200&lt;/span&gt;
&lt;span class="c"&gt;# @expect json .id | tostring == "$PET_ID"&lt;/span&gt;
&lt;span class="c"&gt;# @expect json .status matches "^(available|pending|sold)$"&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/pets/&lt;/span&gt;&lt;span class="nv"&gt;$PET_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because a good test suite also checks the sad paths, the last step &lt;em&gt;expects&lt;/em&gt; a 404:&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="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# @name Deleted pet is gone&lt;/span&gt;
&lt;span class="c"&gt;# @tags negative&lt;/span&gt;
&lt;span class="c"&gt;# @expect status 404&lt;/span&gt;
&lt;span class="c"&gt;# @expect json .error == "not found"&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/pets/&lt;/span&gt;&lt;span class="nv"&gt;$PET_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without any &lt;code&gt;@expect status&lt;/code&gt;, a request passes on any &lt;code&gt;2xx&lt;/code&gt;. Once you write &lt;code&gt;@expect status 404&lt;/code&gt;, a 404 is a success and a 200 is a failure. Finally, a tool that celebrates things going missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 6: Blow the conch
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Sankh&lt;/em&gt; (शंख, "shankh") is the conch shell blown to announce a beginning. Here's what blowing it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sankh trust examples/petstore
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sankh run examples/petstore
&lt;span class="go"&gt;sankh · petstore · env dev (6 requests)
✓ Log in  200  12ms  auth/01-login.sh
    captured TOKEN=***
✓ List pets  200  4ms  pets/01-list.sh
✓ Create pet  201  5ms  pets/02-create.sh
    captured PET_ID=1
    captured REQUEST_ID=req-1
✓ Get pet  200  3ms  pets/03-get.sh
✓ Delete pet  204  3ms  pets/04-delete.sh
✓ Deleted pet is gone  404  3ms  pets/05-get-deleted.sh

6 passed  41ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;TOKEN=***&lt;/code&gt;. Any variable whose name contains &lt;code&gt;TOKEN&lt;/code&gt;, &lt;code&gt;SECRET&lt;/code&gt;, &lt;code&gt;KEY&lt;/code&gt;, &lt;code&gt;PASSWORD&lt;/code&gt;, &lt;code&gt;AUTH&lt;/code&gt; or &lt;code&gt;COOKIE&lt;/code&gt; is masked everywhere: terminal output, reports and the UI. Screenshots of your terminal are now safe to post on the internet, and so is your pride.&lt;/p&gt;

&lt;p&gt;Now let's break something on purpose. Say someone renames the dog and changes the assertion to &lt;code&gt;@expect json .name == "Fluffy"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;✗ Create pet  201  5ms  pets/02-create.sh
    json .name == "Fluffy": expected .name == "Fluffy", got "Rex"
    │ {"id":1,"name":"Rex","status":"available","tags":["good-boy"]}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells you which assertion failed, what it expected, what it got, and shows the body. Captures only run when every assertion passes, so a broken step never passes a bad &lt;code&gt;PET_ID&lt;/code&gt; down the line. The run exits with code &lt;code&gt;1&lt;/code&gt;, which is exactly what CI wants to hear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 7: CI, the part where Friday is saved
&lt;/h2&gt;

&lt;p&gt;Remember the CI job that needed a different tool, login and environment? Here's the whole thing now:&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;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -fsSL https://sankh.dev/install.sh | sh&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;sankh run . --env ci --tag smoke --trust --report junit&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;PET_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PET_PASSWORD }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One binary. Secrets come from CI, because the process environment beats the env files. &lt;code&gt;--tag smoke&lt;/code&gt; runs just the smoke suite. &lt;code&gt;--report junit&lt;/code&gt; gives your CI a test report it already knows how to display.&lt;/p&gt;

&lt;p&gt;The exit codes mean something, too:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exit code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Everything passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;An assertion, a capture or a request failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Usage or configuration error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;The collection isn't trusted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And that pull request with the 1,400-line JSON diff? Now it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; # @expect status 201
 # @expect json .name == "Rex"
&lt;span class="gi"&gt;+# @expect json .status == "available"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can review that. You can even review it on your phone, in a queue, while pretending to listen to someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 8: "Wait, it runs shell scripts from a git repo?"
&lt;/h2&gt;

&lt;p&gt;Yes. And that should make you a little nervous, so Sankh is nervous on your behalf.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trust first.&lt;/strong&gt; A freshly cloned collection refuses to run until you &lt;code&gt;sankh trust&lt;/code&gt; it. In a git repository the trust is tied to the HEAD commit, so after a &lt;code&gt;git pull&lt;/code&gt; you review and trust again. Nobody sneaks a &lt;code&gt;rm -rf&lt;/code&gt; into &lt;code&gt;03-get.sh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The local UI is locked down.&lt;/strong&gt; &lt;code&gt;sankh serve&lt;/code&gt; binds to &lt;code&gt;127.0.0.1&lt;/code&gt;, rejects foreign &lt;code&gt;Host&lt;/code&gt; headers (so no DNS rebinding tricks) and blocks cross-origin calls. Any other address needs a token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No telemetry.&lt;/strong&gt; The only network requests Sankh makes are the ones in your files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chapter 9: For people who like buttons
&lt;/h2&gt;

&lt;p&gt;Not everyone wants to live in the terminal, and that's fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sankh serve examples/petstore    &lt;span class="c"&gt;# http://localhost:4747&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a small web UI with a file tree, a form editor and a raw editor, an environment picker (with a &lt;strong&gt;Manage&lt;/strong&gt; button to edit env files) and live results. The server runs the requests, not the browser, so there's no CORS drama.&lt;/p&gt;

&lt;p&gt;There's also a desktop app for macOS, Windows and Linux, which is the same UI in a native window. And a &lt;strong&gt;Scratch&lt;/strong&gt; collection for "let me just try this one call" moments, with &lt;strong&gt;Copy to…&lt;/strong&gt; for when that one call turns out to be important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 10: Bringing the old collection along
&lt;/h2&gt;

&lt;p&gt;That Postman collection from Chapter 1? It doesn't have to be left behind:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sankh import postman Petstore.postman_collection.json &lt;span class="nt"&gt;--env&lt;/span&gt; dev.postman_environment.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Folders become directories and requests become numbered &lt;code&gt;.sh&lt;/code&gt; files. &lt;code&gt;{{baseUrl}}&lt;/code&gt; becomes &lt;code&gt;${BASE_URL}&lt;/code&gt;. Common test scripts translate directly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Postman&lt;/th&gt;
&lt;th&gt;Sankh&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pm.response.to.have.status(201)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;# @expect status 201&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pm.environment.set("token", pm.response.json().token)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;# @capture TOKEN=.token&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Anything Sankh can't translate is kept as comments and listed in an import report, because Sankh doesn't run JavaScript. Secrets are never written to disk; they're listed in &lt;code&gt;.env.example&lt;/code&gt;, and &lt;code&gt;.env.local&lt;/code&gt; is created with placeholder values for you to fill in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Epilogue: How it works (it's almost embarrassing)
&lt;/h2&gt;

&lt;p&gt;Sankh is written in Rust, and the trick at its core is small. It never modifies your file. It defines a &lt;code&gt;curl&lt;/code&gt; shell function that adds &lt;code&gt;-o&lt;/code&gt;, &lt;code&gt;-D&lt;/code&gt; and &lt;code&gt;-w '%{json}'&lt;/code&gt; to record the body, headers and timing, then sources your file. When curl finishes, Sankh checks your annotations against what it recorded.&lt;/p&gt;

&lt;p&gt;That's why every curl flag you already know just works. Sankh didn't reinvent HTTP. It just started taking notes.&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;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://sankh.dev/install.sh | sh
sankh init my-api
sankh trust my-api
sankh run my-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://sankh.dev" rel="noopener noreferrer"&gt;sankh.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/sankh-dev/sankh" rel="noopener noreferrer"&gt;github.com/sankh-dev/sankh&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: MIT or Apache-2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The annotation format is young, and &lt;code&gt;@expect header&lt;/code&gt;, &lt;code&gt;@expect time&lt;/code&gt;, &lt;code&gt;@retry&lt;/code&gt; and &lt;code&gt;@depends&lt;/code&gt; are next on the list. If you try it, tell me what's missing, and what made you smile. I'll take both.&lt;/p&gt;

&lt;p&gt;Now go close that laptop. It's Friday. 🐚&lt;/p&gt;

</description>
      <category>api</category>
      <category>opensource</category>
      <category>rust</category>
      <category>testing</category>
    </item>
    <item>
      <title>I wanted GraphQL's superpower without GraphQL's personality disorder</title>
      <dc:creator>Ramananda Panda</dc:creator>
      <pubDate>Tue, 07 Jul 2026 04:57:23 +0000</pubDate>
      <link>https://dev.to/rp01/i-wanted-graphqls-superpower-without-graphqls-personality-disorder-26jl</link>
      <guid>https://dev.to/rp01/i-wanted-graphqls-superpower-without-graphqls-personality-disorder-26jl</guid>
      <description>&lt;p&gt;So I built &lt;strong&gt;MeshQL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's a small TypeScript library that lets clients say &lt;em&gt;"give me these fields, nested like this"&lt;/em&gt; - over normal REST - while you keep writing SQL (or Prisma, or Drizzle, or Kysely) like a person who still enjoys weekends.&lt;/p&gt;

&lt;p&gt;Tagline on the tin: &lt;strong&gt;Shape your API, not your codebase.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/meshql/meshql" rel="noopener noreferrer"&gt;github.com/meshql/meshql&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://docs.meshql.dev" rel="noopener noreferrer"&gt;docs.meshql.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Site: &lt;a href="https://meshql.dev" rel="noopener noreferrer"&gt;meshql.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The problem nobody admits at standup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt; is fine until every frontend wants a different &lt;code&gt;?include=&lt;/code&gt; and you accidentally ship half the database in JSON because someone asked for &lt;code&gt;user.posts.comments.author.avatar.url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GraphQL&lt;/strong&gt; fixes that - and then invoices you for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;47 resolvers&lt;/li&gt;
&lt;li&gt;N+1 archaeology&lt;/li&gt;
&lt;li&gt;Codegen that eats your types and burps &lt;code&gt;Maybe&amp;lt;T&amp;gt;&lt;/code&gt; at 2am&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I actually wanted was the &lt;strong&gt;middle&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Normal URLs. Query in headers. One handler per entity. SQL I already know how to write.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's MeshQL.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it does (without the enterprise keynote voice)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Client sends a &lt;strong&gt;field selection&lt;/strong&gt; - JSON or a little QL syntax - in &lt;code&gt;X-Mesh-Query&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;MeshQL parses it and builds a &lt;strong&gt;JoinPlan&lt;/strong&gt;: exactly the fields and joins the client asked for. No over-fetching guilt trip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your resolver&lt;/strong&gt; runs one query (SQL, Prisma &lt;code&gt;findMany&lt;/code&gt;, whatever).&lt;/li&gt;
&lt;li&gt;MeshQL &lt;strong&gt;shapes&lt;/strong&gt; flat SQL rows into nested JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No resolver per field. No dataloader yoga. No "quick sync, I just need to regenerate the schema real quick" (famous last words).&lt;/p&gt;




&lt;h2&gt;
  
  
  A tiny example
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fields&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="s2"&gt;id&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="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fields&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="s2"&gt;accessToken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tokens&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_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="na"&gt;joins&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="s2"&gt;user.tokens&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="na"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tokens.user_id = users.id&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;many&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Client query (conceptually):&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&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="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tokens"&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="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"accessToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;
&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;Your resolver gets a plan that says &lt;em&gt;only&lt;/em&gt; fetch those fields. Return flat rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;user_id&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="nx"&gt;user_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokens_accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tok_ada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MeshQL hands the client:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokens"&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="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"accessToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tok_ada"&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still REST. Still one &lt;code&gt;GET /mesh/user/1&lt;/code&gt;. Still your Express/Fastify/Hono app. The client just stops bullying your backend into bespoke DTOs.&lt;/p&gt;




&lt;h2&gt;
  
  
  "But I use Prisma and I'm emotionally attached"
&lt;/h2&gt;

&lt;p&gt;Same. We just shipped &lt;strong&gt;v0.6.0&lt;/strong&gt; with ORM adapters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You&lt;/th&gt;
&lt;th&gt;One line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prisma&lt;/td&gt;
&lt;td&gt;&lt;code&gt;withPrisma(mesh, prisma, { schema })&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;withDrizzle(mesh, db, { schema })&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kysely&lt;/td&gt;
&lt;td&gt;&lt;code&gt;withKysely(mesh, db, { schema, dialect: "postgres" })&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw SQL enjoyers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;buildSelectSql(plan, schema)&lt;/code&gt; + your pool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Catch-all resolver pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&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="c1"&gt;// plan.fields and plan.joins = only what the client asked for&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prisma example app in the repo: &lt;a href="https://github.com/meshql/meshql/tree/main/examples/express-prisma" rel="noopener noreferrer"&gt;express-prisma&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hot take: MeshQL does NOT want your database password
&lt;/h2&gt;

&lt;p&gt;This confused me at first, so I'll save you the trip:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MeshQL doesn't create or pool connections.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You create &lt;code&gt;PrismaClient&lt;/code&gt; / &lt;code&gt;pg.Pool&lt;/code&gt; / &lt;code&gt;DatabaseSync&lt;/code&gt; once at startup. You pass it into the resolver. MeshQL plans queries and shapes JSON. Connection lifecycle stays &lt;em&gt;your&lt;/em&gt; problem - which is actually what you want in production anyway.&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://docs.meshql.dev/guide/database-connections" rel="noopener noreferrer"&gt;Database connections&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's in the box
&lt;/h2&gt;

&lt;p&gt;Published on &lt;strong&gt;&lt;a href="https://jsr.io/@meshql" rel="noopener noreferrer"&gt;JSR&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;@meshql/*&lt;/code&gt;) and &lt;strong&gt;npm&lt;/strong&gt; (&lt;code&gt;meshql-*&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@meshql/core&lt;/code&gt; - parser, planner, shaper&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@meshql/postgres&lt;/code&gt; / &lt;code&gt;@meshql/sqlite&lt;/code&gt; - parameterized SQL builders&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@meshql/prisma&lt;/code&gt; / &lt;code&gt;@meshql/drizzle&lt;/code&gt; / &lt;code&gt;@meshql/kysely&lt;/code&gt; - ORM adapters (new in 0.6.0)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@meshql/http&lt;/code&gt; - Express, Fastify, Hono&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@meshql/client&lt;/code&gt; - typed client for Node + browser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@meshql/integrity&lt;/code&gt; + &lt;code&gt;@meshql/access&lt;/code&gt; - signing &amp;amp; field/row access (optional, for when paranoia is a feature)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install the core stack:&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="c"&gt;# JSR&lt;/span&gt;
npx jsr add @meshql/core @meshql/http @meshql/client

&lt;span class="c"&gt;# npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;meshql-core meshql-http meshql-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prisma path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx jsr add @meshql/core @meshql/prisma @meshql/http
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Probably you if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You like SQL (or your ORM) and dislike resolver mazes&lt;/li&gt;
&lt;li&gt;Your mobile/web clients keep asking for different field shapes&lt;/li&gt;
&lt;li&gt;You want GraphQL-style selection without standing up GraphQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Probably not you if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need a full mutation/subscription graph with a federated supergraph and a committee&lt;/li&gt;
&lt;li&gt;You want the framework to own your DB pool (try something else; we're not fighting for that job)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it in five minutes
&lt;/h2&gt;

&lt;p&gt;Full walkthrough: &lt;a href="https://docs.meshql.dev/guide/run-example" rel="noopener noreferrer"&gt;docs.meshql.dev/guide/run-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or clone the &lt;strong&gt;showcase&lt;/strong&gt; - React + SQLite, no Docker, actual signed requests in DevTools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/meshql/meshql.git
&lt;span class="nb"&gt;cd &lt;/span&gt;meshql &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm build
pnpm &lt;span class="nt"&gt;--filter&lt;/span&gt; showcase start
&lt;span class="c"&gt;# http://localhost:3010&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Honestly? I got tired of choosing between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shipping bloated REST&lt;/strong&gt;, and&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adopting GraphQL&lt;/strong&gt; and suddenly my side project needed a platform team.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MeshQL is the "what if the client shaped the response, but I kept my stack boring" experiment. It's MIT, Node 22+, works on Bun/Deno, and I'm using it for real APIs - not as a thought experiment that dies in a &lt;code&gt;packages/&lt;/code&gt; folder (we've all been there).&lt;/p&gt;

&lt;p&gt;If you try it and something feels off, open an issue. If you try it and it clicks, I'd love to hear what you built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/meshql/meshql" rel="noopener noreferrer"&gt;meshql/meshql&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://docs.meshql.dev" rel="noopener noreferrer"&gt;docs.meshql.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Prisma demo: &lt;a href="https://github.com/meshql/meshql/tree/main/examples/express-prisma" rel="noopener noreferrer"&gt;express-prisma&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;P.S. - Yes, the name is a pun. No, I won't apologize. Yes, &lt;code&gt;post.comments.author.name&lt;/code&gt; works end-to-end now. We fixed that before the ORM release because nested reads that don't actually read are a special kind of betrayal.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>node</category>
      <category>api</category>
      <category>graphql</category>
    </item>
    <item>
      <title>🚀 I Built QNVS — A Node.js Version Manager That Doesn't Need Admin Rights (Yes, Even on Windows)</title>
      <dc:creator>Ramananda Panda</dc:creator>
      <pubDate>Thu, 09 Apr 2026 13:27:13 +0000</pubDate>
      <link>https://dev.to/rp01/i-built-qnvs-a-nodejs-version-manager-that-doesnt-need-admin-rights-yes-even-on-windows-20k1</link>
      <guid>https://dev.to/rp01/i-built-qnvs-a-nodejs-version-manager-that-doesnt-need-admin-rights-yes-even-on-windows-20k1</guid>
      <description>&lt;p&gt;&lt;strong&gt;No sudo. No IT ticket. No drama. Just &lt;code&gt;qnvs&lt;/code&gt; and vibes.&lt;/strong&gt;&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%2Fimages.unsplash.com%2Fphoto-1498050108023-c5249f4df085%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1498050108023-c5249f4df085%3Fw%3D1200" alt="Hero image — developer at a clean workstation" width="1200" height="799"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested hero image: &lt;a href="https://unsplash.com/photos/m_HRfLhgABo" rel="noopener noreferrer"&gt;Unsplash — Christopher Gower (Developer workstation)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem That Made Me Build This
&lt;/h2&gt;

&lt;p&gt;Picture this: Monday morning. You're a developer at a company where IT guards the admin password like it's nuclear launch codes. You need Node.js 22 for a new project, but your machine has Node 18. The options?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Submit an IT ticket.&lt;/strong&gt; Estimated response: 3-7 business days. Maybe. If Mercury is in retrograde.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use nvm.&lt;/strong&gt; Great on macOS/Linux! On Windows? &lt;em&gt;[laughs in Group Policy]&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use nvm-windows.&lt;/strong&gt; Requires admin privileges to create symlinks. Back to step 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download Node.js manually.&lt;/strong&gt; Sure, if you enjoy living like it's 2009.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these worked for me. So instead of rage-quitting, I rage-coded.&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;&lt;a href="https://github.com/qnvs/qnvs" rel="noopener noreferrer"&gt;QNVS&lt;/a&gt;&lt;/strong&gt; — Quick Node Version Switcher. A single binary, zero-dependency, cross-platform, no-admin-required Node.js version manager with a gorgeous interactive TUI. Built in Go. Ships in ~10MB. Works everywhere.&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%2Fimages.unsplash.com%2Fphoto-1555066931-4365d14bab8c%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1555066931-4365d14bab8c%3Fw%3D1200" alt="Terminal with beautiful UI" width="1200" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/4hbJ-eymZ1o" rel="noopener noreferrer"&gt;Unsplash — Florian Olivo (Terminal/Code)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Wait, What Does "No Admin Required" Actually Mean?
&lt;/h2&gt;

&lt;p&gt;Let me be crystal clear because this is the &lt;em&gt;entire reason&lt;/em&gt; QNVS exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ No &lt;code&gt;sudo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;❌ No Administrator PowerShell&lt;/li&gt;
&lt;li&gt;❌ No Developer Mode toggle&lt;/li&gt;
&lt;li&gt;❌ No Group Policy exceptions&lt;/li&gt;
&lt;li&gt;❌ No IT tickets&lt;/li&gt;
&lt;li&gt;❌ No "please sir, may I have write access to Program Files"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;QNVS lives entirely in &lt;strong&gt;user space&lt;/strong&gt;. On macOS/Linux, it chills in &lt;code&gt;~/.qnvs&lt;/code&gt;. On Windows, it sits in &lt;code&gt;%LOCALAPPDATA%\qnvs&lt;/code&gt;. That's it. Your home directory. The one place on the computer that's actually &lt;em&gt;yours&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You download the binary. You run &lt;code&gt;qnvs setup&lt;/code&gt;. You install Node.js versions. You switch between them. You feel powerful. All without elevating a single privilege.&lt;/p&gt;


&lt;h2&gt;
  
  
  The TUI That Made My Terminal Beautiful ✨
&lt;/h2&gt;

&lt;p&gt;Look, I could've just built a boring CLI. But I had access to the &lt;a href="https://charm.sh/" rel="noopener noreferrer"&gt;Charm&lt;/a&gt; ecosystem — &lt;code&gt;bubbletea&lt;/code&gt;, &lt;code&gt;bubbles&lt;/code&gt;, &lt;code&gt;lipgloss&lt;/code&gt; — and honestly, if you're going to build a terminal tool in 2025, you owe it to yourself to make it &lt;em&gt;pretty&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;qnvs&lt;/code&gt; with no arguments and you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ██████╗ ███╗   ██╗██╗   ██╗███████╗
██╔═══██╗████╗  ██║██║   ██║██╔════╝
██║   ██║██╔██╗ ██║██║   ██║███████╗
██║▄▄ ██║██║╚██╗██║╚██╗ ██╔╝╚════██║
╚██████╔╝██║ ╚████║ ╚████╔╝ ███████║
 ╚══▀▀═╝ ╚═╝  ╚═══╝  ╚═══╝  ╚══════╝
  Quick Node Version Switcher • No admin required

╭──────────────────────────────────────────────╮
│                                              │
│  ▸ 📦  Install Node.js                       │
│        Download and install a new version    │
│                                              │
│    📋  List/Switch                            │
│    🗑️   Uninstall                             │
│    🔧  Setup                                  │
│    🔓  Toggle TLS Skip                        │
│    ❓  Help                                   │
│    👋  Exit                                   │
│                                              │
│   2 installed   Active: v22.22.0             │
╰──────────────────────────────────────────────╯

  ↑↓ navigate │ ⏎ select │ q quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrow keys to navigate. Enter to select. &lt;code&gt;q&lt;/code&gt; to quit. That's the &lt;em&gt;entire&lt;/em&gt; learning curve.&lt;/p&gt;

&lt;p&gt;No flags to memorize. No &lt;code&gt;--help&lt;/code&gt; to desperately consult. Just a menu. With emojis. Like civilized people.&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%2Fimages.unsplash.com%2Fphoto-1595225476474-87563907a212%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1595225476474-87563907a212%3Fw%3D1200" alt="Colorful keyboard / aesthetic dev setup" width="1200" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/hwLAI5lRhdM" rel="noopener noreferrer"&gt;Unsplash — Clay Banks (Colorful keyboard)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Windows Problem (And How I Solved It)
&lt;/h2&gt;

&lt;p&gt;Here's the dirty secret of Node.js version managers on Windows: &lt;strong&gt;they almost all rely on symlinks or junctions&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symlinks&lt;/strong&gt; need Developer Mode or admin rights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directory junctions&lt;/strong&gt; (&lt;code&gt;mklink /J&lt;/code&gt;) &lt;em&gt;usually&lt;/em&gt; work without admin... until your company's Group Policy says "nah"&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cue developer tears&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;QNVS takes a &lt;strong&gt;hybrid approach&lt;/strong&gt; that I'm unreasonably proud of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Try to create a directory junction. This works on most Windows machines without admin rights and gives you fast, transparent version switching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; If junctions fail (thanks, Group Policy), QNVS &lt;em&gt;automatically&lt;/em&gt; falls back to &lt;strong&gt;shim mode&lt;/strong&gt; — it creates tiny &lt;code&gt;.cmd&lt;/code&gt; wrapper scripts in &lt;code&gt;~/.qnvs/bin/&lt;/code&gt; that redirect to the active Node.js version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@echo off
"C:\Users\you\AppData\Local\qnvs\versions\v22.22.0\node.exe" %*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A &lt;code&gt;.cmd&lt;/code&gt; file. Group Policy can't block &lt;code&gt;.cmd&lt;/code&gt; files because that would break &lt;em&gt;literally everything else on Windows&lt;/em&gt;. It's not a hack — it's a feature.&lt;/p&gt;

&lt;p&gt;The fallback is completely automatic and transparent. You don't configure anything. You don't even know it happened. QNVS just... works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CLI: For When You Know What You Want
&lt;/h2&gt;

&lt;p&gt;The interactive TUI is great for exploring, but sometimes you just want to get in, switch versions, and get out. The CLI has your back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install Node 22 (latest 22.x.x)
qnvs install 22

# Install the latest LTS
qnvs install lts

# Install a specific version
qnvs install 20.10.0

# Switch versions
qnvs use 22

# See what you've got
qnvs list

# What's active?
qnvs current
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Version resolution is smart. Type &lt;code&gt;22&lt;/code&gt; and QNVS fetches the latest &lt;code&gt;22.x.x&lt;/code&gt; from nodejs.org. Type &lt;code&gt;lts&lt;/code&gt; and it finds the latest Long Term Support release. Type &lt;code&gt;latest&lt;/code&gt; and... you get the latest. Revolutionary stuff.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qnvs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Launch the interactive TUI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qnvs install &amp;lt;version&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Download and install a version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qnvs use &amp;lt;version&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch to an installed version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qnvs list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List installed versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qnvs current&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show the active version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qnvs uninstall &amp;lt;version&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove a version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qnvs setup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Initialize QNVS + configure PATH&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Corporate VPN? No Problem.
&lt;/h2&gt;

&lt;p&gt;Raise your hand if you've ever seen this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x509: certificate signed by unknown authority
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're behind a corporate VPN like &lt;strong&gt;Cato&lt;/strong&gt;, &lt;strong&gt;Zscaler&lt;/strong&gt;, or &lt;strong&gt;Palo Alto&lt;/strong&gt;, your company is probably doing TLS inspection — which means your HTTPS requests are getting re-signed by a corporate certificate that your tools don't trust.&lt;/p&gt;

&lt;p&gt;Most tools: "Figure it out yourself. Here's a 47-step Stack Overflow thread."&lt;/p&gt;

&lt;p&gt;QNVS: "Toggle a switch."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;qnvs install 22 --insecure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Interactive mode:&lt;/strong&gt; Just select &lt;strong&gt;"🔓 Toggle TLS Skip"&lt;/strong&gt; from the menu before installing.&lt;/p&gt;

&lt;p&gt;Is it ideal? No. TLS verification exists for good reasons. But when you're on a locked-down corporate machine and the VPN is doing inspection, this beats "can't install Node.js at all" every single time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Go? (Again)
&lt;/h2&gt;

&lt;p&gt;I keep building things in Go and people keep asking why. So here's the elevator pitch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single binary.&lt;/strong&gt; No runtime. No &lt;code&gt;node_modules&lt;/code&gt;. No Python virtual environment. No Java classpath. Just one file. Copy it. Run it. Done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-compilation is stupid easy.&lt;/strong&gt; One command gives me binaries for Windows/macOS/Linux across amd64 and arm64. Six platforms, zero hassle:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOOS=windows GOARCH=amd64 go build -o qnvs-windows-amd64.exe .
GOOS=darwin  GOARCH=arm64  go build -o qnvs-macos-arm64 .
GOOS=linux   GOARCH=amd64  go build -o qnvs-linux-amd64 .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Charm ecosystem.&lt;/strong&gt; Bubbletea + Bubbles + Lipgloss = gorgeous terminal UIs with minimal effort. It's like React but for terminals and without the existential crisis of choosing a state management library.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;~10MB binary.&lt;/strong&gt; The entire tool — TUI, version resolution, download with progress bars, archive extraction, platform detection — fits in the space of about 3 JPEG photos from your phone.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fimages.unsplash.com%2Fphoto-1611532736597-de2d4265fba3%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1611532736597-de2d4265fba3%3Fw%3D1200" alt="Gopher / Go mascot" width="1200" height="1800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://gopherize.me" rel="noopener noreferrer"&gt;Gopherize.me&lt;/a&gt; for a custom gopher, or &lt;a href="https://unsplash.com/s/photos/golang" rel="noopener noreferrer"&gt;Unsplash — Go-related imagery&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  How It Actually Works Under The Hood
&lt;/h2&gt;

&lt;p&gt;For the curious (and the people who read source code for fun — I see you), here's the architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The whole project is two files:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;main.go&lt;/code&gt; — The core: version resolution, downloading, extraction, symlink/junction/shim management, CLI handling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;interactive.go&lt;/code&gt; — The entire TUI, built on Bubbletea's Elm architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Version resolution&lt;/strong&gt; hits &lt;code&gt;https://nodejs.org/dist/index.json&lt;/code&gt;, parses the JSON, and fuzzy-matches your input. &lt;code&gt;22&lt;/code&gt; → latest &lt;code&gt;v22.x.x&lt;/code&gt;. &lt;code&gt;lts&lt;/code&gt; → latest release where the &lt;code&gt;lts&lt;/code&gt; field is a string (Node.js marks LTS versions with a codename like &lt;code&gt;"Jod"&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downloading&lt;/strong&gt; uses a custom progress bar (Charm's &lt;code&gt;progress&lt;/code&gt; component) that shows real-time MB progress. It does a &lt;code&gt;HEAD&lt;/code&gt; request first to get the content length, then streams the download with a 32KB buffer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extraction&lt;/strong&gt; handles both &lt;code&gt;.tar.gz&lt;/code&gt; (macOS/Linux) and &lt;code&gt;.zip&lt;/code&gt; (Windows) natively — no &lt;code&gt;tar&lt;/code&gt; or &lt;code&gt;unzip&lt;/code&gt; binary needed. Pure Go. It even handles symlinks in tar archives (npm/npx are symlinks in the Node.js distribution).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Switching&lt;/strong&gt; is where it gets spicy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unix:&lt;/strong&gt; Swap a symlink. &lt;code&gt;~/.qnvs/current&lt;/code&gt; → &lt;code&gt;~/.qnvs/versions/v22.22.0&lt;/code&gt;. Done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows (happy path):&lt;/strong&gt; Create a directory junction. Same concept, Windows flavor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows (corporate dystopia):&lt;/strong&gt; Generate &lt;code&gt;.cmd&lt;/code&gt; shims. Same result, different mechanism.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Installation: Pick Your Poison
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L https://github.com/qnvs/qnvs/releases/latest/download/qnvs-linux-amd64 -o qnvs
chmod +x qnvs
./qnvs setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invoke-WebRequest -Uri "https://github.com/qnvs/qnvs/releases/latest/download/qnvs-windows-amd64.exe" -OutFile "qnvs.exe"
.\qnvs.exe setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Build from source:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/qnvs/qnvs.git
cd qnvs
go build -o qnvs .
./qnvs setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;setup&lt;/code&gt; creates the directory structure, copies the binary to &lt;code&gt;~/.qnvs/bin/&lt;/code&gt;, and even tries to auto-append the PATH export to your &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt;. On Windows, it tells you exactly what to add to your PATH. No guessing.&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%2Fimages.unsplash.com%2Fphoto-1474631245212-32dc3c8310c6%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1474631245212-32dc3c8310c6%3Fw%3D1200" alt="Light bulb / idea / inspiration" width="1200" height="962"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/7e2pe9wjL9M" rel="noopener noreferrer"&gt;Unsplash — Riccardo Annandale (Light bulb)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  QNVS vs. The Competition
&lt;/h2&gt;

&lt;p&gt;Let's be honest — there are other version managers out there. Here's where QNVS stands:&lt;/p&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;&lt;strong&gt;QNVS&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;nvm&lt;/th&gt;
&lt;th&gt;nvm-windows&lt;/th&gt;
&lt;th&gt;fnm&lt;/th&gt;
&lt;th&gt;Volta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No admin required&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&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;Windows support&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&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;Works behind corporate VPN&lt;/td&gt;
&lt;td&gt;✅ (built-in)&lt;/td&gt;
&lt;td&gt;❌&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;Interactive TUI&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&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;Single binary&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&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;Corporate Windows (no junctions)&lt;/td&gt;
&lt;td&gt;✅ (auto shim)&lt;/td&gt;
&lt;td&gt;N/A&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;Zero dependencies&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (bash)&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;p&gt;The &lt;strong&gt;corporate Windows&lt;/strong&gt; row is the killer feature. If you've ever been stuck on a locked-down Windows machine where &lt;code&gt;mklink&lt;/code&gt; is blocked and Developer Mode is disabled, you know the pain. QNVS is the first version manager I know of that handles this gracefully with its automatic shim fallback.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Is This For?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🏢 &lt;strong&gt;Corporate developers&lt;/strong&gt; trapped behind Group Policy and VPN inspection&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;Windows users&lt;/strong&gt; who are tired of "requires admin" being the answer to everything&lt;/li&gt;
&lt;li&gt;🎨 &lt;strong&gt;TUI enthusiasts&lt;/strong&gt; who believe CLI tools should be beautiful&lt;/li&gt;
&lt;li&gt;🏃 &lt;strong&gt;Developers who hate bloat&lt;/strong&gt; — one binary, no runtime, no install scripts&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Anyone juggling multiple Node.js projects&lt;/strong&gt; with different version requirements&lt;/li&gt;
&lt;li&gt;🆕 &lt;strong&gt;Beginners&lt;/strong&gt; who find &lt;code&gt;nvm&lt;/code&gt; confusing — just run &lt;code&gt;qnvs&lt;/code&gt; and use the arrow keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever Googled "how to install Node.js without admin rights" at 11 PM on a work laptop... I built this for you.&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%2Fimages.unsplash.com%2Fphoto-1513151233558-d860c5398176%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1513151233558-d860c5398176%3Fw%3D1200" alt="Celebration / confetti" width="1200" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/Xaanw0s0pMk" rel="noopener noreferrer"&gt;Unsplash — Jason Leung (Confetti)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;QNVS is at &lt;strong&gt;v1.1.3&lt;/strong&gt; and I'm actively working on it. Things rattling around in my brain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📌 &lt;strong&gt;Auto-detect &lt;code&gt;.nvmrc&lt;/code&gt; / &lt;code&gt;.node-version&lt;/code&gt;&lt;/strong&gt; — walk into a project directory and QNVS switches automatically&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Self-update&lt;/strong&gt; — &lt;code&gt;qnvs update&lt;/code&gt; to grab the latest QNVS binary&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Package manager support&lt;/strong&gt; — install and manage &lt;code&gt;pnpm&lt;/code&gt;/&lt;code&gt;yarn&lt;/code&gt; alongside Node&lt;/li&gt;
&lt;li&gt;🪟 &lt;strong&gt;Windows code signing&lt;/strong&gt; — so you stop getting the "Unknown Publisher" warning&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Mirror support&lt;/strong&gt; — for environments that can't reach nodejs.org directly&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Disk usage display&lt;/strong&gt; — see how much space each version is eating&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Give It a Spin ⭐
&lt;/h2&gt;

&lt;p&gt;QNVS is &lt;strong&gt;open-source&lt;/strong&gt; (MIT licensed) and lives at &lt;strong&gt;&lt;a href="https://github.com/qnvs/qnvs" rel="noopener noreferrer"&gt;github.com/qnvs/qnvs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Three steps to happiness:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the binary for your platform from &lt;a href="https://github.com/qnvs/qnvs/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;qnvs setup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;qnvs&lt;/code&gt; and bask in the TUI glow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Star the repo if it saved you from filing an IT ticket. Open an issue if something's broken. Submit a PR if you want to make it better. Or just enjoy switching Node versions without asking anyone's permission for once in your corporate life.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with 🐹 Go, the ✨ Charm ecosystem, and a deep resentment for "Please contact your system administrator."&lt;/em&gt;&lt;/p&gt;




</description>
      <category>cli</category>
      <category>node</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>🚀 I Built PQPM — A Process Manager That Doesn't Care What Language You Speak</title>
      <dc:creator>Ramananda Panda</dc:creator>
      <pubDate>Thu, 09 Apr 2026 06:13:49 +0000</pubDate>
      <link>https://dev.to/rp01/i-built-pqpm-a-process-manager-that-doesnt-care-what-language-you-speak-npp</link>
      <guid>https://dev.to/rp01/i-built-pqpm-a-process-manager-that-doesnt-care-what-language-you-speak-npp</guid>
      <description>&lt;p&gt;And yes, your PHP queue workers, Go binaries, and Python bots can finally coexist in peace.**&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%2Fimages.unsplash.com%2Fphoto-1558494949-ef010cbdcc31%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1558494949-ef010cbdcc31%3Fw%3D1200" alt="Hero image — servers in a data center" width="1200" height="673"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested hero image: &lt;a href="https://unsplash.com/photos/M5tzZtFCOfs" rel="noopener noreferrer"&gt;Unsplash — Taylor Vick (Server Room)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Itch I Couldn't Stop Scratching
&lt;/h2&gt;

&lt;p&gt;If you've ever managed a VPS — especially with something like Virtualmin — you &lt;em&gt;know&lt;/em&gt; the pain. You've got 14 users on a shared server. Dave is running a Laravel queue worker. Sarah has a Go API. And someone named &lt;code&gt;admin3&lt;/code&gt; (who no one remembers creating) is running a rogue Python Discord bot eating 90% of the RAM.&lt;/p&gt;

&lt;p&gt;You SSH in. You &lt;code&gt;nohup&lt;/code&gt;. You &lt;code&gt;screen&lt;/code&gt;. You pray.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PM2?&lt;/strong&gt; Great tool! ...if your entire universe is Node.js. But the moment you need to babysit a PHP artisan command or a compiled Go binary, PM2 looks at you like you just spoke Klingon.&lt;/p&gt;

&lt;p&gt;I needed something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let &lt;strong&gt;non-root users&lt;/strong&gt; manage their own processes (without calling me at 2 AM)&lt;/li&gt;
&lt;li&gt;Was &lt;strong&gt;language-agnostic&lt;/strong&gt; — Go, PHP, Python, Rust, a bash script that plays elevator music — I don't judge&lt;/li&gt;
&lt;li&gt;Had real &lt;strong&gt;resource limits&lt;/strong&gt; so one bad actor doesn't nuke the whole server&lt;/li&gt;
&lt;li&gt;Was &lt;strong&gt;dead simple&lt;/strong&gt; to configure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built it. Meet &lt;strong&gt;&lt;a href="https://github.com/pqpm/pqpm" rel="noopener noreferrer"&gt;PQPM&lt;/a&gt;&lt;/strong&gt; — the Process Queue Process Manager.&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%2Fimages.unsplash.com%2Fphoto-1629654297299-c8506221ca97%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1629654297299-c8506221ca97%3Fw%3D1200" alt="Code on a terminal screen" width="1200" height="900"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/g5jpH62pwes" rel="noopener noreferrer"&gt;Unsplash — Gabriel Heinzer (Terminal/Code)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What Even Is PQPM?
&lt;/h2&gt;

&lt;p&gt;PQPM is a &lt;strong&gt;lightweight, system-level process manager&lt;/strong&gt; written in Go. It runs as a daemon (&lt;code&gt;pqpmd&lt;/code&gt;) on your Linux server and lets any user on the system manage their own long-running processes through a simple CLI (&lt;code&gt;pqpm&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Think of it as a bouncer at a nightclub: the daemon has the master key (runs as root), but it &lt;em&gt;immediately&lt;/em&gt; drops privileges to match the user who's requesting the action. User A can't touch User B's stuff. Period.&lt;/p&gt;

&lt;p&gt;Here's the whole mental model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Daemon&lt;/strong&gt; (&lt;code&gt;pqpmd&lt;/code&gt;) — runs as root, listens on a Unix socket&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Config&lt;/strong&gt; (&lt;code&gt;~/.pqpm.toml&lt;/code&gt;) — each user defines their services in a dead-simple TOML file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The CLI&lt;/strong&gt; (&lt;code&gt;pqpm&lt;/code&gt;) — users run commands like &lt;code&gt;pqpm start my-worker&lt;/code&gt; with zero sudo required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Magic&lt;/strong&gt; — the daemon verifies the caller's identity using kernel-level socket credentials (&lt;code&gt;SO_PEERCRED&lt;/code&gt;), then spawns the process under that user's UID/GID&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No &lt;code&gt;sudo&lt;/code&gt;. No &lt;code&gt;chmod 777&lt;/code&gt; hacks. No "just run everything as root and hope for the best."&lt;/p&gt;


&lt;h2&gt;
  
  
  The Config File is &lt;em&gt;Chef's Kiss&lt;/em&gt; 🤌
&lt;/h2&gt;

&lt;p&gt;I'm a sucker for simple config files. XML can leave. YAML can take its "is this a string or a boolean" energy elsewhere. TOML just... makes sense.&lt;/p&gt;

&lt;p&gt;Here's what a user's &lt;code&gt;~/.pqpm.toml&lt;/code&gt; looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[service.my-worker]
command = "/usr/bin/php /home/user/public_html/artisan queue:work"
restart = "always"
max_memory = "512MB"
cpu_limit = "20%"

[service.api-server]
command = "/home/user/bin/api-server --port 8080"
restart = "on-failure"
max_memory = "1GB"
cpu_limit = "50%"
working_dir = "/home/user/api"
env = { NODE_ENV = "production", PORT = "8080" }

[service.python-bot]
command = "/usr/bin/python3 /home/user/bots/discord_bot.py"
restart = "always"
max_memory = "256MB"
cpu_limit = "10%"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three services. Three languages. One config file. Zero drama.&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%2Fimages.unsplash.com%2Fphoto-1611532736597-de2d4265fba3%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1611532736597-de2d4265fba3%3Fw%3D1200" alt="Gopher plush or Go mascot" width="1200" height="1800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/a-small-figurine-sitting-on-top-of-a-laptop-computer-a0mHbB970Aw" rel="noopener noreferrer"&gt;Unsplash — Chinmay B (Gopher)&lt;/a&gt; or any Go mascot image from &lt;a href="https://gopherize.me" rel="noopener noreferrer"&gt;Gopherize.me&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Go? Because Speed is Non-Negotiable
&lt;/h2&gt;

&lt;p&gt;When your process manager is responsible for keeping other people's services alive, it better not be the thing that dies first.&lt;/p&gt;

&lt;p&gt;Go gave me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goroutines &amp;amp; channels&lt;/strong&gt; for async, non-blocking process monitoring (each managed process gets its own goroutine — beautiful)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static binaries&lt;/strong&gt; — &lt;code&gt;pqpmd&lt;/code&gt; and &lt;code&gt;pqpm&lt;/code&gt; are single-file executables. No runtime. No dependencies. Just &lt;code&gt;curl&lt;/code&gt;, extract, and go&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-class syscall support&lt;/strong&gt; — spawning processes, dropping privileges with &lt;code&gt;setuid&lt;/code&gt;/&lt;code&gt;setgid&lt;/code&gt;, reading &lt;code&gt;SO_PEERCRED&lt;/code&gt; from Unix sockets — Go's &lt;code&gt;golang.org/x/sys&lt;/code&gt; package is a dream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-compilation&lt;/strong&gt; — building for &lt;code&gt;amd64&lt;/code&gt; and &lt;code&gt;arm64&lt;/code&gt; in one &lt;code&gt;make&lt;/code&gt; command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire dependency tree is laughably small: &lt;code&gt;BurntSushi/toml&lt;/code&gt; for config parsing, &lt;code&gt;spf13/cobra&lt;/code&gt; for the CLI, and &lt;code&gt;golang.org/x/sys&lt;/code&gt; for syscalls. That's it. Three dependencies. In 2025. I'll wait while you pick your jaw up off the floor.&lt;/p&gt;


&lt;h2&gt;
  
  
  Security: The Part I Lost Sleep Over (So You Don't Have To)
&lt;/h2&gt;

&lt;p&gt;This is a process manager that runs as root. Let me say that louder for the people in the back: &lt;strong&gt;it runs as root&lt;/strong&gt;. If the security isn't airtight, you might as well email your SSH keys to a stranger.&lt;/p&gt;

&lt;p&gt;Here's how PQPM stays paranoid (in a healthy way):&lt;/p&gt;
&lt;h3&gt;
  
  
  🔐 Identity via &lt;code&gt;SO_PEERCRED&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When the CLI connects to the daemon over a Unix Domain Socket, the daemon asks the &lt;em&gt;kernel&lt;/em&gt; — not the user — "who is this?" The kernel responds with the UID, GID, and PID. No tokens. No passwords. No trust-me-bro headers. The kernel doesn't lie.&lt;/p&gt;
&lt;h3&gt;
  
  
  🪂 Immediate Privilege Drop
&lt;/h3&gt;

&lt;p&gt;The daemon spawns the process as root but &lt;em&gt;instantly&lt;/em&gt; drops to the target user's UID/GID. The spawned process never runs with elevated privileges. Not even for a millisecond... well, maybe a couple of nanoseconds. Go is fast.&lt;/p&gt;
&lt;h3&gt;
  
  
  🧱 Resource Limits via cgroups
&lt;/h3&gt;

&lt;p&gt;Each process can have hard limits on memory and CPU using Linux cgroups (v2). If Dave's queue worker decides to allocate 16 GB of RAM, cgroups will politely (and firmly) say no.&lt;/p&gt;
&lt;h3&gt;
  
  
  📂 Path Restrictions
&lt;/h3&gt;

&lt;p&gt;Processes are locked to running within the user's authorized directories. No sneaky &lt;code&gt;../../etc/shadow&lt;/code&gt; business.&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%2Fimages.unsplash.com%2Fphoto-1555949963-ff9fe0c870eb%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1555949963-ff9fe0c870eb%3Fw%3D1200" alt="Lock and shield / cybersecurity" width="1200" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/mT7lXZPjk7U" rel="noopener noreferrer"&gt;Unsplash — FlyD (Neon Lock)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The CLI: Boring On Purpose
&lt;/h2&gt;

&lt;p&gt;I didn't want a CLI that requires a PhD to operate. Six commands. That's your entire vocabulary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqpm start &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fire up a service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqpm stop &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gracefully stop it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqpm restart &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop + start (shocking, I know)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqpm status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;See what's running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqpm log &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tail logs for a service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqpm version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Print the version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's it. No &lt;code&gt;pqpm init --template advanced --with-monitoring --enable-clustering --sacrifice-goat&lt;/code&gt;. Just start, stop, restart, status, log, version. Done.&lt;/p&gt;


&lt;h2&gt;
  
  
  Installation: The One-Liner™
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSL https://raw.githubusercontent.com/pqpm/pqpm/main/install.sh | sudo bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This detects your architecture, pulls the latest release from GitHub, drops the binaries into &lt;code&gt;/usr/local/bin&lt;/code&gt;, creates runtime directories, and sets up the systemd service. The whole thing takes about 5 seconds.&lt;/p&gt;

&lt;p&gt;Want to build from source? Also easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/pqpm/pqpm.git
cd pqpm
make build &amp;amp;&amp;amp; sudo make install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating? Run the one-liner again. Your managed processes keep running and get "re-adopted" by the daemon once it restarts. Like a responsible parent coming home from the grocery store.&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%2Fimages.unsplash.com%2Fphoto-1516849841032-87cbac4d88f7%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1516849841032-87cbac4d88f7%3Fw%3D1200" alt="Rocket launch" width="1200" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/OHOU-5UVIYQ" rel="noopener noreferrer"&gt;Unsplash — SpaceX (Rocket Launch)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Who Is This For?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPS admins&lt;/strong&gt; running shared hosting environments (especially Virtualmin/Webmin setups)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small teams&lt;/strong&gt; where multiple developers deploy services on the same box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone tired of PM2&lt;/strong&gt; who runs more than just Node.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The sysadmin&lt;/strong&gt; who wants their users to stop asking for root access to run a queue worker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo devs&lt;/strong&gt; running a $5 VPS with a Go API, a PHP backend, and a Python cron job — all at once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever typed &lt;code&gt;nohup something &amp;amp;&lt;/code&gt; and then immediately regretted your life choices, PQPM is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;PQPM is at &lt;strong&gt;v0.2.3&lt;/strong&gt; and actively being developed. Here's what's on the roadmap (or at least, living rent-free in my head):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📊 &lt;strong&gt;Web dashboard&lt;/strong&gt; — because sometimes you want to click buttons instead of typing commands&lt;/li&gt;
&lt;li&gt;🔔 &lt;strong&gt;Notifications&lt;/strong&gt; — get pinged when a process dies (and auto-restarts)&lt;/li&gt;
&lt;li&gt;📈 &lt;strong&gt;Metrics export&lt;/strong&gt; — Prometheus-compatible resource usage stats&lt;/li&gt;
&lt;li&gt;🐳 &lt;strong&gt;Container-aware mode&lt;/strong&gt; — playing nice with Docker environments&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;More tests&lt;/strong&gt; — always more tests&lt;/li&gt;
&lt;/ul&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%2Fimages.unsplash.com%2Fphoto-1522071820081-009f0129c71c%3Fw%3D1200" 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%2Fimages.unsplash.com%2Fphoto-1522071820081-009f0129c71c%3Fw%3D1200" alt="Open source community / collaboration" width="1200" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Suggested image: &lt;a href="https://unsplash.com/photos/QckxruozjRg" rel="noopener noreferrer"&gt;Unsplash — Annie Spratt (Team collaboration)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Give It a Spin ⭐
&lt;/h2&gt;

&lt;p&gt;PQPM is &lt;strong&gt;open-source&lt;/strong&gt; (MIT licensed) and lives at &lt;strong&gt;&lt;a href="https://github.com/pqpm/pqpm" rel="noopener noreferrer"&gt;github.com/pqpm/pqpm&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If it saves you even one &lt;code&gt;sudo&lt;/code&gt; argument with a client, one 2 AM SSH session, or one "why is the server at 100% CPU" panic — I'll consider it a win.&lt;/p&gt;

&lt;p&gt;Star the repo if you dig it. Open an issue if you find a bug. Submit a PR if you're feeling generous. Or just silently use it and never tell me — that's cool too. I'll never know. But my download counter will. 👀&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with 🐹 Go, ☕ too much coffee, and a burning hatred for &lt;code&gt;nohup&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Image Source Summary
&lt;/h2&gt;

&lt;p&gt;Here's a quick reference for all the suggested images (all free to use via Unsplash):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hero / Banner&lt;/td&gt;
&lt;td&gt;Server room / data center&lt;/td&gt;
&lt;td&gt;&lt;a href="https://unsplash.com/photos/M5tzZtFCOfs" rel="noopener noreferrer"&gt;Unsplash — Taylor Vick&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"What Is PQPM"&lt;/td&gt;
&lt;td&gt;Terminal with code&lt;/td&gt;
&lt;td&gt;&lt;a href="https://unsplash.com/photos/g5jpH62pwes" rel="noopener noreferrer"&gt;Unsplash — Gabriel Heinzer&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Why Go?"&lt;/td&gt;
&lt;td&gt;Go gopher / mascot&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://gopherize.me" rel="noopener noreferrer"&gt;Gopherize.me&lt;/a&gt; or &lt;a href="https://unsplash.com/s/photos/golang" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security section&lt;/td&gt;
&lt;td&gt;Neon lock / cybersecurity&lt;/td&gt;
&lt;td&gt;&lt;a href="https://unsplash.com/photos/mT7lXZPjk7U" rel="noopener noreferrer"&gt;Unsplash — FlyD&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installation&lt;/td&gt;
&lt;td&gt;Rocket launch&lt;/td&gt;
&lt;td&gt;&lt;a href="https://unsplash.com/photos/OHOU-5UVIYQ" rel="noopener noreferrer"&gt;Unsplash — SpaceX&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community / CTA&lt;/td&gt;
&lt;td&gt;Team collaboration&lt;/td&gt;
&lt;td&gt;&lt;a href="https://unsplash.com/photos/QckxruozjRg" rel="noopener noreferrer"&gt;Unsplash — Annie Spratt&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Fixing "No valid 'aps-environment' entitlement string found for application" in Flutter App</title>
      <dc:creator>Ramananda Panda</dc:creator>
      <pubDate>Wed, 12 Feb 2025 16:56:53 +0000</pubDate>
      <link>https://dev.to/rp01/fixing-no-valid-aps-environment-entitlement-string-found-for-application-in-flutter-app-3o8l</link>
      <guid>https://dev.to/rp01/fixing-no-valid-aps-environment-entitlement-string-found-for-application-in-flutter-app-3o8l</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you're developing a Flutter app with push notifications on iOS, you might encounter this frustrating error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"No valid 'aps-environment' entitlement string found for application"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This issue prevents push notifications from working correctly and often appears when configuring Firebase Cloud Messaging (FCM) or APNs (Apple Push Notification Service). But don’t worry! In this blog, we’ll walk you through the exact steps to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does This Happen?
&lt;/h2&gt;

&lt;p&gt;The error occurs because iOS requires a valid `&lt;code&gt;entitlement in your app’s provisioning profile. This entitlement determines whether your app supports push notifications in development (&lt;/code&gt;development&lt;code&gt;) or production (&lt;/code&gt;production`). If it’s missing, iOS refuses to deliver push notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Fix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Enable Push Notifications in Xcode
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open your Flutter project’s &lt;strong&gt;iOS folder&lt;/strong&gt; in Xcode.&lt;/li&gt;
&lt;li&gt;Select your &lt;strong&gt;Runner&lt;/strong&gt; project from the left panel.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Signing &amp;amp; Capabilities&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;+ Capability&lt;/strong&gt; button and add &lt;strong&gt;Push Notifications&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Also, add &lt;strong&gt;Background Modes&lt;/strong&gt; and check &lt;strong&gt;Remote notifications&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Ensure Your Provisioning Profile Supports Push Notifications
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://developer.apple.com/" rel="noopener noreferrer"&gt;Apple Developer Account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Certificates, Identifiers &amp;amp; Profiles&lt;/strong&gt; &amp;gt; &lt;strong&gt;App IDs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Find your app’s bundle ID and make sure &lt;strong&gt;Push Notifications&lt;/strong&gt; is enabled.&lt;/li&gt;
&lt;li&gt;If it's not enabled, edit the App ID and enable Push Notifications.&lt;/li&gt;
&lt;li&gt;Now, head to &lt;strong&gt;Profiles&lt;/strong&gt; and regenerate your &lt;strong&gt;Provisioning Profile&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Download and install the new profile in Xcode (&lt;strong&gt;Preferences &amp;gt; Accounts &amp;gt; Manage Certificates&lt;/strong&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. Check the &lt;code&gt;Entitlements.plist&lt;/code&gt; File
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Runner/Runner.entitlements&lt;/strong&gt; in Xcode.&lt;/li&gt;
&lt;li&gt;Ensure it contains the following key:
&lt;code&gt;`xml
&amp;lt;key&amp;gt;aps-environment&amp;lt;/key&amp;gt;
&amp;lt;string&amp;gt;development&amp;lt;/string&amp;gt;  &amp;lt;!-- or "production" if releasing --&amp;gt;
`&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If the file doesn’t exist, create a new one named &lt;strong&gt;Runner.entitlements&lt;/strong&gt; and add the above content.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Build Settings&lt;/strong&gt; in Xcode, search for &lt;strong&gt;Code Signing Entitlements&lt;/strong&gt;, and make sure it includes &lt;code&gt;Runner.entitlements&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4. Make &lt;code&gt;Runner.entitlements&lt;/code&gt; Available for Debug Mode
&lt;/h3&gt;

&lt;p&gt;When a user enables push notifications, Xcode adds a &lt;strong&gt;RunnerProfile.entitlements&lt;/strong&gt; file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;br&gt;
&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br&gt;
&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;br&gt;
&amp;lt;plist version="1.0"&amp;gt;&lt;br&gt;
&amp;lt;dict&amp;gt;&lt;br&gt;
    &amp;lt;key&amp;gt;aps-environment&amp;lt;/key&amp;gt;&lt;br&gt;
    &amp;lt;string&amp;gt;development&amp;lt;/string&amp;gt;&lt;br&gt;
&amp;lt;/dict&amp;gt;&lt;br&gt;
&amp;lt;/plist&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, this file may not be available when you try to build and run the app on a simulator or device. To resolve this, you need to explicitly specify the entitlement file for &lt;strong&gt;debug mode&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Build Settings&lt;/strong&gt; in Xcode.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Signing &amp;gt; Code Signing Entitlements&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set the entitlement file path (&lt;code&gt;Runner.entitlements&lt;/code&gt;) for each build mode.&lt;/li&gt;
&lt;li&gt;Users can set multiple entitlement files for &lt;strong&gt;debug&lt;/strong&gt; and &lt;strong&gt;release&lt;/strong&gt; modes.

&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;debug&lt;/strong&gt;, set &lt;code&gt;aps-environment&lt;/code&gt; to &lt;code&gt;development&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;release&lt;/strong&gt;, set &lt;code&gt;aps-environment&lt;/code&gt; to &lt;code&gt;production&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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%2Fgzqa6kv9srygp454lt6o.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%2Fgzqa6kv9srygp454lt6o.png" alt=" " width="680" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Rebuild and Deploy
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Clean the project:
&lt;code&gt;sh
flutter clean
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reinstall dependencies:
&lt;code&gt;sh
flutter pub get
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run your app again:
&lt;code&gt;sh
flutter run
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If you are building for release, ensure you archive and upload the build to TestFlight or App Store.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This error can be annoying, but following these steps should resolve it quickly. If you still face issues, double-check your Apple Developer settings and Xcode configurations.&lt;/p&gt;

&lt;p&gt;Now, go ahead and test your push notifications! 🚀&lt;/p&gt;

&lt;p&gt;Have you faced this issue? Let me know in the comments how you fixed it or if you need any help! 🙌&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ios</category>
      <category>pushnotification</category>
      <category>fcm</category>
    </item>
  </channel>
</rss>
