<?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: PeiSong</title>
    <description>The latest articles on DEV Community by PeiSong (@peisongo).</description>
    <link>https://dev.to/peisongo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3826192%2Fd2ac3a64-695b-4837-b43f-29162ddf91df.jpeg</url>
      <title>DEV Community: PeiSong</title>
      <link>https://dev.to/peisongo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peisongo"/>
    <language>en</language>
    <item>
      <title>API Tooling Didn’t Get Better. We Just Lowered Our Expectations</title>
      <dc:creator>PeiSong</dc:creator>
      <pubDate>Sun, 29 Mar 2026 12:09:58 +0000</pubDate>
      <link>https://dev.to/peisongo/api-tooling-didnt-get-better-we-just-lowered-our-expectations-112k</link>
      <guid>https://dev.to/peisongo/api-tooling-didnt-get-better-we-just-lowered-our-expectations-112k</guid>
      <description>&lt;p&gt;It is strange how little we talk about APIs now.&lt;/p&gt;

&lt;p&gt;Not because APIs matter less. They matter more than ever. Modern software is held together by APIs: SaaS products, internal platforms, mobile apps, partner integrations, automation workflows, and now AI agents calling tools over HTTP-shaped boundaries all day long.&lt;/p&gt;

&lt;p&gt;And yet the API ecosystem feels oddly quiet, almost resigned. As if the problem has already been solved. As if Postman, Swagger, a test runner, some CI glue, and a folder full of half-maintained examples are simply the natural end state.&lt;/p&gt;

&lt;p&gt;Even newer waves like GraphQL and gRPC, important as they are, never really restarted the broader API tooling conversation. They shifted some technical boundaries, but the workflow problem remained.&lt;/p&gt;

&lt;p&gt;I do not think the problem was solved.&lt;/p&gt;

&lt;p&gt;I think we just got used to the friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The easiest API problem got dramatically better
&lt;/h2&gt;

&lt;p&gt;For a long time, the most visible API problem was documentation.&lt;/p&gt;

&lt;p&gt;Docs were incomplete. Docs drifted. Docs showed idealized examples that did not quite match reality. Swagger and OpenAPI helped a lot, but the workflow was still familiar: read the spec, hit the endpoint, discover the actual behavior, update your mental model, move on.&lt;/p&gt;

&lt;p&gt;AI changed this surprisingly fast.&lt;/p&gt;

&lt;p&gt;Today, if you have a half-decent spec, some examples, or even just a working endpoint, AI can explain the API, summarize it, generate payloads, compare versions, write a client, and answer questions about the docs faster than most human workflows ever could.&lt;/p&gt;

&lt;p&gt;That is real progress. But it also exposed something deeper: documentation used to absorb a lot of our frustration with APIs. Once AI made that part dramatically easier, the unsolved parts of API work became much harder to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem was never just docs
&lt;/h2&gt;

&lt;p&gt;The harder problem is everything that happens after understanding an API.&lt;/p&gt;

&lt;p&gt;How do you explore it, keep the useful parts, turn one-off discovery into repeatable verification, test a real workflow instead of a pile of disconnected requests, move from local use to CI without rewriting everything, and make this whole thing work with AI instead of around it?&lt;/p&gt;

&lt;p&gt;That is why workflow matters so much. Workflow determines what knowledge survives, how quickly feedback returns, whether a useful experiment becomes a durable asset or disappears, and whether AI is helping inside a real verification loop or just generating more throwaway code.&lt;/p&gt;

&lt;p&gt;Workflow is where API knowledge either compounds or dies.&lt;/p&gt;

&lt;p&gt;This is where I think the ecosystem largely failed. We got many partial tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;documentation tools&lt;/li&gt;
&lt;li&gt;request senders&lt;/li&gt;
&lt;li&gt;schema generators&lt;/li&gt;
&lt;li&gt;mock servers&lt;/li&gt;
&lt;li&gt;test runners&lt;/li&gt;
&lt;li&gt;SDK generators&lt;/li&gt;
&lt;li&gt;contract tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of them are good. Swagger is good. OpenAPI is useful. Design-first was a genuinely strong idea.&lt;/p&gt;

