<?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: Galih Putro Aji</title>
    <description>The latest articles on DEV Community by Galih Putro Aji (@galih_putroaji).</description>
    <link>https://dev.to/galih_putroaji</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%2F4113013%2F955ae32d-6a2a-47ac-a9a1-e276c6158818.png</url>
      <title>DEV Community: Galih Putro Aji</title>
      <link>https://dev.to/galih_putroaji</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/galih_putroaji"/>
    <language>en</language>
    <item>
      <title>A $40 Million Startup Built an AI That Can’t Chat. Three Days Later, Its Free Version Appeared</title>
      <dc:creator>Galih Putro Aji</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:38:41 +0000</pubDate>
      <link>https://dev.to/galih_putroaji/a-40-million-startup-built-an-ai-that-cant-chat-three-days-later-its-free-version-appeared-ked</link>
      <guid>https://dev.to/galih_putroaji/a-40-million-startup-built-an-ai-that-cant-chat-three-days-later-its-free-version-appeared-ked</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmoq867jbkj05mqzg2byo.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmoq867jbkj05mqzg2byo.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jev from TypeSafe and Laya from the community: what was promised, what was proven, and who actually came first.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On September 15, 2026, TypeSafe AI emerged from stealth with a &lt;a href="https://finance.yahoo.com/technology/ai/articles/typesafe-ai-emerges-stealth-40m-190000776.html" rel="noopener noreferrer"&gt;$40 million seed round led by DCVC&lt;/a&gt; and a product deliberately designed to be unable to write sentences. Its name is Jev. Three days later, an engineer from Kerala named Nandakishor M &lt;a href="https://dev.to/nandakishor_m_6cc0adfde9f/i-built-non-autoregressive-decision-models-a-year-ago-then-a-frontier-lab-called-it-a-18me"&gt;released Laya&lt;/a&gt;, an open-source model that mimics Jev’s API, and &lt;a href="https://github.com/NandhaKishorM/laya" rel="noopener noreferrer"&gt;its repository&lt;/a&gt; now has around 25,700 stars on GitHub. To &lt;a href="https://analyticsindiamag.com/ai-features/this-kerala-engineer-built-open-source-jev-alternative-a-year-before-the-hype" rel="noopener noreferrer"&gt;Analytics India Magazine&lt;/a&gt;, he claimed that he had been working on the idea since April 2025, while researching ways to detect hallucinations before an LLM finished responding.&lt;/p&gt;

&lt;p&gt;The story is easy to summarize: an expensive startup, copied by the community within days. The more honest version is messier. What was copied was the shape of the interface, not the model itself, and the Laya figures most frequently quoted come with footnotes that are rarely carried along with them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jev, an AI that only answers choices
&lt;/h3&gt;

&lt;p&gt;Ordinary language models receive text and respond with text. Jev receives a “state” (an email, JSON document) along with a list of typed questions, then returns answers that can be read directly by a program: a single choice, a score, or a yes/no probability, all accompanied by a confidence level. Its founder, Diogo Almeida, &lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" rel="noopener noreferrer"&gt;describes Jev&lt;/a&gt; as a “frontier-intelligence function call.”&lt;/p&gt;

&lt;p&gt;An example from Laya’s README, which uses the same format: a customer email complains about being charged twice and threatens to cancel the subscription unless refunded that day. The model answers “billing department,” plus a churn probability of around 89 percent. The decision threshold is in your code; above a certain number, the process runs automatically, while below it, it is routed to a human.&lt;/p&gt;

&lt;p&gt;TypeSafe’s claims are bold. The price is $0.042 per million input tokens with free output, response time is 70 to 500 milliseconds, and &lt;a href="https://typesafe.ai/" rel="noopener noreferrer"&gt;their homepage&lt;/a&gt; states that it is 193.6x faster and 444.6x cheaper. Their launch blog itself acknowledges that those figures come from four workflows created by the internal team that may be biased, and that such a low price has not yet been proven to be free of subsidies. The transparency is commendable, but the benchmark is still their own.&lt;/p&gt;

&lt;p&gt;The phrase “cannot hallucinate” needs to be read narrowly. &lt;a href="https://en.wowtale.net/2026/09/21/235190/" rel="noopener noreferrer"&gt;What it means&lt;/a&gt; is that the answer cannot fall outside the schema you define, not that the answer is guaranteed to be correct. A wrong answer can still arrive in a perfect format.&lt;/p&gt;