&lt;p&gt;But if we are honest, none of that really changed the day-to-day workflow for most teams.&lt;/p&gt;

&lt;p&gt;Too much of the category kept reinventing the API client while leaving the workflow itself mostly untouched.&lt;/p&gt;

&lt;p&gt;The industry spent years polishing variations of the same request-sending model and calling that progress.&lt;/p&gt;

&lt;p&gt;The common reality still looks the same: you explore in one place, document in another, test in another, automate in another, and debug failures somewhere else. The useful knowledge gets lost in the gaps. That is the part the industry quietly normalized.&lt;/p&gt;

&lt;p&gt;A small example: someone changes a field name in the backend response. The frontend type is now stale. The API example in the docs is stale too. A saved request still "works" but no longer reflects the real workflow. The test suite fails later in CI, far away from the original change. Nothing about this is rare. It is ordinary API work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design-first made sense for the human coordination era
&lt;/h2&gt;

&lt;p&gt;Design-first was not a bad idea. It was a coordination technology for humans.&lt;/p&gt;

&lt;p&gt;It made sense in a world where frontend and backend were built by different people, often on different teams, moving at different speeds, and trying to align before implementation drifted too far. In that world, the spec had to carry a lot of weight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;team alignment&lt;/li&gt;
&lt;li&gt;mock generation&lt;/li&gt;
&lt;li&gt;frontend/backend parallel work&lt;/li&gt;
&lt;li&gt;review before implementation&lt;/li&gt;
&lt;li&gt;external documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was a real problem, and design-first was a real answer to it.&lt;/p&gt;

&lt;p&gt;But AI changes the coordination model.&lt;/p&gt;

&lt;p&gt;Now one developer, with one or more AI agents, can often move across frontend, backend, tests, examples, and docs in a single loop. The coordination cost that used to justify spec-first workflow collapses.&lt;/p&gt;

&lt;p&gt;The new default starts to look more like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI writes backend code&lt;/li&gt;
&lt;li&gt;AI writes frontend code&lt;/li&gt;
&lt;li&gt;AI drafts tests&lt;/li&gt;
&lt;li&gt;the code gets run&lt;/li&gt;
&lt;li&gt;the spec is generated or updated only when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does not make contracts unimportant. It means the center of gravity shifts away from spec as the starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI makes this problem more obvious, not less
&lt;/h2&gt;

&lt;p&gt;A lot of people assume AI reduces the importance of API tooling. I think the opposite is true.&lt;/p&gt;

&lt;p&gt;AI is very good at working with code, structured schemas, and explicit contracts. It is much less naturally aligned with fragmented click-through workflows, stale collections, and operational knowledge scattered across tools.&lt;/p&gt;

&lt;p&gt;If AI helps you understand an endpoint, draft a request, or even generate a test, that is useful. But if the workflow still breaks the moment you want to keep that work, verify it, rerun it in CI, inspect failures, or reuse it inside a larger flow, then AI is not solving the actual problem. It is just accelerating the first step.&lt;/p&gt;

&lt;p&gt;That is also what current AI coding tools still lack in practice. The bottleneck is usually not "more documentation." It is live execution feedback, structured traces, failing assertions, environment context, and a clean way to turn exploratory code into durable verification.&lt;/p&gt;

&lt;p&gt;AI solved a lot of API explanation. It did not solve API workflow. If anything, it made the gap more visible.&lt;/p&gt;

&lt;p&gt;That is why testing matters more now, not as endpoint checking after the fact, but as the place where exploration becomes verification, verification stays close to the code, and the same artifact survives local use, CI, debugging, and AI-assisted authoring.&lt;/p&gt;

&lt;p&gt;The old center was specification. The new center should be executable workflow.&lt;/p&gt;

&lt;p&gt;In the AI era, the source of truth is less likely to be a carefully maintained design document and more likely to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;working code&lt;/li&gt;
&lt;li&gt;runnable tests&lt;/li&gt;
&lt;li&gt;traces and real responses&lt;/li&gt;
&lt;li&gt;generated specs when needed&lt;/li&gt;
&lt;li&gt;verification that survives across local runs, CI, and debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We do not need less contract. We need less ceremony and more executable truth.&lt;/p&gt;

&lt;p&gt;That kind of workflow is what I think the ecosystem should be building toward: API exploration stays in code, useful discoveries become runnable checks, AI can help write and refactor them, and the same artifacts survive local runs, debugging, and CI.&lt;/p&gt;

&lt;p&gt;That is also the direction I want Glubean to push toward, not as another request sender, but as tooling built around that loop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6n7z9cpa4lmrcbo2ksu.gif" 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%2Fa6n7z9cpa4lmrcbo2ksu.gif" alt="Executable API workflow in VSCode" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That matters because, despite all the abstraction layers, the real boundary still looks a lot like HTTP: requests, responses, auth, retries, headers, payloads, failures, and state transitions between calls. If that boundary remains central, the workflow around it cannot stay this fragmented forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is time to care about API tooling again
&lt;/h2&gt;

&lt;p&gt;I do not think the conclusion is that we need another Swagger clone. And I do not think AI will make API tooling disappear.&lt;/p&gt;

&lt;p&gt;The conclusion is almost the opposite: it is time to take the API ecosystem seriously again.&lt;/p&gt;

&lt;p&gt;Not just as documentation, schema, generated code, or a request sender with a nicer UI, but as a workflow problem.&lt;/p&gt;

&lt;p&gt;A real API workflow should let you understand an API, explore it, keep the useful parts, turn them into verification, run them in different environments, inspect failures, and make that whole loop work better with AI instead of breaking under it.&lt;/p&gt;

&lt;p&gt;That is still missing.&lt;/p&gt;

&lt;p&gt;We did not stop talking about APIs because they became perfect. We stopped talking about them because we stopped expecting anything better.&lt;/p&gt;

&lt;p&gt;I think that is a mistake.&lt;/p&gt;

&lt;p&gt;The reason I am building Glubean is simple: it never felt good enough in the API ecosystem. Not the exploration workflow. Not the testing story. Not the handoff into automation.&lt;/p&gt;

&lt;p&gt;So I want to try one more time. Not because I think one new tool will magically fix everything, but because I do not think the community should give up on this problem.&lt;/p&gt;

</description>
      <category>api</category>
      <category>testing</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Why I Replaced Postman with a TypeScript Workflow in VSCode</title>
      <dc:creator>PeiSong</dc:creator>
      <pubDate>Mon, 23 Mar 2026 09:06:11 +0000</pubDate>
      <link>https://dev.to/peisongo/why-i-replaced-postman-with-a-typescript-workflow-in-vscode-14ll</link>
      <guid>https://dev.to/peisongo/why-i-replaced-postman-with-a-typescript-workflow-in-vscode-14ll</guid>
      <description>&lt;p&gt;Every time I work with an API, I go through two phases.&lt;/p&gt;

&lt;p&gt;First, I’m exploring — sending requests, checking responses, and figuring out how things actually behave. For years, this happened in Postman, or &lt;code&gt;curl&lt;/code&gt;, or a scratch file hidden somewhere.&lt;/p&gt;

&lt;p&gt;Then later, when I need to make sure things keep working, I move to a test file and rewrite the same requests from scratch. By that point, I’ve already forgotten half of what I learned during the exploration phase.&lt;/p&gt;

&lt;p&gt;I kept doing this until I realized the problem wasn't laziness — it's that these two phases happen in completely different tools with completely different formats. The exploration work is always throwaway.&lt;/p&gt;

&lt;p&gt;That’s the gap I wanted to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "UI-First" Exploration
&lt;/h2&gt;