&lt;p&gt;Almeida previously worked on RLHF at OpenAI, and &lt;a href="https://www.forbes.com/sites/the-prompt/2026/09/15/this-200-million-startup-wants-to-fix-ais-overconfidence-problem/" rel="noopener noreferrer"&gt;Forbes&lt;/a&gt; reports that its seed valuation was around $200 million. TypeSafe trained Jev entirely on synthetic data and has not disclosed its architecture; &lt;a href="https://techcrunch.com/2026/09/18/a-new-kind-of-ai-model-from-a-chatgpt-inventor-is-thrilling-developers/" rel="noopener noreferrer"&gt;according to TechCrunch&lt;/a&gt;, outside observers suspect that there may be an open-weight LLM underneath.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laya: the same interface, a different engine
&lt;/h3&gt;

&lt;p&gt;Laya is a ModernBERT-large encoder with 421 million parameters and a decision head on top. All questions are answered in a single forward pass, taking around 33 to 40 milliseconds on a T4 GPU, licensed under Apache 2.0, and pip install laya is enough to try it. Its built-in server, laya-serve, exposes a POST /v1/systemone endpoint with the same request and response shape as Jev's API, &lt;a href="https://huggingface.co/convaiinnovations/laya" rel="noopener noreferrer"&gt;according to its model card&lt;/a&gt;, so a Jev client only needs to change the base URL.&lt;/p&gt;

&lt;p&gt;That is why the word “clone” is not quite accurate. Jev’s weights are closed and its architecture has not been published, while Laya is a standalone bidirectional encoder. What they share is only the concept and the API contract.&lt;/p&gt;

&lt;p&gt;The community’s enthusiasm is real: around 25.7 thousand stars, 2.2 thousand forks, and a model card with 4.12 thousand likes. Ports for Apple silicon and Huawei Ascend NPUs have already appeared. One example documented in the README: fine-tuning Laya for a browser agent increased top-1 element selection accuracy from 0.10 to 0.66.&lt;/p&gt;

&lt;h3&gt;
  
  
  The numbers that need to be read twice
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpi638jjve0seje3i35vj.jpeg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpi638jjve0seje3i35vj.jpeg" width="800" height="507"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Jev and Laya accuracy according to the comparison table in Laya’s README. Jev’s figures come from a third party&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The comparison table in the README places Laya above Jev on the typed-decisions benchmark: 0.766 versus 0.727. Read the footnote. The 0.766 figure belongs to a checkpoint that has already been fine-tuned on the benchmark’s own training split. Its base checkpoint only reaches 0.362, below the majority-class guess of 0.461, and the README itself states that Laya is a fast foundation for specialization, not a zero-shot decision engine. AG News is included in its training-data mixture. On Banking77, with dozens of labels, Jev leads by a wide margin: 0.870 versus 0.425.&lt;/p&gt;

&lt;p&gt;The Jev figures in that table are not measured by Laya; its developer cites them from third-party testing. One of them, a &lt;a href="https://github.com/AbdelStark/jev-benchmarks" rel="noopener noreferrer"&gt;300-example pilot by AbdelStark&lt;/a&gt; comparing Jev with the GLiNER2.5 model, provides a mixed picture. Jev wins decisively on AG News and Banking77. On the emotion dataset, its accuracy is only 0.480, and on 16 percent of examples it assigned zero probability to the correct label. Jev’s latency of 236 to 256 milliseconds in that pilot was measured from France over the internet, while Laya’s 33 milliseconds runs locally on a GPU. Part of the difference is simply network distance.&lt;/p&gt;

&lt;p&gt;Laya itself starts out with probabilities that are too overconfident. The average expected calibration error (ECE) of its default checkpoint is 0.466, and only falls to 0.081 after temperature fitting on held-out data. This means the confidence scores from the default version are not yet trustworthy before you tune them on your own data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who came first?
&lt;/h3&gt;

&lt;p&gt;Nandakishor claims that he published the idea a year before Jev through two arXiv papers, and accuses TypeSafe of launching the same concept without a paper, open weights, or a dataset. His first paper, &lt;a href="https://arxiv.org/abs/2503.23303" rel="noopener noreferrer"&gt;SalesRLAgent&lt;/a&gt;, predicts sales conversion probabilities using reinforcement learning and synthetic data from GPT-4o. &lt;a href="https://arxiv.org/abs/2510.01237" rel="noopener noreferrer"&gt;His second paper&lt;/a&gt; routes LLM queries based on estimated confidence before the model answers, and its experiments use SmolLM2–360M with 72 training examples.&lt;/p&gt;

&lt;p&gt;The two are aligned: a small model trained with RL to measure its own confidence. However, the text of the second paper does not contain typed questions based on choice, score, or boolean values, nor a reward based on a proper scoring rule, which is central to Laya. On Dev.to, Nandakishor describes the paper as an exact framework for schema-based decisions, and that description is broader than the paper itself.&lt;/p&gt;

&lt;p&gt;TypeSafe’s side is equally fragile. The basic construction — an encoder that scores labels provided only at inference time — has existed for years through NLI-based zero-shot classification and the GLiNER family of models since 2023. &lt;a href="https://akmaier.substack.com/p/laya-jev-and-the-return-of-the-discriminative" rel="noopener noreferrer"&gt;An analysis on Substack&lt;/a&gt; by akmaier concludes that both sides claim more originality than is supported by the public record. TypeSafe, according to &lt;a href="https://dev.to/jamilxt/jev-vs-laya-the-same-ai-idea-one-closed-and-one-open-3c6e"&gt;coverage&lt;/a&gt; through the end of September, has not responded publicly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The real test
&lt;/h3&gt;

&lt;p&gt;The idea belongs to no one. TypeSafe packaged it into a ready-to-use service without fine-tuning; Nandakishor showed how quickly such an API contract could be reproduced, and that half of its value only appears after the model is trained on your own data. Both can be true at the same time.&lt;/p&gt;

&lt;p&gt;If your ticketing or content-moderation workflow is already running on top of an LLM, do not stop at the README. Collect two hundred to three hundred real examples that you have already labeled, run both models, then calculate not just accuracy, but what percentage of decisions can be automated at a five-percent error threshold. That is the metric used by the AbdelStark pilot, and it determines how many tickets truly no longer need to be handled by a human. That number does not appear in any README.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Bottom Line
&lt;/h3&gt;

&lt;p&gt;Ultimately, the standoff between Jev and Laya is less about who holds the deed to discriminative decision architectures and more about a practical shift away from generic generative text toward strict schema reliability. While TypeSafe packages an instant, out-of-the-box pipeline for teams that want immediate results without fine-tuning, Laya demonstrates the agile power of open-source models ready to be specialized and calibrated directly on internal domain data. Instead of relying on headline-grabbing benchmarks or marketing claims, the real decision rests in your own operational pipeline: run a few hundred real examples through both, measure your risk thresholds, and see which one actually reduces human workload without breaking production&lt;/p&gt;




</description>
      <category>systemonemodel</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Why We Dumped Swagger UI for Scalar in Our Go Backend</title>
      <dc:creator>Galih Putro Aji</dc:creator>
      <pubDate>Mon, 07 Sep 2026 02:33:17 +0000</pubDate>
      <link>https://dev.to/galih_putroaji/why-we-dumped-swagger-ui-for-scalar-in-our-go-backend-1bfp</link>
      <guid>https://dev.to/galih_putroaji/why-we-dumped-swagger-ui-for-scalar-in-our-go-backend-1bfp</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5hrw5bef27e6iw8fy2a.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5hrw5bef27e6iw8fy2a.png" alt="Why We Dumped Swagger UI for Scalar in Our Go Backend" width="799" height="420"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Why We Dumped Swagger UI for Scalar in Our Go Backend&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few weeks ago, at 4:45 PM on a Friday (because of course it was Friday), our frontend engineer dropped a message on Slack:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Hey, why is the API playground on staging trying to send requests to localhost:8080? Did someone leave their dev server running on a laptop somewhere?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Silence in the channel…&lt;br&gt;&lt;br&gt;
That single innocent question exposed a dirty secret: our API documentation was broken, outdated, and living in an alternate universe.&lt;/p&gt;