&lt;p&gt;Yes, Postman already solves part of this. If all you want is to send a request and inspect the response, it works.&lt;/p&gt;

&lt;p&gt;But once the workflow gets more real, I want the thing I write during exploration to already be code: code in git, code I can review in a PR, code that can use normal npm packages, and code that runs in CLI and CI later.&lt;/p&gt;

&lt;p&gt;That matters even more now because code works much better with AI tools. An agent can generate, edit, and refactor TypeScript much more easily than it can maintain a click-through UI workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: exploration and verification should be the same workflow
&lt;/h2&gt;

&lt;p&gt;What if exploration and testing were the same thing? &lt;/p&gt;

&lt;p&gt;Not "export your Postman collection as tests." Not "record and replay." Just: write a request and assertion in a real code file, run it with one click, and keep it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6n7z9cpa4lmrcbo2ksu.gif" 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%2Fa6n7z9cpa4lmrcbo2ksu.gif" alt="Scratch mode demo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By using &lt;strong&gt;TypeScript&lt;/strong&gt; as the exploration tool, you get:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Full npm power:&lt;/strong&gt; Need a random UUID? &lt;code&gt;crypto.randomUUID()&lt;/code&gt;. Need a mock user? Use &lt;code&gt;faker&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Type Safety:&lt;/strong&gt; Your IDE tells you if you misspelled a header or a parameter before you even hit "Send."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Refactor-ability:&lt;/strong&gt; Want to change a URL across 50 tests? &lt;code&gt;Cmd+F&lt;/code&gt; and replace. That's just normal code — no special scripting sandbox needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I built: Glubean
&lt;/h2&gt;

&lt;p&gt;I built a workflow where the distance between "I'm just trying this" and "I should keep this in the repo" is much smaller.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One test, one click, one trace:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@glubean/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-user&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://dummyjson.com/users/1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've used Jest or Vitest in VSCode, you know the play button that appears next to each test. Same thing here — I click it, the test runs, and a &lt;strong&gt;Result Viewer&lt;/strong&gt; opens right inside VSCode. &lt;/p&gt;

&lt;p&gt;You see the method, URL, status code, and the full request/response body. It feels like Postman, but it's powered by the file you're actually editing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuj1qcxw6r4e0fwl1iz5.gif" 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%2Fnuj1qcxw6r4e0fwl1iz5.gif" alt="Explore mode — run tests and inspect results" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real workflow — shared state, steps, npm packages:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once I have something worth keeping, I click the file-level play button and get a real result report. This is also where the workflow stops looking like Postman.&lt;/p&gt;

&lt;p&gt;Instead of a saved request in a UI, I have normal TypeScript: I can use the builder API for a multi-step flow and pass state from one step to the next. If one step fails, the rest are skipped automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@glubean/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://dummyjson.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Simple test — fetch a known user&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-user&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/users/1`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Loaded user: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Builder test — reads shared userId, runs multi-step verification&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifyAndUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verify-and-update&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="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check user profile&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;originalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&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="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Update user name&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;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;originalName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Renamed &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;originalName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; → Updated`&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Tests in the same file share module scope and run in export order — &lt;code&gt;getUser&lt;/code&gt; runs first and sets &lt;code&gt;userId&lt;/code&gt; for &lt;code&gt;verifyAndUpdate&lt;/code&gt;. Shared variables should be declared at module level but assigned only inside test callbacks. See &lt;a href="https://docs.glubean.com/reference/limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt; for details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The builder's &lt;code&gt;.step()&lt;/code&gt; chain passes state between steps inside a single test. When I run the file, the result report shows each step's status and duration.&lt;/p&gt;

&lt;p&gt;This is just a small slice of what the SDK does — schema validation, data-driven execution, custom metrics, retries, plugin support for browser, GraphQL, gRPC, and more. But the point of this post is the workflow, not the feature list.&lt;/p&gt;