&lt;p&gt;Like almost every Go developer over the last five years, we had faithfully followed the holy Go ritual:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write magic comments above handler functions like a medieval monk copying manuscripts (// &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; user_id path string true "User ID").&lt;/li&gt;
&lt;li&gt;Run swag init and pray we didn't miss a comma.&lt;/li&gt;
&lt;li&gt;Mount Swagger UI on /swagger/*.&lt;/li&gt;
&lt;li&gt;Congratulate ourselves on being “documentation champions.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It worked initially. But as our backend grew, that ritual turned into a comedy of errors.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Three Pain Points That Drove Us Crazy
&lt;/h3&gt;

&lt;p&gt;Before we threw Swagger UI out the window, we were dealing with three daily frustrations:&lt;/p&gt;
&lt;h4&gt;
  
  
  1. The “Doc Drift” Trap
&lt;/h4&gt;

&lt;p&gt;Your annotations live as comments above HTTP handlers. You refactor a Go struct inside a domain package, but forget to update the 15 lines of magic comments floating above your handler function.&lt;/p&gt;

&lt;p&gt;Result? Your docs claim the endpoint returns a plain string, but your Go code returns a nested JSON object. Your frontend team spends two hours debugging phantom errors while you sip coffee completely unaware.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. The 2015-Era UI Experience
&lt;/h4&gt;

&lt;p&gt;Let’s be honest: Swagger UI looks like it was designed when Internet Explorer 8 was still a thing. Testing JWT-authenticated endpoints, reading nested JSON error schemas, and searching across 30+ endpoints felt like navigating a government tax portal.&lt;/p&gt;
&lt;h4&gt;
  
  
  3. The Hardcoded Host Nightmare
&lt;/h4&gt;

&lt;p&gt;Our generated spec had host: "localhost:8080" baked inside it. When QA opened the docs on staging (&lt;a href="https://staging-api.example.com/docs" rel="noopener noreferrer"&gt;https://staging-api.example.com/docs&lt;/a&gt;) to test a release, every click on "Try it out" silently fired HTTP calls to localhost:8080.&lt;/p&gt;

&lt;p&gt;Cue 30 minutes of classic “It works on my machine!” debates.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enter Scalar: Single Source of Truth + Zero Build Overhead
&lt;/h3&gt;

&lt;p&gt;We decided we didn’t want another 50MB npm build step in our CI pipeline, nor did we want complex static asset embedding in our Go binary. We wanted something dead simple, ridiculously sleek, and impossible to break.&lt;/p&gt;

&lt;p&gt;That’s when we dumped Swagger UI and switched to  &lt;strong&gt;Scalar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of scattering annotations across 40 Go files, we made a radical move: a single api/openapi/openapi.yaml file as the &lt;strong&gt;one true source of truth&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;go-backend/
├── api/
│ └── openapi/
│ └── openapi.yaml &amp;lt;-- One True Source of Truth
├── internal/
│ └── delivery/
│ └── http/
│ ├── handler/
│ │ ├── docs.go &amp;lt;-- Serves Scalar HTML &amp;amp; openapi.yaml
│ │ └── docs_test.go &amp;lt;-- Contract &amp;amp; security tests
│ └── router.go &amp;lt;-- Environment guardrails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scalar runs as a modern, zero-build web component (@scalar/api-reference) straight from a CDN. No Node.js, no Webpack, no build step.&lt;/p&gt;

&lt;p&gt;All our Go backend needs to do is serve a 15-line HTML shell and the raw YAML file in internal/delivery/http/handler/docs.go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;DocsHandler&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;openAPIPath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewDocsHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openAPIPath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;DocsHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;openAPIPath&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;openAPIPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"api/openapi/openapi.yaml"&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;DocsHandler&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;openAPIPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;openAPIPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// RenderUI serves the Scalar API reference HTML shell&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;DocsHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RenderUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;`&amp;lt;!doctype html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;API Reference&amp;lt;/title&amp;gt;
    &amp;lt;meta charset="utf-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1" /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!-- Scalar CDN script integration --&amp;gt;
    &amp;lt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;`script id="api-reference" data-url="/docs/openapi.yaml"&amp;gt;&amp;lt;/`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;`script&amp;gt;
    &amp;lt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;`script src="&amp;lt;https://cdn.jsdelivr.net/npm/@scalar/api-reference&amp;gt;"&amp;gt;&amp;lt;/`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;`script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;`&lt;/span&gt;
 &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HeaderContentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIMETextHTMLCharsetUTF8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ServeSpec serves the raw OpenAPI YAML file&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;DocsHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ServeSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openAPIPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"OpenAPI specification not found"&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="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HeaderContentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/yaml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&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;Notice data-url="/docs/openapi.yaml": the browser automatically fetches the contract relative to whatever host served the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Secret Sauce: Relative Routing (url: /api)
&lt;/h3&gt;

&lt;p&gt;The real magic that cured our staging headaches was replacing full domain URLs in openapi.yaml with a single relative path:&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="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.3&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Go Service API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Same-origin API server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By defining url: /api, Scalar's HTTP client attaches test requests directly to whichever domain is currently in the address bar:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7w3m9bv8ir1npmwkrdf8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7w3m9bv8ir1npmwkrdf8.png" alt="image2" width="799" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No environment variables, no YAML templating hackery, and zero CORS errors on staging. It just works because the browser already knows where it is!&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping Production Locked Down (404 for Unexpected Visitors)
&lt;/h3&gt;

&lt;p&gt;As much as we love interactive API playgrounds, we don’t want bored internet scanners getting a free interactive map of our internal endpoints in production.&lt;/p&gt;

&lt;p&gt;In internal/delivery/http/router.go, we locked the /docs route behind an environment check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RegisterRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docsHandler&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DocsHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="c"&gt;// ... register auth, user, referee routes ...&lt;/span&gt;

 &lt;span class="c"&gt;// Security Guardrail: Mount /docs only in dev, test, and staging.&lt;/span&gt;
 &lt;span class="c"&gt;// In production, /docs returns a polite 404 Not Found.&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppEnv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;docsGroup&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/docs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;docsGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docsHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RenderUI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;docsGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/openapi.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docsHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeSpec&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="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"production"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"prod"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an automated vulnerability scanner hits GET /docs on production, Fiber responds with a standard 404 Not Found. Move along, nothing to see here!&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying It All in 5 Milliseconds
&lt;/h3&gt;

&lt;p&gt;To make sure nobody accidentally breaks our routing or leaks docs to production during a late-night refactor, we wrote a quick unit test (internal/delivery/http/handler/docs_test.go):&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;func TestDocsOpenAPISpec_UsesRelativeAPIServer(t *testing.T) {
 app := fiber.New()
 docsHandler := handler.NewDocsHandler("../../../../api/openapi/openapi.yaml")
 app.Get("/docs/openapi.yaml", docsHandler.ServeSpec)

 req := httptest.NewRequest(http.MethodGet, "/docs/openapi.yaml", nil)
 resp, err := app.Test(req)
 if err != nil || resp.StatusCode != http.StatusOK {
  t.Fatalf("expected 200 OK, got %d", resp.StatusCode)
 }

 buf := make([]byte, 1024)
 n, _ := resp.Body.Read(buf)
 content := string(buf[:n])

 if !strings.Contains(content, "url: /api") {
  t.Errorf("expected openapi spec to define relative server 'url: /api', got:\n%s", content)
 }
}

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./internal/delivery/http/handler &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt; &lt;span class="s2"&gt;"TestDocs"&lt;/span&gt;
&lt;span class="go"&gt;=== RUN TestDocsRoutes_NonProduction_Serves200
--- PASS: TestDocsRoutes_NonProduction_Serves200 (0.00s)
=== RUN TestDocsOpenAPISpec_UsesRelativeAPIServer
--- PASS: TestDocsOpenAPISpec_UsesRelativeAPIServer (0.00s)
PASS
ok go-backend/internal/delivery/http/handler    0.004s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Moral of the Story
&lt;/h3&gt;

&lt;p&gt;Switching from Swagger UI to Scalar delivered three huge wins for our team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero Doc Drift&lt;/strong&gt; : PRs review openapi.yaml directly—no more hidden annotation bugs inside Go handler comments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Build Overhead&lt;/strong&gt; : Scalar runs in the browser via CDN web component. No npm dependencies or binary bloat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Happier Frontend Devs&lt;/strong&gt; : Staging playgrounds actually work, and the UI feels like a modern developer tool built in this decade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team is still wrestling with broken Swagger playgrounds or 2015-era UI, dumping Swagger for Scalar is easily one of the highest-ROI Developer Experience (DX) wins you can score in an afternoon.&lt;/p&gt;

</description>
      <category>fiber</category>
      <category>softwaredevelopment</category>
      <category>api</category>
      <category>devrel</category>
    </item>
  </channel>
</rss>