&lt;p&gt;The code I wrote while exploring was already real verification code. I didn't need to rewrite it or convert it into another format. That's the thing I was trying to get right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv020j36835hts0mdm0rm.gif" 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%2Fv020j36835hts0mdm0rm.gif" alt="Fix workflow — iterate until green" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters: The "Friction" Tax
&lt;/h2&gt;

&lt;p&gt;The real cost of the old workflow isn't just the time spent rewriting. It's the &lt;strong&gt;stuff you never bother to turn into tests&lt;/strong&gt; because the friction is too high.&lt;/p&gt;

&lt;p&gt;You tried an edge case during exploration (e.g., "What if the price is negative?"), it worked, you moved on. Two months later it breaks. Nobody catches it because that exploration lived in a &lt;code&gt;curl&lt;/code&gt; command buried in your terminal history or a temporary tab in Postman you forgot to save.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If trying something useful and keeping something useful are the same action, more of that work survives.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  From VSCode to CI
&lt;/h2&gt;

&lt;p&gt;Because it's just code, these files run in CLI and CI without any changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;glubean run tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't want one format for local exploration and another for automation. No more "exporting JSON" or "syncing collections." Your verification files live where they belong: in your repository, next to your source code.&lt;/p&gt;

&lt;p&gt;The same file runs in your terminal and CI — no conversion, no export:&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;# Same file, same assertions — now in your terminal or CI pipeline.&lt;/span&gt;
&lt;span class="c"&gt;# The result.json contains the exact same traces you see in the VSCode Result Viewer.&lt;/span&gt;
npx glubean run explore/dummyjson/smoke.test.ts &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;--emit-full-trace&lt;/span&gt; &lt;span class="nt"&gt;--result-json&lt;/span&gt; ./smoke.result.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open any &lt;code&gt;.result.json&lt;/code&gt; in VSCode and the Glubean extension renders it in the Result Viewer automatically — full trace inspection, assertions, and events, even when the test was run from the CLI.&lt;/p&gt;

&lt;p&gt;That broader path is part of what I like about this workflow: start by exploring in VSCode, keep the useful parts as committed verification, run the same files in CLI/CI later, and only think about Cloud when you want help understanding failures, tracking metrics over time, and getting notified when behavior changes.&lt;/p&gt;

&lt;p&gt;But this post is really about the first phase: exploration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is now
&lt;/h2&gt;

&lt;p&gt;I'm building this as &lt;a href="https://github.com/glubean/glubean" rel="noopener noreferrer"&gt;Glubean&lt;/a&gt; — a free and open source SDK + CLI + VSCode extension. The local workflow is free, and Cloud upload is optional. It's also designed so the platform does not need to know your secrets, but that deserves its own post. There's an AI angle too, but I don't want to overload this one: code-first exploration turns out to be a much better surface for AI authoring than click-through UI workflows, and I'll write about that separately.&lt;/p&gt;

&lt;p&gt;If you want to try it: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=glubean.glubean" rel="noopener noreferrer"&gt;Glubean VSCode extension&lt;/a&gt;. You may need to reload the VSCode window after installing.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;.test.js&lt;/code&gt; file (no project setup needed — just one file).&lt;/li&gt;
&lt;li&gt;Write a &lt;code&gt;ctx.http.get()&lt;/code&gt; and click the play button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For scratch mode limitations (TypeScript type errors, no &lt;code&gt;.env&lt;/code&gt; support), see &lt;a href="https://docs.glubean.com/reference/limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s the first step. Open a test file, write one request, and click play.&lt;/p&gt;

&lt;p&gt;If you want runnable examples instead of toy snippets, I also put together a &lt;a href="https://github.com/glubean/cookbook" rel="noopener noreferrer"&gt;cookbook repo&lt;/a&gt; with patterns you can run in VSCode after a quick &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you’re still bouncing between Postman and a test suite, I’d love to hear what keeps you there.&lt;/p&gt;

</description>
      <category>api</category>
      <category>typescript</category>
      <category>vscode</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
