<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged http</title>
<link>https://coderlegion.com/tag/http</link>
<description>Powered by Question2Answer</description>
<item>
<title>Debug HTTP 422 API Validation Errors Safely</title>
<link>https://coderlegion.com/31006/debug-http-422-api-validation-errors-safely</link>
<description>&lt;p&gt;When an API returns an HTTP 422 response, client integrations often discard field-level details or dump entire response bodies into logs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick solution:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;async function readAndFormat422(response: Response): Promise&amp;lt;string&amp;gt; {
  const raw = await response.text();
  try {
    const body = JSON.parse(raw);
    const errors = body.errors;
    if (errors &amp;amp;&amp;amp; typeof errors === &quot;object&quot;) {
      return Object.entries(errors)
        .slice(0, 10)
        .map(([field, val]) =&amp;gt; field + &quot;: &quot; + String(val))
        .join(&quot;; &quot;);
    }
    return body.message || &quot;Unprocessable content&quot;;
  } catch {
    return raw.slice(0, 200);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTTP 422 is one of those status codes that is specific enough to sound useful and vague enough to waste an hour. The server understood the request content type and syntax, but could not process the instructions in the payload.&lt;/p&gt;
&lt;h3 id=&quot;what-http-422-actually-tells-you&quot;&gt;What HTTP 422 actually tells you&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc9110.html#name-422-unprocessable-content&quot;&gt;RFC 9110&lt;/a&gt; defines 422 Unprocessable Content as a case where the server understands the content type and the syntax is correct, but cannot process the contained instructions. &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/422&quot;&gt;MDN’s 422 reference&lt;/a&gt; summarizes the status as well. Because providers return inconsistent error-body schemas, your client must handle varied shapes safely without assuming a universal structure.&lt;/p&gt;
&lt;h3 id=&quot;the-anti-patterns-of-error-handling&quot;&gt;The anti-patterns of error handling&lt;/h3&gt;
&lt;p&gt;Catching non-2xx responses by keeping only the status code strips out all valuable context, turning detailed validation failures into generic messages. Conversely, logging the raw response text directly often leaks user input, tokens, or unexpected internal details into your observability stack.&lt;/p&gt;
&lt;h3 id=&quot;redacting-and-bounding-output&quot;&gt;Redacting and bounding output&lt;/h3&gt;
&lt;p&gt;Always bound character lengths and field counts to prevent memory bloat or log floods, and sanitize sensitive patterns like bearer tokens before persistence. For related architectural workflows, consult &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/designing-a-reliable-serverless-ai-publishing-workflow/&quot;&gt;Designing a Reliable Serverless AI Publishing Workflow&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/building-a-reliable-batch-publishing-workflow/&quot;&gt;Building a Reliable Batch Publishing Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;verification-checklist&quot;&gt;Verification checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Read error bodies exactly once as text before parsing.&lt;/li&gt;
&lt;li&gt;Enforce structural limits on extracted error fields.&lt;/li&gt;
&lt;li&gt;Sanitize output strings for authorization tokens.&lt;/li&gt;
&lt;li&gt;Confirm 422 responses do not trigger infinite retry loops.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31006/debug-http-422-api-validation-errors-safely</guid>
<pubDate>Wed, 14 Oct 2026 03:03:25 +0000</pubDate>
</item>
<item>
<title>HTTP Just Got a New Method After 16 Years—Meet QUERY. </title>
<link>https://coderlegion.com/27817/http-just-got-a-new-method-after-16-years-meet-query</link>
<description>&lt;p&gt;&lt;strong&gt; The New HTTP QUERY Method: The First New HTTP Method in 16 Years&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What if you could have the simplicity of GET with the flexibility of POST? That's exactly what the new HTTP QUERY method aims to deliver.&lt;/p&gt;
&lt;p&gt;If you've built REST APIs, you've probably faced this situation before.&lt;/p&gt;
&lt;p&gt;You need to fetch data from your server, so naturally, GET seems like the right choice. But then your filters become more complex. You need nested objects, multiple conditions, sorting, pagination, maybe even advanced search criteria. Suddenly, your URL starts looking like a puzzle.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /orders?status=paid&amp;amp;customer=Ravi&amp;amp;from=2026-01-01&amp;amp;to=2026-06-30&amp;amp;sort=-amount&amp;amp;page=2&amp;amp;include=products,shipping,payments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As the application grows, that URL only gets longer and harder to manage.&lt;/p&gt;
&lt;p&gt;Most developers eventually switch to POST for search endpoints, even though they're only retrieving data.&lt;/p&gt;
&lt;p&gt;For years, this has been one of the awkward compromises in REST API design.&lt;/p&gt;
&lt;p&gt;That changes with the new &lt;strong&gt;QUERY&lt;/strong&gt; HTTP method, standardized in &lt;strong&gt;RFC 10008&lt;/strong&gt; (June 2026).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3fymeosu9lavyo325l4r.png&quot; alt=&quot;HTTP year wise Discovered&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-was-query-needed-&quot;&gt;Why Was QUERY Needed?&lt;/h3&gt;
&lt;p&gt;Let's understand the problem first.&lt;/p&gt;
&lt;p&gt;Imagine you're building an e-commerce platform.&lt;/p&gt;
&lt;p&gt;A user wants to search orders based on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Customer name&lt;/li&gt;
&lt;li&gt;Order status&lt;/li&gt;
&lt;li&gt;Date range&lt;/li&gt;
&lt;li&gt;Price range&lt;/li&gt;
&lt;li&gt;Multiple product categories&lt;/li&gt;
&lt;li&gt;Sort order&lt;/li&gt;
&lt;li&gt;Pagination&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With &lt;strong&gt;GET&lt;/strong&gt;, everything has to fit inside the URL.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /orders?customer=Ravi&amp;amp;status=paid&amp;amp;category=laptop&amp;amp;category=gaming&amp;amp;page=3&amp;amp;sort=-price
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works...&lt;/p&gt;
&lt;p&gt;...until it doesn't.&lt;/p&gt;
&lt;p&gt;As the filters become more advanced, URLs become difficult to read, difficult to encode correctly, and can even exceed practical length limits imposed by browsers, servers, or proxies.&lt;/p&gt;
&lt;p&gt;Another downside is that query parameters often appear in logs, browser history, and analytics systems, making them less suitable for sensitive search criteria.&lt;/p&gt;
&lt;p&gt;Clearly, developers needed a better solution.&lt;/p&gt;
&lt;h3 id=&quot;why-not-just-use-post-&quot;&gt;Why Not Just Use POST?&lt;/h3&gt;
&lt;p&gt;This is exactly what many APIs do today.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;POST /orders/search
Content-Type: application/json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;`{&lt;br&gt;
  &quot;customer&quot;: &quot;Ravi&quot;,&lt;br&gt;
  &quot;status&quot;: &quot;paid&quot;,&lt;br&gt;
  &quot;categories&quot;: [&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;Laptop&quot;,
&quot;Gaming&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  ],&lt;br&gt;
  &quot;price&quot;: {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;min&quot;: 500,
&quot;max&quot;: 2000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  },&lt;br&gt;
  &quot;page&quot;: 3&lt;br&gt;
}`&lt;/p&gt;
&lt;p&gt;The request body solves many problems.&lt;/p&gt;
&lt;p&gt;It's clean.&lt;/p&gt;
&lt;p&gt;It's readable.&lt;/p&gt;
&lt;p&gt;It supports complex JSON.&lt;/p&gt;
&lt;p&gt;So &lt;strong&gt;what's wrong?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The issue isn't technical.&lt;/p&gt;
&lt;p&gt;It's semantic.&lt;/p&gt;
&lt;p&gt;POST traditionally represents an operation that may change server state.&lt;/p&gt;
&lt;p&gt;Searching for data doesn't.&lt;/p&gt;
&lt;p&gt;Using POST for read-only operations has always been a compromise because HTTP infrastructure can't safely assume the request is cacheable or automatically retryable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/sg56ca07gii5kfjbyyhx.png&quot; alt=&quot;Comparison&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;meet-query&quot;&gt;Meet QUERY&lt;/h3&gt;
&lt;p&gt;QUERY combines the strengths of both GET and POST.&lt;/p&gt;
&lt;p&gt;Instead of placing filters in the URL, you send them inside the request body.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;QUERY /orders
Content-Type: application/json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;`{&lt;br&gt;
  &quot;customer&quot;: &quot;Ravi&quot;,&lt;br&gt;
  &quot;status&quot;: &quot;paid&quot;,&lt;br&gt;
  &quot;categories&quot;: [&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;Laptop&quot;,
&quot;Gaming&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  ],&lt;br&gt;
  &quot;price&quot;: {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;min&quot;: 500,
&quot;max&quot;: 2000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  }&lt;br&gt;
}`&lt;/p&gt;
&lt;p&gt;Unlike POST, &lt;strong&gt;QUERY&lt;/strong&gt; is explicitly defined as a safe and idempotent method.&lt;/p&gt;
&lt;p&gt;That means everyone involved—the client, server, proxy, and cache—knows that executing the request will &lt;strong&gt;not modify server data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This makes &lt;strong&gt;QUERY&lt;/strong&gt; a much better fit for complex searches.&lt;/p&gt;
&lt;h3 id=&quot;what-does-idempotent-actually-mean-&quot;&gt;What Does &quot;Idempotent&quot; Actually Mean?&lt;/h3&gt;
&lt;p&gt;You'll often hear this word when talking about HTTP methods.&lt;/p&gt;
&lt;p&gt;An operation is idempotent if performing it multiple times has the same effect as performing it once.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;GET /users/42&lt;/p&gt;
&lt;p&gt;Calling this request one time or ten times returns the same user.&lt;/p&gt;
&lt;p&gt;Now compare that to:&lt;/p&gt;
&lt;p&gt;POST /users&lt;/p&gt;
&lt;p&gt;Calling it twice could create two different users.&lt;/p&gt;
&lt;p&gt;That's why POST isn't considered idempotent.&lt;/p&gt;
&lt;p&gt;QUERY behaves like GET in this regard.&lt;/p&gt;
&lt;p&gt;If the network connection drops halfway through the response, a client can safely retry the QUERY request without worrying about accidentally changing server data.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/x6kctbe3xzhr1xzp1ecc.png&quot; alt=&quot;Query Flow&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;The QUERY method brings several advantages:&lt;/p&gt;
&lt;p&gt;✅ Supports complex request bodies&lt;br&gt;
✅ Safe for read-only operations&lt;br&gt;
✅ Idempotent&lt;br&gt;
✅ Easier for caching systems to understand&lt;br&gt;
✅ Better HTTP semantics&lt;/p&gt;
&lt;p&gt;Instead of inventing custom search endpoints like:&lt;/p&gt;
&lt;p&gt;POST /users/search&lt;br&gt;
POST /orders/filter&lt;br&gt;
POST /products/query&lt;/p&gt;
&lt;p&gt;Applications can eventually expose cleaner APIs using QUERY.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-example&quot;&gt;A Practical Example&lt;/h3&gt;
&lt;p&gt;Imagine you're building an analytics dashboard.&lt;/p&gt;
&lt;p&gt;Today's API might look like this:&lt;/p&gt;
&lt;p&gt;POST /analytics/report&lt;/p&gt;
&lt;p&gt;Request body:&lt;/p&gt;
&lt;p&gt;`{&lt;br&gt;
  &quot;dateRange&quot;: {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;from&quot;: &quot;2026-01-01&quot;,
&quot;to&quot;: &quot;2026-06-30&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  },&lt;br&gt;
  &quot;groupBy&quot;: &quot;country&quot;,&lt;br&gt;
  &quot;metrics&quot;: [&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;sales&quot;,
&quot;profit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  ]&lt;br&gt;
}`&lt;/p&gt;
&lt;p&gt;The request doesn't create anything.&lt;/p&gt;
&lt;p&gt;It simply retrieves data.&lt;/p&gt;
&lt;p&gt;QUERY expresses that intent much more clearly.&lt;/p&gt;
&lt;p&gt;QUERY /analytics/report&lt;/p&gt;
&lt;p&gt;Same body.&lt;/p&gt;
&lt;p&gt;Better semantics.&lt;/p&gt;
&lt;h3 id=&quot;what-about-spring-boot-and-other-frameworks-&quot;&gt;What About Spring Boot and Other Frameworks?&lt;/h3&gt;
&lt;p&gt;If you're a Java developer like me, you've probably written endpoints such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@PostMapping(&quot;/search&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;even though they're only returning data.&lt;/p&gt;
&lt;p&gt;At the time of writing, most frameworks—including Spring Boot, Express, FastAPI, ASP.NET Core, and many API gateways—don't yet provide first-class support for QUERY because the standard is still very new.&lt;/p&gt;
&lt;p&gt;As ecosystem support grows over the next few years, it will be interesting to see how frameworks adopt it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/2cbcahba06pi2e3903av.png&quot; alt=&quot;Adoptation&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;should-you-use-query-today-&quot;&gt;Should You Use QUERY Today?&lt;/h3&gt;
&lt;p&gt;Probably not in production.&lt;/p&gt;
&lt;p&gt;Although the method has now been standardized, widespread support is still limited.&lt;/p&gt;
&lt;p&gt;Many HTTP clients, frameworks, reverse proxies, API gateways, and developer tools have yet to implement it.&lt;/p&gt;
&lt;p&gt;For now, the common approach remains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;GET&lt;/strong&gt; for simple resource retrieval.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;POST&lt;/strong&gt; when you need complex search bodies and QUERY isn't available.&lt;/li&gt;
&lt;li&gt;Keep an eye on framework support as the ecosystem evolves.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HTTP&lt;/strong&gt; has remained remarkably stable over the years, and that's a good thing.&lt;/p&gt;
&lt;p&gt;But there has always been a missing piece between GET and POST.&lt;/p&gt;
&lt;p&gt;QUERY fills that gap by providing a standard way to perform &lt;strong&gt;complex, read-only&lt;/strong&gt; requests without sacrificing HTTP semantics.&lt;/p&gt;
&lt;p&gt;Will it replace &lt;strong&gt;GET&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Will it replace &lt;strong&gt;POST&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Not entirely.&lt;/p&gt;
&lt;p&gt;Instead, it gives developers a better tool for scenarios where neither GET nor POST feels quite right.&lt;/p&gt;
&lt;p&gt;It may take a few years before QUERY becomes common in production APIs, but understanding it today will help you design better APIs tomorrow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What do you think about the new QUERY method?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Would you use it once your favorite framework supports it? Let me know in the comments!&lt;/p&gt;
&lt;p&gt;Thanks for reading! If you enjoyed this article, consider leaving a ❤️ and following me here on DEV Community for more content on Java, Spring Boot, backend engineering, and modern web technologies.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27817/http-just-got-a-new-method-after-16-years-meet-query</guid>
<pubDate>Sun, 20 Sep 2026 09:21:23 +0000</pubDate>
</item>
<item>
<title>Building an HTTP Server From Scratch using TCP.</title>
<link>https://coderlegion.com/27310/building-an-http-server-from-scratch-using-tcp</link>
<description>&lt;p&gt;HTTP is something I interact with every single day, whether I am fetching data on the frontend or building backend APIs. But for a long time, if you had asked me how it actually worked under the hood, I wouldn't have had a good answer. I always treated HTTP like this intimidating, hyper-complex black box that would be miserable to understand, let alone rebuild from scratch. Once I actually dug into the internals, though, I realized the core mechanics are surprisingly straightforward.&lt;/p&gt;
&lt;p&gt;I am using Go for this project, but the concepts are entirely language-agnostic. You can follow along in C, Rust, Python, Node, or whatever language you like; as long as it can open a network socket (sorry, HTML and CSS won't cut it here).&lt;/p&gt;
&lt;p&gt;To understand HTTP, we first have to talk about TCP/IP, because HTTP is just a text-protocol riding on top of &lt;strong&gt;TCP/IP&lt;/strong&gt; stack.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Internet Protocol (IP)&lt;/strong&gt; has one basic job: move raw data from Machine A to Machine B across the world. But it doesn't dump a massive file across the wire all at once. If you download a 10 GB file, IP chops that data into tiny pieces called packets, usually around 1,500 bytes each. It does this because network hardware has physical transmission limits, routers need to share bandwidth fairly among thousands of users, and resending one dropped 1.5 KB packet over flaky Wi-Fi is painless compared to re-downloading an entire 10 GB stream.&lt;/p&gt;
&lt;p&gt;Every device on the internet gets an IP address so these packets know where to go. When you want to visit &lt;code&gt;google.com&lt;/code&gt;, your computer doesn't magically know Google's physical server address right away. It first asks a DNS server to translate &lt;code&gt;google.com&lt;/code&gt; into an actual IP address, like &lt;code&gt;142.250.190.46&lt;/code&gt;. Once your machine has that destination IP, it stamps its own IP address into the packet header so Google knows where to send the reply, and fires the packets into the wild.&lt;/p&gt;
&lt;p&gt;The catch is that IP is completely &quot;best-effort.&quot; It launches packets into the network and immediately stops caring. If a router gets overloaded and drops your packet, or if packets take different routes and arrive completely out of order, IP won't fix it.&lt;/p&gt;
&lt;p&gt;That is why we need &lt;strong&gt;TCP (Transmission Control Protocol)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;TCP sits right on top of IP to turn that chaotic packet delivery into a reliable stream. It tracks every byte with sequence numbers so out-of-order data gets assembled correctly. If a packet goes missing, TCP notices and asks the sender to retransmit it. It also introduces the concept of ports; so when data finally arrives at a computer's IP address, the operating system knows whether to hand those bytes to your web server on port 80 or your database on port 5432.&lt;/p&gt;
&lt;p&gt;So now TCP has solved our biggest headache. We have a solid, reliable pipe open between our client and our server. You throw bytes into one end, and they pop out the other end in the exact right order without getting lost.&lt;/p&gt;
&lt;p&gt;Problem solved, right? Not even close.&lt;/p&gt;
&lt;p&gt;Because while TCP is great at moving bytes from one place to another reliably, it is completely blind to what those bytes actually &lt;em&gt;mean&lt;/em&gt;. It treats everything as one never-ending, continuous river of data (a raw byte stream). If your browser sends a request to load a profile picture, and immediately sends another request for a stylesheet, TCP just mashes all those bytes together in a single stream.&lt;/p&gt;
&lt;p&gt;Your server is now sitting there staring at a raw chunk of bytes, completely clueless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where does the first request end and the second one begin?&lt;/li&gt;
&lt;li&gt;Is the client trying to download a file, submit a form, or delete a record?&lt;/li&gt;
&lt;li&gt;Is this data plain text, an image, or JSON?&lt;/li&gt;
&lt;li&gt;If something goes wrong on the server, how do we tell the client?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we didn't have a standard rulebook, every single developer would invent their own chaotic format. You'd write your own custom protocol where maybe you put an exclamation mark at the end of a message, while someone else uses a random binary flag. Your backend wouldn't be able to talk to any standard browser because neither speaks the same language.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;HTTP (Hypertext Transfer Protocol)&lt;/strong&gt; steps in.&lt;/p&gt;
&lt;p&gt;HTTP is nothing more than an agreed-upon rulebook. If TCP is a telephone line connecting two people, HTTP is the grammar they agree to speak so they understand each other.&lt;/p&gt;
&lt;p&gt;At its core, HTTP turns that blind stream of TCP bytes into predictable, structured messages. In HTTP/1.1, it does this entirely using plain text:&lt;/p&gt;
&lt;p&gt;First, it forces the client to state its intent right on the very first line: like &lt;code&gt;GET /index.html HTTP/1.1&lt;/code&gt;. Now the server instantly knows the action (&lt;code&gt;GET&lt;/code&gt;), the target (&lt;code&gt;/index.html&lt;/code&gt;), and the protocol version.&lt;/p&gt;
&lt;p&gt;Next, it uses standard key-value headers separated by clean line breaks; specifically &lt;code&gt;\r\n&lt;/code&gt; (CRLF: Carriage Return + Line Feed). Why two characters instead of just &lt;code&gt;\n&lt;/code&gt;? Because early internet protocols inherited typewriter conventions from telegraph and terminal days, and now we are stuck with it forever.&lt;/p&gt;
&lt;p&gt;Then, it solves the boundary problem with an empty line (&lt;code&gt;\r\n\r\n&lt;/code&gt;), which screams to the parser: &lt;em&gt;&quot;Hey, the headers are done! Whatever comes next is the actual body payload.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Finally, the server replies with a standardized response that includes a status code like &lt;code&gt;200 OK&lt;/code&gt; if everything went well, or &lt;code&gt;404 Not Found&lt;/code&gt; if you asked for something that doesn't exist.&lt;/p&gt;
&lt;p&gt;That's all HTTP really is. It's not magic, and it's not an intimidating engine. It's just a structured text format running over a raw TCP socket. Once you realize it's just plain text over a byte stream, building one yourself becomes a whole lot less scary.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-does-raw-http-actually-look-like-&quot;&gt;What Does Raw HTTP Actually Look Like?&lt;/h4&gt;
&lt;p&gt;Before we write the code to parse requests and generate responses, let's look at the exact text format traveling across the wire.&lt;/p&gt;
&lt;h5 id=&quot;1-the-http-request-format&quot;&gt;1. The HTTP Request Format&lt;/h5&gt;
&lt;p&gt;When a client wants something from our server, it sends a plain-text payload formatted like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POST /users HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.0.0
Accept: */*
Content-Type: application/json
Content-Length: 26

{&quot;name&quot;: &quot;Dev&quot;, &quot;age&quot;: 22}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Line 1 (Request Line):&lt;/strong&gt; Action (&lt;code&gt;POST&lt;/code&gt;), path (&lt;code&gt;/users&lt;/code&gt;), and version (&lt;code&gt;HTTP/1.1&lt;/code&gt;), terminated by &lt;code&gt;\r\n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 2–6 (Headers):&lt;/strong&gt; Key-value metadata lines, each terminated by &lt;code&gt;\r\n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line 7 (Empty Line):&lt;/strong&gt; A single blank &lt;code&gt;\r\n&lt;/code&gt; with no characters. This tells our server: &lt;em&gt;&quot;The headers are done.&quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line 8 (Body Payload):&lt;/strong&gt; Exactly 26 raw bytes of data matching the &lt;code&gt;Content-Length&lt;/code&gt; header.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;2-the-http-response-format&quot;&gt;2. The HTTP Response Format&lt;/h5&gt;
&lt;p&gt;Once our server finishes processing, it writes back an answer formatted like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 35

{&quot;message&quot;: &quot;user list endpoint&quot;}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Line 1 (Status Line):&lt;/strong&gt; Version (&lt;code&gt;HTTP/1.1&lt;/code&gt;), status number (&lt;code&gt;200&lt;/code&gt;), and status message (&lt;code&gt;OK&lt;/code&gt;), followed by &lt;code&gt;\r\n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lines 2–3 (Headers):&lt;/strong&gt; Key-value details about what we are sending back, terminated by &lt;code&gt;\r\n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line 4 (Empty Line):&lt;/strong&gt; A single blank &lt;code&gt;\r\n&lt;/code&gt; to mark the end of response headers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line 5 (Body Payload):&lt;/strong&gt; Exactly 35 raw bytes of data sent down the wire.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we know the format for both directions, let's build the server to handle it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;building-an-http-server-from-raw-tcp&quot;&gt;Building an HTTP Server from Raw TCP&lt;/h3&gt;
&lt;p&gt;I like to break the implementation down into four distinct steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Initialize a TCP socket and bind it to a local port.&lt;/li&gt;
&lt;li&gt;Accept incoming client connections in a loop.&lt;/li&gt;
&lt;li&gt;Read raw bytes from the socket and parse the HTTP request.&lt;/li&gt;
&lt;li&gt;Construct an HTTP response and write those bytes back over the wire.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;1-setting-up-the-tcp-listener&quot;&gt;1. Setting Up the TCP Listener&lt;/h4&gt;
&lt;p&gt;In any programming language, listening for network traffic comes down to a few basic steps. You ask the operating system to reserve a port (like 8080), and then you wait for someone to connect.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
&quot;fmt&quot;
&quot;log&quot;
&quot;net&quot;
)

func main() {
// Ask the OS to open port 8080 and listen for incoming traffic
listener, err := net.Listen(&quot;tcp&quot;, &quot;:8080&quot;)
if err != nil {
log.Fatal(&quot;[SERVER] Failed to bind to port: &quot;, err)
}
// Make sure we release the port when the server stops
defer listener.Close()

fmt.Println(&quot;[SERVER] Listening on port :8080...&quot;)

// Keep the server running in an infinite loop to accept other connections
for {
// Our program pauses right here until a client connects
conn, err := listener.Accept()
if err != nil {
fmt.Printf(&quot;[SERVER] Failed to accept connection: %v\n&quot;, err)
continue
}

// Pass the connection to a background worker so the loop can keep spinning
go handleConnection(conn)
}
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;what-is-happening-here-&quot;&gt;What is happening here?&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Binding:&lt;/strong&gt; The operating system locks port 8080 for us. From now on, any data sent to this port comes straight to our app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accepting:&lt;/strong&gt; Our program pauses and waits. When a client finally connects, the operating system wakes us up and hands us a connection object (&lt;code&gt;conn&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency:&lt;/strong&gt; If we try to process this connection right here in the main loop, our server will freeze for everyone else. So, we hand the connection off to run in the background. Go uses &lt;code&gt;goroutines&lt;/code&gt;, Python might use threads, and Node uses its event loop. The idea is exactly the same: move the work out of the way so we can instantly wait for the next person.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-the-request-lifecycle-handleconnection-&quot;&gt;2. The Request Lifecycle (&lt;code&gt;handleConnection&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;Before we dive into the details of parsing, let's look at the whole journey of a single connection.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;handleConnection&lt;/code&gt; function does four things in order: it wraps the raw socket to read from it easily, parses the incoming text into a request we can understand, checks the URL path to see what the user wants, and sends back a text response.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func handleConnection(conn net.Conn) {
// Always close the connection when we are completely done
defer conn.Close()
clientAddr := conn.RemoteAddr().String()

// Wrap the raw connection in our custom stream reader
reader := stream.NewReader(conn)

// Try to make sense of the incoming bytes
req, err := request.Parse(reader)
if err != nil {
// If they sent garbage data, reply with a 400 Bad Request
res := response.New()
res.SetStatus(400)
res.SetBody([]byte(&quot;400 Bad Request&quot;), &quot;text/plain&quot;)
_ = res.Send(conn)
return
}

// Prepare a blank response to fill out
res := response.New()

// Basic routing: look at the path and decide what to send back
switch req.Path {
case &quot;/&quot;:
res.SetStatus(200)
res.SetBody([]byte(&quot;Welcome to my scratch HTTP Server!&quot;), &quot;text/plain&quot;)
case &quot;/users&quot;:
res.SetStatus(200)
res.SetBody([]byte(`{&quot;message&quot;: &quot;user list endpoint&quot;}`), &quot;application/json&quot;)
default:
// If we don't recognize the path, send a 404
res.SetStatus(404)
res.SetBody([]byte(&quot;404 Page Not Found&quot;), &quot;text/plain&quot;)
}

// Push the final formatted text back through the socket
_ = res.Send(conn)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We break this workload into three clear pieces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;stream&lt;/code&gt;&lt;/strong&gt;: Safely reads a continuous flow of bytes from the connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;request&lt;/code&gt;&lt;/strong&gt;: Turns those raw bytes into structured HTTP data (methods, paths, and headers).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;response&lt;/code&gt;&lt;/strong&gt;: Formats our answer into valid HTTP text and pushes it back to the user.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-reading-the-raw-byte-stream-pkg-stream-&quot;&gt;3. Reading the Raw Byte-Stream (&lt;code&gt;pkg/stream&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;The network doesn't understand neat lines of text; it just gives us raw chunks of bytes. If we try to read one byte at a time directly from the network just to find a newline character, it is incredibly slow.&lt;/p&gt;
&lt;p&gt;The universal fix for this is buffering. Instead of reading byte by byte, we pull a large chunk of data (like 4KB) into our application's memory all at once. Once it is safely in our memory, we can quickly scan through it to find our lines.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package stream

import (
&quot;bufio&quot;
&quot;io&quot;
&quot;strings&quot;
)

type Reader struct {
buffered *bufio.Reader
}

func NewReader(r io.Reader) *Reader {
return &amp;amp;Reader{
// Pull data in large chunks to save time and system resources
buffered: bufio.NewReader(r),
}
}

func (r *Reader) ReadLine() (string, error) {
// Read bytes until we hit a newline character
lineBytes, err := r.buffered.ReadBytes('\n')
// Clean off the trailing \r\n before handing the string back
return strings.TrimRight(string(lineBytes), &quot;\r\n&quot;), err
}

func (r *Reader) ReadExact(count int) ([]byte, error) {
// Create an empty chunk of memory of exactly the size we need
buf := make([]byte, count)
// Block and fill the memory completely before moving on
_, err := io.ReadFull(r.buffered, buf)
return buf, err
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We need two different ways to read:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Line-by-line:&lt;/strong&gt; HTTP metadata (like headers) is separated by line breaks. We read line-by-line until we hit the empty line that tells us the headers are done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exact byte counts:&lt;/strong&gt; The actual body of a request (like an uploaded image) is just raw data. It might have random line breaks in it. So, once the headers are done, we stop reading lines and instead read an exact number of bytes based on the &lt;code&gt;Content-Length&lt;/code&gt; header.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-parsing-the-request-pkg-request-&quot;&gt;4. Parsing the Request (&lt;code&gt;pkg/request&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;Now we write the parser to read that incoming text stream into a structured Go struct:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package request

import (
&quot;fmt&quot;
&quot;strconv&quot;
&quot;strings&quot;
&quot;github.com/devxdh/http-from-scratch/pkg/stream&quot;
)

type Request struct {
Method  string
Path    string
Version string
Headers map[string]string
Body    []byte
}

func Parse(reader *stream.Reader) (*Request, error) {
req := &amp;amp;Request{Headers: make(map[string]string)}

// 1. Grab the very first line (e.g., &quot;GET / HTTP/1.1&quot;)
line, err := reader.ReadLine()
if err != nil { return nil, err }

// Break the line into chunks using spaces
fLineArr := strings.Split(line, &quot; &quot;)
if len(fLineArr) &amp;lt; 3 { return nil, fmt.Errorf(&quot;Malformed request line&quot;) }

// Assign Method, Path, and Version respectively
req.Method, req.Path, req.Version = fLineArr[0], fLineArr[1], fLineArr[2]

// 2. Start reading the headers line by line
for {
line, err := reader.ReadLine()
if err != nil { return nil, err }

// An empty line means the headers section is completely done
if line == &quot;&quot; { break } 

// Split at the first colon we see (e.g., &quot;Host: localhost&quot;)
headerLine := strings.SplitN(line, &quot;:&quot;, 2)
if len(headerLine) &amp;lt; 2 { continue }

// Lowercase the key so we can easily search for it later
key := strings.ToLower(strings.TrimSpace(headerLine[0]))
req.Headers[key] = strings.TrimSpace(headerLine[1])
}

// 3. If there is a body, figure out how long it is
if val, ok := req.Headers[&quot;content-length&quot;]; ok {
contentLength, err := strconv.Atoi(val)
if err != nil {
return nil, fmt.Errorf(&quot;Invalid content-length: %s&quot;, val)
}
// Read that exact number of bytes directly into our request body
req.Body, err = reader.ReadExact(contentLength)
if err != nil {
return nil, fmt.Errorf(&quot;Failed to read body: %v&quot;, err)
}
}

return req, nil
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I keep two rules in mind when parsing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Case-Insensitivity:&lt;/strong&gt; HTTP header names don't care about uppercase or lowercase. We convert them all to lowercase right away so we don't run into bugs later when searching for &quot;Content-Length&quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Splitting Headers:&lt;/strong&gt; Some headers have colons in their values (like &lt;code&gt;Host: localhost:8080&lt;/code&gt;). We only split on the very first colon we see, so we don't accidentally cut the data in half.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-writing-the-http-response-pkg-response-&quot;&gt;5. Writing the HTTP Response (&lt;code&gt;pkg/response&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;Sending an answer is doing the exact reverse of parsing. We build a block of text containing our Status Line, our Headers, and a mandatory empty line. Then we push that text, along with any body data, back through the connection.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package response

import (
&quot;fmt&quot;
&quot;io&quot;
&quot;strings&quot;
)

var StatusRegister = map[int]string{
200: &quot;OK&quot;,
  400: &quot;Bad Request&quot;,
  404: &quot;Not Found&quot;,
}

type Response struct {
StatusCode int
StatusText string
Headers    map[string]string
Body       []byte
}

func New() *Response {
return &amp;amp;Response{
StatusCode: 200,
StatusText: StatusRegister[200],
Headers:    make(map[string]string),
}
}

func (res *Response) SetHeader(key, value string) {
res.Headers[key] = value
}

func (res *Response) SetBody(body []byte, contentType string) {
res.Body = body
// Only set a content type if we actually provided one
if contentType != &quot;&quot; {
res.SetHeader(&quot;Content-Type&quot;, contentType)
}
}

func (res *Response) SetStatus(code int) {
text, ok := StatusRegister[code]
if !ok {
text = &quot;Unknown&quot;
}
res.StatusCode = code
res.StatusText = text
}

func (res *Response) Send(w io.Writer) error {
// We use a builder to efficiently glue all our text together
var builder strings.Builder

// 1. Start with the Status Line
fmt.Fprintf(&amp;amp;builder, &quot;HTTP/1.1 %d %s\r\n&quot;, res.StatusCode, res.StatusText)

// Automatically count bytes so the client knows when to stop reading
res.SetHeader(&quot;Content-Length&quot;, fmt.Sprintf(&quot;%d&quot;, len(res.Body)))

// 2. Add all the headers, making sure to use \r\n
for key, val := range res.Headers {
fmt.Fprintf(&amp;amp;builder, &quot;%s: %s\r\n&quot;, key, val)
}

// 3. Add the blank line that tells the client headers are done
fmt.Fprint(&amp;amp;builder, &quot;\r\n&quot;)

// Push the constructed text block to the socket
_, err := w.Write([]byte(builder.String()))
if err != nil { return err }

// 4. Finally, push the raw body payload (if we have one)
if len(res.Body) &amp;gt; 0 {
_, err = w.Write(res.Body)
}
return err
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why I build it this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automatic Content-Length:&lt;/strong&gt; We count the bytes of our body payload right before sending and set the &lt;code&gt;Content-Length&lt;/code&gt; header ourselves. If we don't tell the client exactly how much data to expect, it might hang forever waiting for more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Send in one go:&lt;/strong&gt; We build the entire text block of headers in memory first, and then send it all at once. This keeps things fast and simple.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-testing-the-server-with-curl--v&quot;&gt;6. Testing the Server with &lt;code&gt;curl -v&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Now that every component is in place, we can start our server and test it using &lt;code&gt;curl -v&lt;/code&gt; (verbose mode).&lt;/p&gt;
&lt;p&gt;You can automate this using a simple &lt;code&gt;Makefile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-makefile&quot;&gt;.PHONY: start test-get test-users test-post test-404

start:
go run ./main.go

test-get:
curl -v http://localhost:8080/

test-users:
curl -v http://localhost:8080/users

test-post:
curl -v -X POST http://localhost:8080/users \
-H &quot;Content-Type: text/plain&quot; \
-d &quot;This is super horsey TCP/IP payload.&quot;

test-404:
curl -v http://localhost:8080/non-existent-path

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run &lt;code&gt;make start&lt;/code&gt; in one terminal, and &lt;code&gt;make test-users&lt;/code&gt; in another. Here is what the raw TCP exchange looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;* Connected to localhost (127.0.0.1) port 8080
&amp;gt; GET /users HTTP/1.1
&amp;gt; Host: localhost:8080
&amp;gt; User-Agent: curl/8.0.0
&amp;gt; Accept: */*
&amp;gt; 
&amp;lt; HTTP/1.1 200 OK
&amp;lt; Content-Type: application/json
&amp;lt; Content-Length: 35
&amp;lt; 
{&quot;message&quot;: &quot;user list endpoint&quot;}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Lines starting with &lt;code&gt;&amp;gt;&lt;/code&gt; are the raw plain-text HTTP request bytes sent by &lt;code&gt;curl&lt;/code&gt; over the TCP socket.&lt;/li&gt;
&lt;li&gt;The empty line right after &lt;code&gt;Accept: */*&lt;/code&gt; is the &lt;code&gt;\r\n\r\n&lt;/code&gt; boundary our parser scanned for.&lt;/li&gt;
&lt;li&gt;Lines starting with &lt;code&gt;&amp;lt;&lt;/code&gt; are the raw response bytes constructed by our &lt;code&gt;response.Send&lt;/code&gt; function and pushed down the socket.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because HTTP is fundamentally plain text over TCP, you could even talk to this server by sending raw text through Netcat (&lt;code&gt;nc localhost 8080&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Source Code: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/devxdh/http-from-tcp&quot;&gt;http-from-tcp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Building an HTTP server from scratch demystified a lot of networking magic. Hope you got some value form this blog :).&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/27310/building-an-http-server-from-scratch-using-tcp</guid>
<pubDate>Tue, 15 Sep 2026 20:00:16 +0000</pubDate>
</item>
<item>
<title>Making HTTP Better on ESP32</title>
<link>https://coderlegion.com/26255/making-http-better-on-esp32</link>
<description>&lt;h2 id=&quot;http-on-esp32-isn-t-hard-the-unnecessary-work-around-it-is-&quot;&gt;HTTP on ESP32 Isn't Hard. The Unnecessary Work Around It Is.&lt;/h2&gt;
&lt;p&gt;Making an HTTP request from an ESP32 is relatively straightforward.&lt;/p&gt;
&lt;p&gt;Connect to Wi-Fi, create an HTTP client, send a request, receive a response.&lt;/p&gt;
&lt;p&gt;The real challenge usually appears later.&lt;/p&gt;
&lt;p&gt;As an embedded application grows, HTTP communication starts accumulating responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentication tokens&lt;/li&gt;
&lt;li&gt;Custom headers&lt;/li&gt;
&lt;li&gt;Query parameters and dynamic paths&lt;/li&gt;
&lt;li&gt;JSON request bodies&lt;/li&gt;
&lt;li&gt;JSON response parsing&lt;/li&gt;
&lt;li&gt;Temporary buffers&lt;/li&gt;
&lt;li&gt;Memory allocations&lt;/li&gt;
&lt;li&gt;Repeated connections&lt;/li&gt;
&lt;li&gt;Error handling and status validation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these things are particularly difficult on their own.&lt;/p&gt;
&lt;p&gt;The problem is the amount of &lt;strong&gt;unnecessary work surrounding a simple HTTP request&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;On a server or desktop application, that overhead might not matter much. On an ESP32 running continuously alongside sensors, Wi-Fi, display drivers, Bluetooth, and other tasks, memory usage and repeated allocations can become much more important.&lt;/p&gt;
&lt;p&gt;This article explores a question I started asking while building ESP32 applications:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;How much work does an ESP32 actually need to do to make an HTTP request?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-simple-request-can-turn-into-a-long-workflow&quot;&gt;A Simple Request Can Turn Into a Long Workflow&lt;/h3&gt;
&lt;p&gt;Consider a common situation.&lt;/p&gt;
&lt;p&gt;An API returns the following JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;temperature&quot;: 24.8,
  &quot;humidity&quot;: 68,
  &quot;device&quot;: {
    &quot;id&quot;: &quot;esp32-office&quot;,
    &quot;location&quot;: &quot;office&quot;
  },
  &quot;metadata&quot;: {
    &quot;version&quot;: &quot;1.4&quot;,
    &quot;updatedAt&quot;: 1725500000
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But your ESP32 only needs this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;temperature
device.id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A traditional workflow might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HTTP Request
     ↓
Receive the complete response
     ↓
Store the response in memory
     ↓
Create a JSON document
     ↓
Parse the entire JSON
     ↓
Navigate through the structure
     ↓
Extract two values
     ↓
Discard temporary data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach is completely valid.&lt;/p&gt;
&lt;p&gt;In fact, a full JSON parser is often the right solution when your application genuinely needs to work with an entire JSON document dynamically.&lt;/p&gt;
&lt;p&gt;But there is an important question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If the application already knows exactly which values it needs, do we need to allocate and process everything else?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That question became the starting point for a different approach.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-cost-isn-t-http&quot;&gt;The Real Cost Isn't HTTP&lt;/h3&gt;
&lt;p&gt;HTTP itself is not necessarily the problem.&lt;/p&gt;
&lt;p&gt;The surrounding workflow often is.&lt;/p&gt;
&lt;p&gt;A request can involve:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Create Client
     ↓
Configure Headers
     ↓
Build Authentication
     ↓
Format URL &amp;amp; Query Parameters
     ↓
Build JSON Request Body
     ↓
Open Connection
     ↓
Send Request
     ↓
Receive Response
     ↓
Store Response
     ↓
Allocate JSON Document
     ↓
Parse JSON
     ↓
Extract Values
     ↓
Destroy Temporary Objects
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Repeat that process continuously, and things become more interesting.&lt;/p&gt;
&lt;p&gt;For an ESP32 application that runs for hours, days, or even months, developers often start thinking about questions that are less important in traditional applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How often am I allocating memory on the heap?&lt;/li&gt;
&lt;li&gt;Am I storing data I don't actually need?&lt;/li&gt;
&lt;li&gt;Can I reuse this connection across requests?&lt;/li&gt;
&lt;li&gt;Can authentication configuration persist without repeated boilerplate?&lt;/li&gt;
&lt;li&gt;Can I avoid repeatedly rebuilding headers and URLs?&lt;/li&gt;
&lt;li&gt;Can I extract only the fields I care about directly into variables?&lt;/li&gt;
&lt;li&gt;Will a missing JSON key crash my device or leave it in an unstable state?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal isn't to eliminate every allocation at all costs.&lt;/p&gt;
&lt;p&gt;The goal is to avoid &lt;strong&gt;allocations and processing that don't provide value to the application&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;memory-predictability-matters-on-embedded-devices&quot;&gt;Memory Predictability Matters on Embedded Devices&lt;/h3&gt;
&lt;p&gt;The ESP32 is a capable microcontroller, but it still operates under resource constraints.&lt;/p&gt;
&lt;p&gt;An embedded application might be handling several things simultaneously:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ESP32 Application
│
├── Wi-Fi
├── Sensors
├── Display
├── Bluetooth
├── FreeRTOS Tasks
├── HTTP Communication
└── Application Logic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every component competes for memory.&lt;/p&gt;
&lt;p&gt;This is why memory predictability can be just as important as raw performance.&lt;/p&gt;
&lt;p&gt;Repeated dynamic allocations are not automatically bad. However, when an application performs frequent network operations over long periods, reducing unnecessary temporary allocations can help create a more predictable memory profile.&lt;/p&gt;
&lt;p&gt;This becomes particularly relevant when dealing with HTTP responses.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;do-you-really-need-the-entire-json-response-&quot;&gt;Do You Really Need the Entire JSON Response?&lt;/h3&gt;
&lt;p&gt;Let's return to the earlier example.&lt;/p&gt;
&lt;p&gt;Suppose the ESP32 only needs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;temperature
device.id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of building an entire JSON structure in memory, another approach is to bind those fields directly to variables.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;float temperature;
char deviceId[32];

client.get(&quot;/sensor&quot;)
      .getBody(&quot;temperature&quot;, &amp;amp;temperature)
      .getBody(&quot;device.id&quot;, deviceId, sizeof(deviceId));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application tells the HTTP client:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;When you find &lt;code&gt;temperature&lt;/code&gt;, write it here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;When you find &lt;code&gt;device.id&lt;/code&gt;, write it here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The application doesn't need to manually navigate through a JSON document afterward.&lt;/p&gt;
&lt;p&gt;The values go directly to their final destination.&lt;/p&gt;
&lt;p&gt;This concept is called &lt;strong&gt;response binding&lt;/strong&gt;, and it became the central idea behind my library, &lt;strong&gt;ESP32-HTTP-Client&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of following this workflow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Response
   ↓
String
   ↓
JSON Document
   ↓
Extract Values
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The workflow becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Response Stream
   ↓
Find Required Fields
   ↓
Write Directly Into Variables
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The complete response body does not need to be stored before processing.&lt;/p&gt;
&lt;p&gt;The library processes the response as a stream and binds matched fields directly to the variables provided by the application.&lt;/p&gt;
&lt;h4 id=&quot;nested-paths-arrays-and-missing-keys&quot;&gt;Nested Paths, Arrays, and Missing Keys&lt;/h4&gt;
&lt;p&gt;Response binding isn't limited to shallow properties. It supports deep dot notation and numeric array indexing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;char city[32];
int secondReading;

client.get(&quot;/data&quot;)
      .getBody(&quot;sensors.0.readings.1&quot;, &amp;amp;secondReading)
      .getBody(&quot;device.location.city&quot;, city, sizeof(city));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And critically for embedded reliability: &lt;strong&gt;if a key is missing or the schema changes, the target variable is simply left untouched&lt;/strong&gt;. The library does not crash, throw exceptions, or leave memory in an inconsistent state.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reducing-work-means-more-than-reducing-memory&quot;&gt;Reducing Work Means More Than Reducing Memory&lt;/h3&gt;
&lt;p&gt;Memory was one motivation behind this approach.&lt;/p&gt;
&lt;p&gt;But reducing unnecessary work can also improve code simplicity.&lt;/p&gt;
&lt;p&gt;Consider the amount of code developers often write around a simple request using the standard ESP32 libraries (&lt;code&gt;HTTPClient&lt;/code&gt; + &lt;code&gt;ArduinoJson&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// The traditional approach: ~15-20 lines of procedural boilerplate
HTTPClient http;
http.begin(&quot;https://api.example.com/report&quot;);
http.addHeader(&quot;Authorization&quot;, &quot;Bearer my-jwt-token&quot;);
http.addHeader(&quot;X-Device-ID&quot;, &quot;ESP32-001&quot;);

int code = http.GET();
if (code == HTTP_CODE_OK) {
    // 1. Read complete payload into RAM (heap allocation #1)
    String payload = http.getString();

    // 2. Allocate JSON document (heap allocation #2)
    DynamicJsonDocument doc(2048);
    DeserializationError err = deserializeJson(doc, payload);

    if (!err) {
        // 3. Navigate structure and copy values manually
        int userId = doc[&quot;userId&quot;];
        float temperature = doc[&quot;sensor&quot;][&quot;temp&quot;];
        char city[32];
        strncpy(city, doc[&quot;address&quot;][&quot;city&quot;] | &quot;&quot;, sizeof(city));
    }
}
http.end();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice everything that happens here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Authentication and headers&lt;/strong&gt; are rebuilt for every single request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intermediate buffers&lt;/strong&gt; (&lt;code&gt;payload&lt;/code&gt; String and &lt;code&gt;doc&lt;/code&gt; JSON tree) are allocated on the heap to store data the application will never use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual parsing and navigation&lt;/strong&gt; are written out imperatively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cleanup and disposal&lt;/strong&gt; repeat on every iteration.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A good abstraction should reduce repetition without hiding important control from the developer.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;ESP32-HTTP-Client&lt;/strong&gt;, those responsibilities are divided where they actually belong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authentication and headers&lt;/strong&gt; belong to the client configuration and are declared &lt;strong&gt;once&lt;/strong&gt; (&lt;code&gt;client.bearer(...)&lt;/code&gt;, &lt;code&gt;client.setHeader(...)&lt;/code&gt;), persisting automatically across requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buffers and deserialization&lt;/strong&gt; are bypassed entirely—the response stream binds matched values straight into your destination variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So when the goal is simply to retrieve a few values from an API, the application code expresses only its actual intent:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int userId;
float temperature;
char city[32];

// Client already has auth &amp;amp; headers; request simply fetches and binds:
client.get(&quot;/report&quot;)
      .getBody(&quot;userId&quot;, &amp;amp;userId)
      .getBody(&quot;sensor.temp&quot;, &amp;amp;temperature)
      .getBody(&quot;address.city&quot;, city, sizeof(city));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code describes the application's actual intent:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Make a request and give me these values.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And if a specific request needs query parameters, a custom timeout, or needs to capture a response header, they fold into the exact same fluent chain without extra boilerplate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;String token;

client.get(&quot;/report&quot;)
      .query(&quot;period&quot;, &quot;latest&quot;)
      .timeout(5000)
      .getHeader(&quot;Authorization&quot;, &amp;amp;token)
      .getBody(&quot;userId&quot;, &amp;amp;userId)
      .getBody(&quot;sensor.temp&quot;, &amp;amp;temperature)
      .getBody(&quot;address.city&quot;, city, sizeof(city));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the philosophy behind the fluent API used by ESP32-HTTP-Client.&lt;/p&gt;
&lt;h4 id=&quot;fluent-payloads-and-full-rest-support&quot;&gt;Fluent Payloads and Full REST Support&lt;/h4&gt;
&lt;p&gt;This fluent design isn't limited to &lt;code&gt;GET&lt;/code&gt; requests. REST communication is two-way.&lt;/p&gt;
&lt;p&gt;Building JSON request bodies manually with &lt;code&gt;snprintf&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt; concatenations frequently introduces subtle formatting bugs and heap churn. With ESP32-HTTP-Client, sending structured data is just as straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int newId;

// POST request with fluent JSON body construction
client.post(&quot;/devices&quot;)
      .body(&quot;name&quot;, &quot;ESP32-Office&quot;)
      .body(&quot;interval&quot;, 30)
      .body(&quot;active&quot;, true)
      .getBody(&quot;id&quot;, &amp;amp;newId);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client provides first-class support for all standard REST methods: &lt;code&gt;get()&lt;/code&gt;, &lt;code&gt;post()&lt;/code&gt;, &lt;code&gt;put()&lt;/code&gt; (with &lt;code&gt;update()&lt;/code&gt; as an alias), &lt;code&gt;patch()&lt;/code&gt;, and &lt;code&gt;del()&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;path-and-query-parameters-without-string-concatenation&quot;&gt;Path and Query Parameters Without String Concatenation&lt;/h4&gt;
&lt;p&gt;Dynamic URLs in Arduino sketches are typically built with repetitive string operations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Fragile and causes heap fragmentation:
String url = &quot;/devices/&quot; + String(deviceId) + &quot;/readings?limit=&quot; + String(limit);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESP32-HTTP-Client solves this at the builder level with &lt;code&gt;.path()&lt;/code&gt; and &lt;code&gt;.query()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;client.get(&quot;/devices/{id}/readings&quot;)
      .path(&quot;id&quot;, deviceId)
      .query(&quot;limit&quot;, 20)
      .query(&quot;filter&quot;, &quot;active&quot;)
      .getBody(&quot;summary.count&quot;, &amp;amp;count);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Placeholders are replaced and query parameters are appended smoothly, keeping the code readable and avoiding intermediate &lt;code&gt;String&lt;/code&gt; objects on the heap.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;beyond-primitives-struct-lt---gt-json-mapping&quot;&gt;Beyond Primitives: Struct &amp;lt;-&amp;gt; JSON Mapping&lt;/h3&gt;
&lt;p&gt;Binding individual primitive variables works well for small endpoints. But in production firmware, you often work with structured data models.&lt;/p&gt;
&lt;p&gt;Traditionally, handling a data model on an ESP32 involves:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Allocating a &lt;code&gt;DynamicJsonDocument&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Parsing the incoming JSON.&lt;/li&gt;
&lt;li&gt;Manually copying each field into a C++ &lt;code&gt;struct&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Freeing the document.&lt;/li&gt;
&lt;li&gt;Repeating the reverse process whenever you need to serialize the struct to send it back.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ESP32-HTTP-Client addresses this with &lt;strong&gt;zero-allocation bidirectional struct mapping&lt;/strong&gt; via the &lt;code&gt;REST_JSON_MAP&lt;/code&gt; macro:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct DeviceConfig {
    float telemetryInterval = 60.0;
    bool enabled = true;
    char mode[16] = &quot;normal&quot;;

    REST_JSON_MAP(
        REST_FIELD(telemetryInterval),
        REST_FIELD(enabled),
        REST_FIELD(mode)
    )
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once declared, the struct can be populated directly from an API response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;DeviceConfig config;

// The network stream binds directly into the struct fields
client.get(&quot;/device/config&quot;).getBody(&amp;amp;config);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And sending a struct to an API is just as clean:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;client.post(&quot;/device/config&quot;).body(config);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No intermediate JSON documents to size or manage. Serialization and deserialization happen directly between your native C++ structures and the network stream.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;authentication-shouldn-t-become-repeated-boilerplate&quot;&gt;Authentication Shouldn't Become Repeated Boilerplate&lt;/h3&gt;
&lt;p&gt;Performance and memory are only part of the problem.&lt;/p&gt;
&lt;p&gt;Authentication is another common source of repeated HTTP boilerplate.&lt;/p&gt;
&lt;p&gt;Many IoT APIs require authentication using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bearer tokens&lt;/li&gt;
&lt;li&gt;JWT tokens&lt;/li&gt;
&lt;li&gt;HTTP Basic Authentication&lt;/li&gt;
&lt;li&gt;API Keys&lt;/li&gt;
&lt;li&gt;Custom headers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without a reusable abstraction, authentication logic can easily start appearing before every request.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Request A
 ├── Add Authorization Header
 └── Send Request

Request B
 ├── Add Authorization Header
 └── Send Request

Request C
 ├── Add Authorization Header
 └── Send Request
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But authentication usually belongs to the &lt;strong&gt;client configuration&lt;/strong&gt;, not to every individual request.&lt;/p&gt;
&lt;p&gt;A reusable client can configure authentication once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;client.bearer(&quot;your-jwt-token&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then continue making requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;client.get(&quot;/profile&quot;);

client.get(&quot;/devices&quot;);

client.get(&quot;/telemetry&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The authentication header persists across subsequent requests made by that client instance.&lt;/p&gt;
&lt;p&gt;The same idea applies to Basic Authentication:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;client.basic(&quot;username&quot;, &quot;password&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And API keys:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;client.apiKey(&quot;X-API-Key&quot;, &quot;your-api-key&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client also supports persistent custom headers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;client.setHeader(&quot;X-Device-ID&quot;, &quot;ESP32-001&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These configurations remain associated with the client and are applied to subsequent requests.&lt;/p&gt;
&lt;h4 id=&quot;extracting-response-headers&quot;&gt;Extracting Response Headers&lt;/h4&gt;
&lt;p&gt;What about headers returned by the server? Often, authentication tokens, rate limits, or server timestamps are sent in HTTP response headers rather than the JSON body.&lt;/p&gt;
&lt;p&gt;Instead of writing custom header collection routines, you can extract response headers inside the same fluent chain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;String refreshedToken;
int rateLimit;

client.get(&quot;/telemetry&quot;)
      .getHeader(&quot;Authorization&quot;, &amp;amp;refreshedToken)
      .getHeader(&quot;X-RateLimit-Remaining&quot;, &amp;amp;rateLimit);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Header lookups are case-insensitive and can be captured into standard C types or Arduino &lt;code&gt;String&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;This may seem like a small improvement.&lt;/p&gt;
&lt;p&gt;But in embedded applications, reducing repeated code also reduces the chances of inconsistent configuration between requests.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reuse-the-client-not-just-the-code&quot;&gt;Reuse the Client, Not Just the Code&lt;/h3&gt;
&lt;p&gt;Another overlooked source of unnecessary work is connection management.&lt;/p&gt;
&lt;p&gt;Imagine an application that does this repeatedly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Create HTTP Client
      ↓
Connect
      ↓
Make Request
      ↓
Close Connection
      ↓
Destroy Client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then starts again a few seconds later.&lt;/p&gt;
&lt;p&gt;For applications making frequent requests to the same server, connection reuse can significantly reduce overhead.&lt;/p&gt;
&lt;p&gt;ESP32-HTTP-Client enables HTTP Keep-Alive and reuses the underlying connection when possible, allowing subsequent requests to avoid unnecessarily rebuilding the connection.&lt;/p&gt;
&lt;p&gt;The intended usage looks more like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;ESP32HTTPClient client(&quot;https://api.example.com&quot;);

void loop() {
    float temperature;

    client.get(&quot;/sensor&quot;)
          .getBody(&quot;temperature&quot;, &amp;amp;temperature);

    delay(5000);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client instance is reused.&lt;/p&gt;
&lt;p&gt;That doesn't mean every application should keep a connection alive forever.&lt;/p&gt;
&lt;p&gt;There are trade-offs.&lt;/p&gt;
&lt;p&gt;An active HTTPS connection can retain TLS buffers in memory. According to the library's documented measurements, calling &lt;code&gt;client.end()&lt;/code&gt; after a burst of requests can release resources when the device will remain idle for a long period.&lt;/p&gt;
&lt;p&gt;So the better question isn't:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Should I always keep the connection alive?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Does connection reuse make sense for how frequently my device communicates?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That distinction matters.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;measuring-the-difference&quot;&gt;Measuring the Difference&lt;/h3&gt;
&lt;p&gt;Performance claims without context are not particularly useful.&lt;/p&gt;
&lt;p&gt;So I tested the approach using 100 consecutive HTTP GET requests with JSON responses on a real ESP32 device.&lt;/p&gt;
&lt;p&gt;The benchmark compared:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Standard Approach
HTTPClient + ArduinoJson

vs.

ESP32-HTTP-Client
Streaming + Direct Response Binding
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Wi-Fi connection was already established before the benchmark started, and both approaches used their default configurations.&lt;/p&gt;
&lt;p&gt;The documented benchmark results were:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric                      &lt;/th&gt;&lt;th&gt;HTTPClient + ArduinoJson &lt;/th&gt;&lt;th&gt;ESP32-HTTP-Client&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Heap allocation per request &lt;/td&gt;&lt;td&gt;                ~58.2 KB &lt;/td&gt;&lt;td&gt;        ~15 bytes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Average RAM footprint       &lt;/td&gt;&lt;td&gt;                   34.2% &lt;/td&gt;&lt;td&gt;            24.3%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Minimum free heap           &lt;/td&gt;&lt;td&gt;                114.3 KB &lt;/td&gt;&lt;td&gt;         128.6 KB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Average execution time      &lt;/td&gt;&lt;td&gt;                 ~750 ms &lt;/td&gt;&lt;td&gt;           ~59 ms&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The difference comes from several architectural choices, including connection reuse and streaming JSON processing rather than storing the complete response and parsing it afterward.&lt;/p&gt;
&lt;p&gt;However, these numbers should be interpreted correctly.&lt;/p&gt;
&lt;p&gt;They represent &lt;strong&gt;this specific benchmark configuration&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;They do not mean every HTTP request on every ESP32 application will be twelve times faster.&lt;/p&gt;
&lt;p&gt;Network conditions, server behavior, payload size, TLS configuration, connection reuse, and application logic can all affect the result.&lt;/p&gt;
&lt;p&gt;Benchmarking should always be reproducible and specific to the problem being solved.&lt;/p&gt;
&lt;p&gt;The full benchmark setup and source information are documented with the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-more-practical-example&quot;&gt;A More Practical Example&lt;/h3&gt;
&lt;p&gt;Let's consider a realistic IoT scenario.&lt;/p&gt;
&lt;p&gt;An ESP32 communicates with a remote API.&lt;/p&gt;
&lt;p&gt;It needs to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Authenticate using a Bearer token.&lt;/li&gt;
&lt;li&gt;Identify the device using a custom header.&lt;/li&gt;
&lt;li&gt;Send telemetry periodically.&lt;/li&gt;
&lt;li&gt;Receive configuration from the server.&lt;/li&gt;
&lt;li&gt;Extract only the configuration values it needs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The workflow looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;               ESP32     
                 │
      Read Sensor Data (Temp &amp;amp; Humidity)
                 │
                 ▼
       Send Telemetry (POST /telemetry)
                 │
                 ▼
          Authenticated API
                 │
                 ▼
   Receive Configuration (GET /device/config)
                 │
                 ▼
     Extract Required Fields Directly
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client configuration happens once during initialization:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;WiFi.h&amp;gt;
#include &quot;ESP32HTTPClient.h&quot;

ESP32HTTPClient client(&quot;https://api.example.com&quot;);

void setup() {
    Serial.begin(115200);
    // Connect to Wi-Fi...

    // Configure persistent auth and headers once:
    client.bearer(&quot;your-jwt-token&quot;);
    client.setHeader(&quot;X-Device-ID&quot;, &quot;ESP32-001&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in the periodic loop, the application performs both the telemetry upload and configuration check without intermediate boilerplate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void loop() {
    // 1. Read sensor data
    float currentTemp = 24.8;
    float currentHumidity = 65.0;

    // 2. Send telemetry via POST with a fluent JSON payload
    client.post(&quot;/telemetry&quot;)
          .body(&quot;temperature&quot;, currentTemp)
          .body(&quot;humidity&quot;, currentHumidity);

    if (client.isSuccess()) {
        Serial.println(&quot;Telemetry sent successfully.&quot;);
    }

    // 3. Retrieve server configuration via GET with direct response binding
    float interval = 30.0;
    bool enabled = true;

    client.get(&quot;/device/config&quot;)
          .getBody(&quot;telemetryInterval&quot;, &amp;amp;interval)
          .getBody(&quot;enabled&quot;, &amp;amp;enabled);

    if (client.isSuccess()) {
        Serial.printf(&quot;Config updated: interval=%.1fs, enabled=%d\n&quot;, interval, enabled);
    } else {
        Serial.printf(&quot;Request error [%d]: %s\n&quot;, 
                      client.getStatusCode(), 
                      client.getErrorMessage().c_str());
    }

    delay(interval * 1000);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application does not need to manually:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Format a JSON string with &lt;code&gt;snprintf&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt; additions.&lt;/li&gt;
&lt;li&gt;Retrieve a complete JSON response string.&lt;/li&gt;
&lt;li&gt;Allocate a JSON document on the heap.&lt;/li&gt;
&lt;li&gt;Deserialize the document.&lt;/li&gt;
&lt;li&gt;Navigate through the JSON structure.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Instead, it declares what it sends and what it expects.&lt;/p&gt;
&lt;p&gt;That is a much closer representation of what the application actually needs.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;but-should-you-always-avoid-a-full-json-parser-&quot;&gt;But Should You Always Avoid a Full JSON Parser?&lt;/h3&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;This is important.&lt;/p&gt;
&lt;p&gt;A full JSON library remains extremely useful.&lt;/p&gt;
&lt;p&gt;There are situations where you genuinely need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dynamic JSON structures&lt;/li&gt;
&lt;li&gt;Unknown fields&lt;/li&gt;
&lt;li&gt;Complex transformations&lt;/li&gt;
&lt;li&gt;Entire arrays&lt;/li&gt;
&lt;li&gt;Full objects&lt;/li&gt;
&lt;li&gt;Runtime exploration of a JSON document&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those situations, storing and manipulating the entire document may be the correct solution.&lt;/p&gt;
&lt;p&gt;The goal is not:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Never use ArduinoJson.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The goal is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Don't process more data than your application needs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If your application requires only three predictable values from a large API response, direct response binding can be a simpler and more memory-conscious alternative.&lt;/p&gt;
&lt;p&gt;If your application needs to inspect and manipulate an entire dynamic JSON document, a full JSON parser is probably the better tool.&lt;/p&gt;
&lt;p&gt;Choosing the right abstraction depends on the problem.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-does-this-approach-actually-matter-&quot;&gt;When Does This Approach Actually Matter?&lt;/h3&gt;
&lt;p&gt;If your ESP32 sends one small request every few hours, HTTP optimization may not be your biggest concern.&lt;/p&gt;
&lt;p&gt;But the approach becomes more interesting when your application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Runs continuously.&lt;/li&gt;
&lt;li&gt;Makes frequent requests.&lt;/li&gt;
&lt;li&gt;Communicates with the same server repeatedly.&lt;/li&gt;
&lt;li&gt;Has limited memory available.&lt;/li&gt;
&lt;li&gt;Uses multiple peripherals (display, BLE, Wi-Fi, sensors).&lt;/li&gt;
&lt;li&gt;Processes JSON responses.&lt;/li&gt;
&lt;li&gt;Needs predictable memory usage without heap fragmentation.&lt;/li&gt;
&lt;li&gt;Uses authentication across multiple API endpoints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For these scenarios, reducing unnecessary allocations and repeated configuration can make the networking layer simpler and potentially more efficient.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-idea-behind-esp32-http-client&quot;&gt;The Idea Behind ESP32-HTTP-Client&lt;/h3&gt;
&lt;p&gt;I built ESP32-HTTP-Client around a simple question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can HTTP communication on an ESP32 be expressed closer to what the application actually needs?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Get everything
     ↓
Store everything
     ↓
Parse everything
     ↓
Use a small part
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The approach becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Request what you need
     ↓
Extract what you need
     ↓
Store it where you need it
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The library combines several ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Direct JSON response binding&lt;/strong&gt;: Maps response values straight into primitive variables or native C++ &lt;code&gt;struct&lt;/code&gt;s (&lt;code&gt;REST_JSON_MAP&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streaming response processing&lt;/strong&gt;: Parses data on the fly directly from the network stream with zero full-payload buffering.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full REST suite with fluent payloads&lt;/strong&gt;: Clean &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; without manual string formatting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safe URL builders&lt;/strong&gt;: Dynamic path parameters (&lt;code&gt;.path()&lt;/code&gt;) and query strings (&lt;code&gt;.query()&lt;/code&gt;) without dynamic &lt;code&gt;String&lt;/code&gt; concatenation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response header extraction&lt;/strong&gt;: Binds headers directly via &lt;code&gt;.getHeader()&lt;/code&gt; in the same call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent authentication&lt;/strong&gt;: Reusable Bearer tokens, Basic Auth, API keys, and custom headers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP Keep-Alive and connection reuse&lt;/strong&gt;: Drastically reduces handshake latency across sequential requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in reliability&lt;/strong&gt;: Missing response fields never crash the device, and status checking (&lt;code&gt;isSuccess()&lt;/code&gt;, callbacks) is baked in.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The objective was never simply to create another HTTP wrapper.&lt;/p&gt;
&lt;p&gt;It was to question how much unnecessary work an ESP32 performs between:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;I need this data&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;Here is the value.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Making HTTP requests on an ESP32 isn't difficult.&lt;/p&gt;
&lt;p&gt;The challenge is everything that tends to accumulate around those requests as an application grows.&lt;/p&gt;
&lt;p&gt;Authentication.&lt;/p&gt;
&lt;p&gt;Headers.&lt;/p&gt;
&lt;p&gt;Temporary buffers.&lt;/p&gt;
&lt;p&gt;JSON parsing.&lt;/p&gt;
&lt;p&gt;Repeated connections.&lt;/p&gt;
&lt;p&gt;Repeated boilerplate.&lt;/p&gt;
&lt;p&gt;For many projects, the traditional approach is perfectly fine.&lt;/p&gt;
&lt;p&gt;But embedded development often benefits from asking a different question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What work is actually necessary?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the ESP32 already knows which values it needs from an API, processing and storing an entire response may not always be necessary.&lt;/p&gt;
&lt;p&gt;If authentication remains the same across multiple requests, rebuilding the headers every time may not be necessary.&lt;/p&gt;
&lt;p&gt;If the device communicates repeatedly with the same server, reconnecting from scratch may not always be necessary.&lt;/p&gt;
&lt;p&gt;Small decisions like these can change how an embedded application behaves over time.&lt;/p&gt;
&lt;p&gt;That is the idea behind ESP32-HTTP-Client:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP isn't necessarily the expensive part. Sometimes, it's all the unnecessary work around it.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;explore-the-project&quot;&gt;Explore the Project&lt;/h3&gt;
&lt;p&gt;ESP32-HTTP-Client is an open-source Arduino library designed for ESP32 applications that need a lightweight and fluent approach to REST communication.&lt;/p&gt;
&lt;p&gt;You can explore the documentation, examples, benchmarks, and source code here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://esp32httpclient.com&quot;&gt;ESP32-HTTP-Client Documentation&lt;/a&gt;&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/26255/making-http-better-on-esp32</guid>
<pubDate>Sat, 05 Sep 2026 09:23:34 +0000</pubDate>
</item>
<item>
<title>The HTTP QUERY method (RFC 10008) is here — and caching it correctly is harder than it looks</title>
<link>https://coderlegion.com/24910/the-http-query-method-rfc-10008-is-here-and-caching-it-correctly-is-harder-than-it-looks</link>
<description>&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ld3ixdq900l705ew8k3t.gif&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HTTP got a new method: &lt;strong&gt;QUERY&lt;/strong&gt; (RFC 10008, June 2026). It's a &lt;strong&gt;safe, idempotent, cacheable request that carries a body&lt;/strong&gt; — &quot;GET with a body.&quot;&lt;/li&gt;
&lt;li&gt;It fixes a decade-old pain: complex reads (search/filter/graph queries) that don't fit in a URL, without abusing &lt;code&gt;POST&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The subtle part is &lt;strong&gt;caching&lt;/strong&gt;: the RFC requires the &lt;strong&gt;request body&lt;/strong&gt; to be part of the cache key. Get it wrong and one client gets another client's response.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hardik-goel/http-queryable&quot;&gt;&lt;code&gt;http-queryable&lt;/code&gt;&lt;/a&gt; is a small library that does this correctly for Express, Fastify, raw &lt;code&gt;http&lt;/code&gt;, and the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-is-the-query-method-&quot;&gt;What is the QUERY method?&lt;/h3&gt;
&lt;p&gt;For decades HTTP forced a bad choice for &quot;reads with a big input&quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET&lt;/code&gt; — safe, idempotent, cacheable, but &lt;strong&gt;no body&lt;/strong&gt;. Cram everything into the URL and hit length limits.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST&lt;/code&gt; — has a body, but it's &lt;strong&gt;unsafe and non-idempotent&lt;/strong&gt;. Caches and proxies won't treat it as a read, and you can't safely retry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;QUERY&lt;/code&gt; is the missing middle: &lt;strong&gt;a body like POST, but the semantics of GET&lt;/strong&gt; — safe, idempotent, and cacheable.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;QUERY /search HTTP/1.1
Content-Type: application/json

{ &quot;q&quot;: &quot;cats&quot;, &quot;filters&quot;: { &quot;color&quot;: &quot;black&quot; } }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-catch-nobody-talks-about-caching&quot;&gt;The catch nobody talks about: caching&lt;/h3&gt;
&lt;p&gt;RFC 10008 §2.7 is explicit: &lt;strong&gt;the cache key must incorporate the request content.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Shared HTTP caches have always keyed on &lt;strong&gt;method + URL&lt;/strong&gt;. With QUERY, many &lt;em&gt;different&lt;/em&gt; bodies hit the &lt;em&gt;same&lt;/em&gt; URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;QUERY /search  { &quot;q&quot;: &quot;cats&quot; }   -&amp;gt; cats
QUERY /search  { &quot;q&quot;: &quot;dogs&quot; }   -&amp;gt; dogs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A method+URL cache would serve the &lt;strong&gt;cats&lt;/strong&gt; response to the **dctness and security bug — the RFC's Security Considerationscalls it out.&lt;/p&gt;
&lt;h3 id=&quot;doing-it-right-conservative-body-normalization&quot;&gt;Doing it right: conservative body normalization&lt;/h3&gt;
&lt;p&gt;You want two things in tension:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;{&quot;a&quot;:1,&quot;b&quot;:2}&lt;/code&gt; and &lt;code&gt;{ &quot;b&quot;:2, &quot;a&quot;:1 }&lt;/code&gt; mean the same thing → &lt;strong&gt;same key&lt;/strong&gt; (a hit).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{&quot;q&quot;:&quot;cats&quot;}&lt;/code&gt; and &lt;code&gt;{&quot;q&quot;:&quot;dogs&quot;}&lt;/code&gt; differ → *&lt;em&gt;different keys&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key insight is an &lt;strong&gt;asymmetry&lt;/strong&gt;: a false &lt;strong&gt;miss&lt;/strong&gt; is harmle&lt;strong&gt; is a bug (wrong data served). So every normalization must be&lt;/strong&gt;provably meaning-preserving*&lt;em&gt;, and when in doubt you normalize &lt;/em&gt;less*.&lt;/p&gt;
&lt;p&gt;Safe for JSON: whitespace, object key order, string escape forms. NOT safe: merging numeric literals (&lt;code&gt;JSON.parse(&quot;9007199254740993&quot;)&lt;/code&gt; returns &lt;code&gt;...992&lt;/code&gt; — a&lt;br&gt;
silent collision), guessing on duplicate keys, or normalizing ctand.&lt;/p&gt;
&lt;h3 id=&quot;show-me-the-code&quot;&gt;Show me the code&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import express from &quot;express&quot;;
import { queryable, QueryCache } from &quot;http-queryable/express&quot;;

const app = express();
app.use(queryable({ cache: new QueryCache() }));
app.query(&quot;/search&quot;, (req, res) =&amp;gt; res.json(search(req.body)));
app.listen(3000);

curl -X QUERY /search -d '{&quot;q&quot;:&quot;cats&quot;}'      # MISS -&amp;gt; cats
curl -X QUERY /search -d '{ &quot;q&quot; : &quot;cats&quot; }'  # HIT  (same meaning)
curl -X QUERY /search -d '{&quot;q&quot;:&quot;dogs&quot;}'      # MISS -&amp;gt; dogs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That third line is the whole point: a different body gets the cr a stale hit.&lt;/p&gt;
&lt;h3 id=&quot;it-handles-the-rest-too&quot;&gt;It handles the rest too&lt;/h3&gt;
&lt;p&gt;Accept-Query negotiation, Content-Location &quot;switch to GET&quot; flow CORS-safelisted), an isomorphic client with safe auto-retry,and Fastify + raw http adapters over the same core.&lt;/p&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;Node &amp;gt;= 22 (where &lt;code&gt;QUERY&lt;/code&gt; lands in &lt;code&gt;http.METHODS&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install http-queryable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Repo + docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hardik-goel/http-queryable&quot;&gt;https://github.com/hardik-goel/http-queryable&lt;/a&gt; — stars and edge-case bug reports welcome.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24910/the-http-query-method-rfc-10008-is-here-and-caching-it-correctly-is-harder-than-it-looks</guid>
<pubDate>Wed, 19 Aug 2026 09:51:44 +0000</pubDate>
</item>
<item>
<title>Agent Mesh: Solving M2M,A2A payments</title>
<link>https://coderlegion.com/24876/agent-mesh-solving-m2m-a2a-payments</link>
<description>&lt;p&gt;Building the Agent Mesh: Solving Machine-to-Machine Payments (x402), Discovery, and Swarm Telemetry&lt;/p&gt;
&lt;p&gt;Hey Agents,&lt;/p&gt;
&lt;p&gt;Most multi-agent systems today hit the same structural wall: they rely on human-native rails.&lt;/p&gt;
&lt;p&gt;When Agent A needs compute, data, or tool execution from Agent B, it shouldn’t require human intervention, manual API key provisioning, or credit card subscriptions. Autonomous agents need machine-native economics, decentralized mesh discovery, and real-time swarm telemetry.&lt;/p&gt;
&lt;p&gt;We built ScriptMaster Labs as an infrastructure layer designed specifically for autonomous agent swarms and M2M coordination.&lt;/p&gt;
&lt;p&gt;The Stack: What We’re Solving. &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/langchain-citemesh/&quot;&gt;https://pypi.org/project/langchain-citemesh/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Machine-Native Economic Rails (x402 Protocol)&lt;/p&gt;
&lt;p&gt;Direct agent-to-agent micro-transactions and payment streams.&lt;/p&gt;
&lt;p&gt;Allows autonomous agents to buy and sell tool calls, data feeds, and compute per-request without manual billing friction.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Autonomous Agent Mesh &amp;amp; Swarm Memory&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Decentralized interconnects and low-latency telemetry routing for multi-agent swarms.&lt;/p&gt;
&lt;p&gt;Distributed state and memory sync across isolated execution environments.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Model Context Protocol (MCP) &amp;amp; Discovery Beacons&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Native MCP server implementations optimized for Smart Order Routers (SOR).&lt;/p&gt;
&lt;p&gt;Machine-readable beacons (Agent Magnet Beacons &amp;amp; Intention Market Protocols) for programmatic service discovery.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reputation &amp;amp; Verification Telemetry&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Machine-verifiable scoring, telemetry grading, and trust metrics for autonomous actors.&lt;/p&gt;
&lt;p&gt;Deterministic execution frameworks to eliminate hallucinated agent interactions.&lt;/p&gt;
&lt;p&gt;How to Connect&lt;/p&gt;
&lt;p&gt;If you're building autonomous swarms, hosting MCP tools, or experimenting with M2M micro-economies, explore the ecosystem at scriptmasterlabs.com.&lt;/p&gt;
&lt;p&gt;Curious to hear from other builders here: What are you currently using for agent-to-agent payments and swarm state coordination?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24876/agent-mesh-solving-m2m-a2a-payments</guid>
<pubDate>Wed, 19 Aug 2026 04:29:21 +0000</pubDate>
</item>
<item>
<title>The HTTP QUERY Method: Safe Reads with a Body</title>
<link>https://coderlegion.com/22958/the-http-query-method-safe-reads-with-a-body</link>
<description>&lt;p&gt;If you've ever built a search endpoint, you've hit this wall. Your query has filters, sort orders, a nested set of facets, maybe a geo bounding box. It doesn't fit in a URL, and cramming it into query string params is ugly and fragile. So you reach for &lt;code&gt;POST /search&lt;/code&gt;, send the whole thing as a JSON body, and quietly accept that you've just lied about what the request does. It's not creating anything. It's a read. But POST is the only tool that lets you attach a body without fighting the platform.&lt;/p&gt;
&lt;p&gt;That gap finally got filled. In June 2026 the IETF published &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/info/rfc10008/&quot;&gt;RFC 10008&lt;/a&gt;, which defines the HTTP QUERY method: a new verb built for exactly this case.&lt;/p&gt;
&lt;h3 id=&quot;the-two-bad-options&quot;&gt;The two bad options&lt;/h3&gt;
&lt;p&gt;Every read that needs structured input has been stuck choosing between GET and POST, and both are wrong in their own way.&lt;/p&gt;
&lt;p&gt;GET is the semantically correct choice. It's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-safe-methods&quot;&gt;safe&lt;/a&gt; (the client isn't asking to change anything), it's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-idempotent-methods&quot;&gt;idempotent&lt;/a&gt; (retrying it is fine), and it's cacheable. The problem is the body. RFC 9110 is explicit that &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-get&quot;&gt;content in a GET request has no defined semantics&lt;/a&gt;, and sending one may cause some implementations to reject the request. So your query has to live in the URI, where you run into unknown length limits across proxies and servers, encoding overhead, and the query landing in access logs and browser history.&lt;/p&gt;
&lt;p&gt;POST solves the body problem and creates a new one. It carries any payload you want, but it's neither safe nor idempotent by definition. Intermediaries won't cache it, clients won't retry it automatically after a dropped connection, and anything inspecting traffic has to assume the request might have side effects. You get the body, you lose everything that made the request honest.&lt;/p&gt;
&lt;p&gt;QUERY is the missing third option: a method that carries a body &lt;em&gt;and&lt;/em&gt; keeps the semantics of a read.&lt;/p&gt;
&lt;h3 id=&quot;what-query-actually-is&quot;&gt;What QUERY actually is&lt;/h3&gt;
&lt;p&gt;The spec, authored by Julian Reschke, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jasnell&quot;&gt;James Snell&lt;/a&gt;, and Mike Bishop, describes it in one sentence:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A QUERY requests that the request target process the enclosed content in a safe and idempotent manner and then respond with the result of that processing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So a QUERY request looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;QUERY /products HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json

{
  &quot;filters&quot;: { &quot;category&quot;: &quot;keyboards&quot;, &quot;inStock&quot;: true },
  &quot;sort&quot;: [{ &quot;field&quot;: &quot;price&quot;, &quot;order&quot;: &quot;asc&quot; }],
  &quot;page&quot;: { &quot;size&quot;: 20 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The three properties that matter:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's safe and idempotent.&lt;/strong&gt; The client isn't requesting a state change, and the request can be retried or repeated without worrying about partial effects. This is what POST could never promise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's cacheable.&lt;/strong&gt; A cache is allowed to store the response and use it to satisfy later QUERY requests. The catch is that the cache key has to include the request content, not just the URI, since the body is what distinguishes one query from another. Two QUERYs to the same URL with different bodies are different requests. In practice, getting a CDN to key on anything beyond the URI is its own fight, as I found out trying &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/configuring-my-site-for-ai-discoverability&quot;&gt;Accept-based content negotiation on Cloudflare&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The response isn't a representation of the URI.&lt;/strong&gt; Unlike GET, where the response &lt;em&gt;is&lt;/em&gt; the resource at that URL, a QUERY response is the result of running your query over some data scoped to the target. &lt;code&gt;GET /products&lt;/code&gt; returns the products resource; &lt;code&gt;QUERY /products&lt;/code&gt; returns whatever your query selected from it.&lt;/p&gt;
&lt;h3 id=&quot;the-details-that-bite&quot;&gt;The details that bite&lt;/h3&gt;
&lt;p&gt;A few rules are worth knowing before you wire this up.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Content-Type&lt;/code&gt; is mandatory. The server MUST reject the request if the &lt;code&gt;Content-Type&lt;/code&gt; field is missing or inconsistent with the body. In practice you'll see a &lt;code&gt;400&lt;/code&gt; for a missing media type, a &lt;code&gt;415 Unsupported Media Type&lt;/code&gt; for one the server doesn't handle, and a &lt;code&gt;422 Unprocessable Content&lt;/code&gt; when the body parses fine but doesn't make sense as a query.&lt;/p&gt;
&lt;p&gt;There are two response headers that trip people up because they sound alike:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Content-Location&lt;/code&gt;&lt;/strong&gt; points at a resource representing the &lt;em&gt;results&lt;/em&gt; of this query. A client can GET that URL later to fetch the same results again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Location&lt;/code&gt;&lt;/strong&gt; points at a resource representing the &lt;em&gt;query itself&lt;/em&gt;. A client can GET that URL to re-run the query without resending the body, which is handy for turning a heavy query into a shareable link.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Results points to the answer; Location points to the question.&lt;/p&gt;
&lt;p&gt;The spec also nudges you away from HTTP range requests for pagination. Range semantics technically apply, but most query formats already have their own paging built in (think SQL's &lt;code&gt;FETCH FIRST&lt;/code&gt;), and that's the mechanism you should reach for.&lt;/p&gt;
&lt;h3 id=&quot;can-you-use-http-query-today-&quot;&gt;Can you use HTTP QUERY today?&lt;/h3&gt;
&lt;p&gt;On the client, yes, already. The Fetch API and libraries like axios accept arbitrary method strings, so nothing is stopping you from sending one right now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const res = await fetch(&quot;/products&quot;, {
  method: &quot;QUERY&quot;,
  headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
  body: JSON.stringify({ filters: { category: &quot;keyboards&quot; } }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One gotcha before you do: pass the method in uppercase. &lt;a rel=&quot;nofollow&quot; href=&quot;https://fetch.spec.whatwg.org/#concept-method-normalize&quot;&gt;&lt;code&gt;fetch&lt;/code&gt; only normalizes the case&lt;/a&gt; of a fixed set of methods (&lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;), and QUERY isn't on it. Write &lt;code&gt;method: &quot;query&quot;&lt;/code&gt; and it goes out verbatim, which a case-sensitive server will reject.&lt;/p&gt;
&lt;p&gt;The friction is everywhere else in the stack. QUERY isn't a &lt;a rel=&quot;nofollow&quot; href=&quot;https://fetch.spec.whatwg.org/#cors-safelisted-method&quot;&gt;CORS-safelisted method&lt;/a&gt;, so cross-origin QUERY requests trigger a preflight &lt;code&gt;OPTIONS&lt;/code&gt;. Your server has to answer it with &lt;code&gt;Access-Control-Allow-Methods: QUERY&lt;/code&gt; or the browser blocks the real request. And anything that whitelists methods will reject QUERY until you tell it not to: reverse proxies, WAFs, API gateways, &lt;code&gt;limit_except&lt;/code&gt; blocks in nginx. The method passing through the wire is the easy part; the config that guards the wire is where you'll spend your time.&lt;/p&gt;
&lt;p&gt;Framework and server support is still landing. New HTTP methods don't come around often. The last one most people reached for was PATCH back in 2010, so the ecosystem moves slowly. Expect native routing helpers and middleware to fill in over the next couple of years rather than overnight.&lt;/p&gt;
&lt;h3 id=&quot;should-you-rush-to-switch-&quot;&gt;Should you rush to switch?&lt;/h3&gt;
&lt;p&gt;Probably not, and there's no need to. Your &lt;code&gt;POST /search&lt;/code&gt; endpoints work and aren't going anywhere. QUERY is the more correct tool, not an urgent migration.&lt;/p&gt;
&lt;p&gt;What it does give you is a real answer to a question we've been hacking around for years. It's the same pattern the &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/cross-origin-storage-api&quot;&gt;Cross-Origin Storage API&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/webmcp-making-your-site-usable-by-ai-agents&quot;&gt;WebMCP&lt;/a&gt; got right: the platform finally naming something we'd been faking. When you're designing a new read endpoint that needs a structured body, you now have a method that says exactly what it means: this is a safe, repeatable, cacheable read, and here's the query in the body where it belongs. That's worth reaching for on the next thing you build, even if the old endpoints stay put.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22958/the-http-query-method-safe-reads-with-a-body</guid>
<pubDate>Mon, 20 Jul 2026 09:21:55 +0000</pubDate>
</item>
<item>
<title>HTTP Status Codes Explained with a Resturant Analogy</title>
<link>https://coderlegion.com/22713/http-status-codes-explained-with-a-resturant-analogy</link>
<description>&lt;p&gt;You order food.The kitchen responds.That's literally what HTTP is - and once you get this, servers will never feel mysterious again.&lt;/p&gt;
&lt;p&gt;A little backstory&lt;/p&gt;
&lt;p&gt;I'm a newcomer to web development, and for the longest time, servers and HTTP felt like some dark magic I wasn't supposed to understand yet. That changed when I started working on my ASCII-ART-WEB project at Zone01.&lt;/p&gt;
&lt;p&gt;The project required me to build an actual HTTP server from scratch — handling requests, sending responses, setting the right status codes. Suddenly all of this wasn't theoretical anymore. I had to understand it. And when it clicked, it clicked hard.&lt;/p&gt;
&lt;p&gt;This article is me passing that click on to you, using the analogy that finally made it stick.&lt;/p&gt;
&lt;p&gt;The internet is a restaurant&lt;/p&gt;
&lt;p&gt;Seriously. Every time you type a URL into your browser, you're basically sitting at a table and calling a waiter over.&lt;/p&gt;
&lt;p&gt;HTTP stands for HyperText Transfer Protocol. It's just the agreed-upon language that lets your browser and the server talk to each other. Nothing more mysterious than that.&lt;/p&gt;
&lt;p&gt;Every page load is a tiny round-trip conversation:&lt;/p&gt;
&lt;p&gt;Browser → [HTTP request] → Server → [HTTP response + status code] → Browser renders the page&lt;/p&gt;
&lt;p&gt;Step by step:&lt;/p&gt;
&lt;p&gt;You type a URL and hit Enter&lt;br&gt;
Your browser sends an HTTP request to the server at that address&lt;br&gt;
The server receives the request and processes it&lt;br&gt;
The server sends back an HTTP response — which includes the data (a webpage, an image, JSON, etc.) and a status code&lt;br&gt;
Your browser reads the status code, then renders the page&lt;/p&gt;
&lt;p&gt;That status code is the kitchen's way of telling the waiter how the order went before the food even leaves the counter.&lt;/p&gt;
&lt;p&gt;What an HTTP request actually looks like&lt;/p&gt;
&lt;p&gt;When your browser asks for a page, it sends something like this under the hood:&lt;/p&gt;
&lt;p&gt;GET /menu HTTP/1.1&lt;br&gt;
Host: restaurant.com&lt;br&gt;
Accept: text/html&lt;/p&gt;
&lt;p&gt;And the server replies:&lt;/p&gt;
&lt;p&gt;HTTP/1.1 200 OK&lt;br&gt;
Content-Type: text/html&lt;br&gt;
Content-Length: 3456&lt;/p&gt;
&lt;p&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
...&lt;/p&gt;
&lt;p&gt;That 200 at the top? That's the status code. It's the first thing the browser checks.&lt;/p&gt;
&lt;p&gt;The four status codes every dev must know&lt;/p&gt;
&lt;p&gt;There are over 60 official HTTP status codes, but these four will cover 90% of what you encounter day-to-day.&lt;/p&gt;
&lt;p&gt;200 — OK&lt;/p&gt;
&lt;p&gt;&quot;The food arrived hot and exactly as ordered.&quot;&lt;/p&gt;
&lt;p&gt;The happy path. The server found what you asked for, processed everything correctly, and sent it back. This is what you want to see on every request.&lt;/p&gt;
&lt;p&gt; 400 — Bad Request&lt;/p&gt;
&lt;p&gt;&quot;You ordered in a language the kitchen doesn't understand.&quot;&lt;/p&gt;
&lt;p&gt;This means the request itself was broken — maybe it was missing required data, had invalid formatting, or sent something the server just can't parse. It's a client-side error, meaning the problem is in the request you sent, not the server.&lt;/p&gt;
&lt;p&gt; 404 — Not Found&lt;/p&gt;
&lt;p&gt;&quot;We checked the whole fridge. That dish doesn't exist here.&quot;&lt;/p&gt;
&lt;p&gt;The server is working fine, but the specific thing you asked for — that URL, that file, that route — simply doesn't exist. Classic example: you mistype a URL, or someone deleted a page that used to exist.&lt;/p&gt;
&lt;p&gt;500 — Internal Server Error&lt;/p&gt;
&lt;p&gt;&quot;The oven exploded. It's not your fault — something broke inside the kitchen.&quot;&lt;/p&gt;
&lt;p&gt;The server tried to handle your request but something went wrong in its own code. This is a server-side error. As the user, you did nothing wrong. As the developer, this is where you open your logs and start debugging.&lt;/p&gt;
&lt;p&gt;The golden rule of status codes&lt;/p&gt;
&lt;p&gt;4xx = The customer's fault&lt;br&gt;
5xx = The kitchen's fault&lt;/p&gt;
&lt;p&gt;Memorise this. When you're debugging and you see a 4xx, look at what you're sending. When you see a 5xx, look at what the server is doing with it.&lt;/p&gt;
&lt;p&gt;The full status code family tree&lt;br&gt;
Status codes are grouped by their first digit:&lt;br&gt;
1xx-Informational-&quot;Hold on,still cooking..&quot;&lt;br&gt;
2xx-Success-&quot;Here's your food,enjoy&quot;&lt;br&gt;
3xx-Redirection-&quot;Wrong table-they're at table 7 now&quot;&lt;br&gt;
4xx-Client error-&quot;Your order is the problem&quot;&lt;br&gt;
5xx-Server error-&quot;The kitchen is having a moment&quot;&lt;/p&gt;
&lt;p&gt;A few other useful ones you'll bump into:&lt;/p&gt;
&lt;p&gt;201 Created — used in APIs when something new was successfully created (like a new user account)&lt;br&gt;
301 Moved Permanently — the URL has changed forever, the server redirects you&lt;br&gt;
401 Unauthorized — you need to log in first&lt;br&gt;
403 Forbidden — you're logged in but you're not allowed to access this&lt;br&gt;
422 Unprocessable Entity — the server understood your request but couldn't process it (common in form validation)&lt;/p&gt;
&lt;p&gt;How ASCII-ART-WEB helped me understand all of this&lt;/p&gt;
&lt;p&gt;When I was working on the ASCII-ART-WEB project at Zone01, I had to build an HTTP server in Go that:&lt;/p&gt;
&lt;p&gt;Received a POST request with text from a web form&lt;br&gt;
Converted that text into ASCII art using a banner file&lt;br&gt;
Sent back the result as an HTTP response&lt;/p&gt;
&lt;p&gt;At first, I was just copying status codes without understanding them. Then I hit a bug: my server was sending 200 OK even when the input was invalid.&lt;/p&gt;
&lt;p&gt;A user would send an empty form — technically a bad request — and my server was responding like everything was fine. That's when the restaurant analogy hit me: if a customer orders nothing, the kitchen shouldn't just send back an empty plate with a smile. It should say &quot;400 — your order doesn't make sense.&quot;&lt;/p&gt;
&lt;p&gt;Once I fixed that, my server started behaving like a real server:&lt;/p&gt;
&lt;p&gt;go// If the form input is empty, respond with 400&lt;br&gt;
if userText == &quot;&quot; {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http.Error(w, &quot;400 - Bad Request: input cannot be empty&quot;, http.StatusBadRequest)
return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;// If the banner file doesn't exist, respond with 404&lt;br&gt;
if _, err := os.Stat(bannerFile); os.IsNotExist(err) {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http.Error(w, &quot;404 - Banner file not found&quot;, http.StatusNotFound)
return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;// If something unexpected breaks, respond with 500&lt;br&gt;
http.Error(w, &quot;500 - Internal Server Error&quot;, http.StatusInternalServerError)&lt;/p&gt;
&lt;p&gt;Suddenly HTTP wasn't abstract theory. Every status code had a job. Every response had a reason. The restaurant wasn't just an analogy anymore — it was the mental model I used every time I wrote a handler.&lt;/p&gt;
&lt;p&gt;Try it yourself right now&lt;/p&gt;
&lt;p&gt;Open your browser, press F12, and go to the Network tab. Reload any webpage.&lt;/p&gt;
&lt;p&gt;Every single row you see is an HTTP request, with its status code right there in the Status column. Watch the 200s flood in as the page loads. Try visiting a broken URL and watch the 404 appear. This is how real developers debug every single day — and seeing it live is the fastest way to make everything in this article click.&lt;/p&gt;
&lt;p&gt;Wrapping up&lt;/p&gt;
&lt;p&gt;HTTP is just two sides talking to each other — a customer and a kitchen. Status codes are how the kitchen reports back. Once you stop thinking of them as mysterious numbers and start thinking of them as kitchen updates, the whole web starts making more sense.&lt;/p&gt;
&lt;p&gt;If you're working on any project that touches a server — even a small one like ASCII-ART-WEB — I'd genuinely encourage you to try building a basic HTTP handler yourself. Reading about status codes is one thing. Returning the wrong one and watching your app break in a confusing way is what actually teaches you.&lt;/p&gt;
&lt;p&gt;Thanks for reading! If you found this helpful or you're also a newcomer grinding through your first server project, drop a comment — I'd love to hear what you're building.Alsoo don't forget to fllow and connect with me on linked in @mayan okul &lt;/p&gt;
&lt;p&gt;Status:200 OK.This article:served&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22713/http-status-codes-explained-with-a-resturant-analogy</guid>
<pubDate>Wed, 15 Jul 2026 05:29:10 +0000</pubDate>
</item>
<item>
<title>The New HTTP QUERY Method</title>
<link>https://coderlegion.com/22071/the-new-http-query-method</link>
<description>&lt;p&gt;If you've ever built a search endpoint, you've hit this wall. Your query has filters, sort orders, a nested set of facets, maybe a geo bounding box. It doesn't fit in a URL, and cramming it into query string params is ugly and fragile. So you reach for &lt;code&gt;POST /search&lt;/code&gt;, send the whole thing as a JSON body, and quietly accept that you've just lied about what the request does. It's not creating anything. It's a read. But POST is the only tool that lets you attach a body without fighting the platform.&lt;/p&gt;
&lt;p&gt;That gap finally got filled. In June 2026 the IETF published &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/info/rfc10008/&quot;&gt;RFC 10008&lt;/a&gt;, which defines the HTTP QUERY method: a new verb built for exactly this case.&lt;/p&gt;
&lt;h3 id=&quot;the-two-bad-options-1&quot;&gt;The two bad options&lt;/h3&gt;
&lt;p&gt;Every read that needs structured input has been stuck choosing between GET and POST, and both are wrong in their own way.&lt;/p&gt;
&lt;p&gt;GET is the semantically correct choice. It's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-safe-methods&quot;&gt;safe&lt;/a&gt; (the client isn't asking to change anything), it's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-idempotent-methods&quot;&gt;idempotent&lt;/a&gt; (retrying it is fine), and it's cacheable. The problem is the body. RFC 9110 is explicit that &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc9110#name-get&quot;&gt;content in a GET request has no defined semantics&lt;/a&gt;, and sending one may cause some implementations to reject the request. So your query has to live in the URI, where you run into unknown length limits across proxies and servers, encoding overhead, and the query landing in access logs and browser history.&lt;/p&gt;
&lt;p&gt;POST solves the body problem and creates a new one. It carries any payload you want, but it's neither safe nor idempotent by definition. Intermediaries won't cache it, clients won't retry it automatically after a dropped connection, and anything inspecting traffic has to assume the request might have side effects. You get the body, you lose everything that made the request honest.&lt;/p&gt;
&lt;p&gt;QUERY is the missing third option: a method that carries a body &lt;em&gt;and&lt;/em&gt; keeps the semantics of a read.&lt;/p&gt;
&lt;h3 id=&quot;what-query-actually-is-1&quot;&gt;What QUERY actually is&lt;/h3&gt;
&lt;p&gt;The spec, authored by Julian Reschke, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jasnell&quot;&gt;James Snell&lt;/a&gt;, and Mike Bishop, describes it in one sentence:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A QUERY requests that the request target process the enclosed content in a safe and idempotent manner and then respond with the result of that processing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So a QUERY request looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;QUERY /products HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json

{
  &quot;filters&quot;: { &quot;category&quot;: &quot;keyboards&quot;, &quot;inStock&quot;: true },
  &quot;sort&quot;: [{ &quot;field&quot;: &quot;price&quot;, &quot;order&quot;: &quot;asc&quot; }],
  &quot;page&quot;: { &quot;size&quot;: 20 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The three properties that matter:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's safe and idempotent.&lt;/strong&gt; The client isn't requesting a state change, and the request can be retried or repeated without worrying about partial effects. This is what POST could never promise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's cacheable.&lt;/strong&gt; A cache is allowed to store the response and use it to satisfy later QUERY requests. The catch is that the cache key has to include the request content, not just the URI, since the body is what distinguishes one query from another. Two QUERYs to the same URL with different bodies are different requests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The response isn't a representation of the URI.&lt;/strong&gt; Unlike GET, where the response &lt;em&gt;is&lt;/em&gt; the resource at that URL, a QUERY response is the result of running your query over some data scoped to the target. &lt;code&gt;GET /products&lt;/code&gt; returns the products resource; &lt;code&gt;QUERY /products&lt;/code&gt; returns whatever your query selected from it.&lt;/p&gt;
&lt;h3 id=&quot;the-details-that-bite-1&quot;&gt;The details that bite&lt;/h3&gt;
&lt;p&gt;A few rules are worth knowing before you wire this up.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Content-Type&lt;/code&gt; is mandatory. The server MUST reject the request if the &lt;code&gt;Content-Type&lt;/code&gt; field is missing or inconsistent with the body. In practice you'll see a &lt;code&gt;400&lt;/code&gt; for a missing media type, a &lt;code&gt;415 Unsupported Media Type&lt;/code&gt; for one the server doesn't handle, and a &lt;code&gt;422 Unprocessable Content&lt;/code&gt; when the body parses fine but doesn't make sense as a query.&lt;/p&gt;
&lt;p&gt;There are two response headers that trip people up because they sound alike:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Content-Location&lt;/code&gt;&lt;/strong&gt; points at a resource representing the &lt;em&gt;results&lt;/em&gt; of this query. A client can GET that URL later to fetch the same results again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Location&lt;/code&gt;&lt;/strong&gt; points at a resource representing the &lt;em&gt;query itself&lt;/em&gt;. A client can GET that URL to re-run the query without resending the body, which is handy for turning a heavy query into a shareable link.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Results points to the answer; Location points to the question.&lt;/p&gt;
&lt;p&gt;The spec also nudges you away from HTTP range requests for pagination. Range semantics technically apply, but most query formats already have their own paging built in (think SQL's &lt;code&gt;FETCH FIRST&lt;/code&gt;), and that's the mechanism you should reach for.&lt;/p&gt;
&lt;h3 id=&quot;can-you-use-http-query-today--1&quot;&gt;Can you use HTTP QUERY today?&lt;/h3&gt;
&lt;p&gt;On the client, yes, already. The Fetch API and libraries like axios accept arbitrary method strings, so nothing is stopping you from sending one right now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const res = await fetch(&quot;/products&quot;, {
  method: &quot;QUERY&quot;,
  headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
  body: JSON.stringify({ filters: { category: &quot;keyboards&quot; } }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The friction is everywhere else in the stack. QUERY isn't a &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header&quot;&gt;CORS-safelisted method&lt;/a&gt;, so cross-origin QUERY requests trigger a preflight &lt;code&gt;OPTIONS&lt;/code&gt;. Your server has to answer it with &lt;code&gt;Access-Control-Allow-Methods: QUERY&lt;/code&gt; or the browser blocks the real request. And anything that whitelists methods will reject QUERY until you tell it not to: reverse proxies, WAFs, API gateways, &lt;code&gt;limit_except&lt;/code&gt; blocks in nginx. The method passing through the wire is the easy part; the config that guards the wire is where you'll spend your time.&lt;/p&gt;
&lt;p&gt;Framework and server support is still landing. New HTTP methods don't come around often. The last one most people reached for was PATCH back in 2010, so the ecosystem moves slowly. Expect native routing helpers and middleware to fill in over the next couple of years rather than overnight.&lt;/p&gt;
&lt;h3 id=&quot;should-you-rush-to-switch--1&quot;&gt;Should you rush to switch?&lt;/h3&gt;
&lt;p&gt;Probably not, and there's no need to. Your &lt;code&gt;POST /search&lt;/code&gt; endpoints work and aren't going anywhere. QUERY is the more correct tool, not an urgent migration.&lt;/p&gt;
&lt;p&gt;What it does give you is a real answer to a question we've been hacking around for years. When you're designing a new read endpoint that needs a structured body, you now have a method that says exactly what it means: this is a safe, repeatable, cacheable read, and here's the query in the body where it belongs. That's worth reaching for on the next thing you build, even if the old endpoints stay put.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22071/the-new-http-query-method</guid>
<pubDate>Tue, 07 Jul 2026 18:50:12 +0000</pubDate>
</item>
<item>
<title>What Is HTTP &amp; HTTPS ?</title>
<link>https://coderlegion.com/19748/what-is-http-https</link>
<description>&lt;p&gt;Every time you open a browser and visit a website, something invisible but essential happens in the background — your browser and the server are having a conversation. The rules that govern that conversation are called &lt;strong&gt;HTTP&lt;/strong&gt;. And when that conversation needs to be private, we add a layer of security on top, giving us &lt;strong&gt;HTTPS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is the foundation every web developer should understand before writing a single line of backend code.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-http-&quot;&gt;What Is HTTP?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HTTP&lt;/strong&gt; stands for &lt;strong&gt;HyperText Transfer Protocol&lt;/strong&gt;. It is an application-layer protocol that defines how messages are formatted and transmitted between a &lt;strong&gt;client&lt;/strong&gt; (like your browser) and a &lt;strong&gt;server&lt;/strong&gt; (the machine hosting a website or API).&lt;/p&gt;
&lt;p&gt;Think of HTTP as a language — both the client and server speak it so they can understand each other. Without HTTP, a browser would have no standard way to ask for a webpage, and a server would have no standard way to respond.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key characteristics:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stateless&lt;/strong&gt; — each request is independent; the server has no memory of previous requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text-based&lt;/strong&gt; — requests and responses are human-readable plain text&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request/Response model&lt;/strong&gt; — the client always initiates; the server always responds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbga85rad007jc0n7ova.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Want a visual walkthrough?&lt;/strong&gt; Traversy Media's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=iYM2zFP3Zn0&quot;&gt;HTTP Crash Course &amp;amp; Exploration&lt;/a&gt; is one of the best video explanations out there. Highly recommended before moving on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-http-works-the-request-response-cycle&quot;&gt;How HTTP Works: The Request/Response Cycle&lt;/h3&gt;
&lt;p&gt;Every HTTP interaction follows a strict two-step pattern.&lt;/p&gt;
&lt;h4 id=&quot;step-1-the-request&quot;&gt;Step 1 — The Request&lt;/h4&gt;
&lt;p&gt;The client (your browser or app) sends a &lt;strong&gt;request&lt;/strong&gt; to the server. A raw HTTP request looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer &amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It has three parts:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Part&lt;/th&gt;&lt;th&gt;Example&lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Request Line&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;GET /users/123 HTTP/1.1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Method + path + protocol version&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Headers&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;Content-Type: application/json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Metadata about the request&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Body&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;{ &quot;name&quot;: &quot;Anne&quot; }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Data sent with the request (POST/PUT only)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;step-2-the-response&quot;&gt;Step 2 — The Response&lt;/h4&gt;
&lt;p&gt;The server receives the request, processes it, and sends back a &lt;strong&gt;response&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;HTTP/1.1 200 OK
Content-Type: application/json

{
  &quot;id&quot;: 123,
  &quot;name&quot;: &quot;Anne&quot;,
  &quot;email&quot;: &quot;anne@example.com&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It has three parts too:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Part&lt;/th&gt;&lt;th&gt;Example&lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Status Line&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Protocol version + status code&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Headers&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;Content-Type: application/json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Metadata about the response&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Body&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;{ &quot;id&quot;: 123, ... }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;The actual data returned&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;http-status-codes&quot;&gt;HTTP Status Codes&lt;/h3&gt;
&lt;p&gt;Status codes are the server's way of telling the client what happened. They are grouped into five families:&lt;/p&gt;
&lt;p&gt;![[Pasted image 20260529223910.png]]&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Range &lt;/th&gt;&lt;th&gt;Category      &lt;/th&gt;&lt;th&gt;Common Examples&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;1xx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Informational &lt;/td&gt;&lt;td&gt;&lt;code&gt;100 Continue&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;2xx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Success       &lt;/td&gt;&lt;td&gt;&lt;code&gt;200 OK&lt;/code&gt;, &lt;code&gt;201 Created&lt;/code&gt;, &lt;code&gt;204 No Content&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;3xx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Redirection   &lt;/td&gt;&lt;td&gt;&lt;code&gt;301 Moved Permanently&lt;/code&gt;, &lt;code&gt;304 Not Modified&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;4xx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Client Error  &lt;/td&gt;&lt;td&gt;&lt;code&gt;400 Bad Request&lt;/code&gt;, &lt;code&gt;401 Unauthorized&lt;/code&gt;, &lt;code&gt;404 Not Found&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;5xx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Server Error  &lt;/td&gt;&lt;td&gt;&lt;code&gt;500 Internal Server Error&lt;/code&gt;, &lt;code&gt;503 Service Unavailable&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; If it starts with &lt;code&gt;4&lt;/code&gt;, you (the client) did something wrong. If it starts with &lt;code&gt;5&lt;/code&gt;, the server broke.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;making-http-requests-in-go&quot;&gt;Making HTTP Requests in Go&lt;/h3&gt;
&lt;p&gt;Go's &lt;code&gt;net/http&lt;/code&gt; package gives you everything you need. Here is a complete example that makes a GET request and reads the response body:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;net/http&quot;
)

func main() {
	resp, err := http.Get(&quot;https://api.example.com/users/123&quot;)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	// Read the response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	fmt.Printf(&quot;Status : %s\n&quot;, resp.Status)
	fmt.Printf(&quot;Body   : %s\n&quot;, string(body))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also inspect headers directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;fmt.Println(resp.Header.Get(&quot;Content-Type&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-https-&quot;&gt;What Is HTTPS?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HTTPS&lt;/strong&gt; is simply HTTP with a security layer on top — specifically &lt;strong&gt;TLS&lt;/strong&gt; (Transport Layer Security), the modern successor to SSL.&lt;/p&gt;
&lt;p&gt;Without HTTPS, all data travels as plain text. Anyone sitting between the client and server — on a public Wi-Fi network, for example — can read it. HTTPS solves this by &lt;strong&gt;encrypting&lt;/strong&gt; the data before it travels.&lt;/p&gt;
&lt;p&gt;HTTPS gives you three guarantees:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Confidentiality&lt;/strong&gt; — data is encrypted; no one in the middle can read it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrity&lt;/strong&gt; — data cannot be tampered with in transit without detection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt; — you can trust you are actually talking to the right server&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-https-works-the-tls-handshake&quot;&gt;How HTTPS Works: The TLS Handshake&lt;/h3&gt;
&lt;p&gt;Before any encrypted data is sent, the client and server perform a &lt;strong&gt;TLS handshake&lt;/strong&gt; — a brief negotiation to agree on encryption settings and verify identities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cf-assets.www.cloudflare.com/slt3lc6tev37/5aYOr5erfyNBq20X5djTco/3c859532c91f25d961b2884bf521c1eb/tls-ssl-handshake.png&quot; alt=&quot;TLS/SSL Handshake Diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is a simplified view of the steps:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. CLIENT HELLO  →  &quot;Here are the TLS versions and ciphers I support.&quot;

2. SERVER HELLO  ←  &quot;OK, let's use TLS 1.3 and this cipher suite.&quot;
                    &quot;Here is my SSL certificate (signed by a trusted CA).&quot;

3. CLIENT        →  Verifies the certificate is valid and trusted.
                    Generates a session key and sends it encrypted.

4. BOTH SIDES       Derive the same symmetric session key.

5. ENCRYPTED        All further communication is encrypted.
   CHANNEL ✓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the handshake, everything works exactly like regular HTTP — except every message is encrypted. That is all HTTPS is: &lt;strong&gt;HTTP + TLS encryption&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;http-vs-https&quot;&gt;HTTP vs HTTPS&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;HTTP&lt;/th&gt;&lt;th&gt;HTTPS&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Port&lt;/td&gt;&lt;td&gt;&lt;code&gt;80&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;443&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Encryption&lt;/td&gt;&lt;td&gt;✗ None&lt;/td&gt;&lt;td&gt;✓ TLS/SSL&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Data in transit&lt;/td&gt;&lt;td&gt;Plain text&lt;/td&gt;&lt;td&gt;Encrypted&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Certificate required&lt;/td&gt;&lt;td&gt;✗&lt;/td&gt;&lt;td&gt;✓ (from a CA like Let's Encrypt)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;URL prefix&lt;/td&gt;&lt;td&gt;&lt;code&gt;http://&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Browser indicator&lt;/td&gt;&lt;td&gt;⚠ Not Secure&lt;/td&gt;&lt;td&gt;Secure&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SEO ranking&lt;/td&gt;&lt;td&gt;Lower&lt;/td&gt;&lt;td&gt;Higher (Google preference)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Use case&lt;/td&gt;&lt;td&gt;Internal tooling only&lt;/td&gt;&lt;td&gt;Everything public-facing&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Practical note:&lt;/strong&gt; There is no valid reason to use plain HTTP for a public-facing service in 2025. Free SSL certificates are available from &lt;a rel=&quot;nofollow&quot; href=&quot;https://letsencrypt.org/&quot;&gt;Let's Encrypt&lt;/a&gt;. Tools like Caddy and Nginx handle the setup automatically.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;making-https-requests-in-go&quot;&gt;Making HTTPS Requests in Go&lt;/h3&gt;
&lt;p&gt;Go's &lt;code&gt;http.Get&lt;/code&gt; and &lt;code&gt;http.Client&lt;/code&gt; use HTTPS by default when you pass an &lt;code&gt;https://&lt;/code&gt; URL — no extra setup needed. The TLS handshake happens automatically under the hood.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;net/http&quot;
)

func main() {
	// Go handles TLS automatically — just use https://
	resp, err := http.Get(&quot;https://api.example.com/users/123&quot;)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	fmt.Printf(&quot;Status : %s\n&quot;, resp.Status)
	fmt.Printf(&quot;Body   : %s\n&quot;, string(body))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to skip TLS verification in a local development environment (never in production), you can configure the transport:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
	&quot;crypto/tls&quot;
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;net/http&quot;
)

func main() {
	// ⚠ Only use InsecureSkipVerify in local dev — never in production
	transport := &amp;amp;http.Transport{
		TLSClientConfig: &amp;amp;tls.Config{InsecureSkipVerify: true},
	}
	client := &amp;amp;http.Client{Transport: transport}

	resp, err := client.Get(&quot;https://localhost:8443/health&quot;)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf(&quot;Status : %s\n&quot;, resp.Status)
	fmt.Printf(&quot;Body   : %s\n&quot;, string(body))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Concept&lt;/th&gt;&lt;th&gt;What It Is&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;HTTP&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;The protocol defining how clients and servers communicate&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Request&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;A client message containing a method, path, headers, and optional body&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Response&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;A server reply containing a status code, headers, and optional body&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Status Code&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;A 3-digit number telling the client what happened&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;HTTPS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;HTTP with TLS encryption layered on top&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;TLS Handshake&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;A negotiation that sets up the encrypted channel before data flows&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;further-reading-amp-watch&quot;&gt;Further Reading &amp;amp; Watch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=iYM2zFP3Zn0&quot;&gt;HTTP Crash Course &amp;amp; Exploration — Traversy Media&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=yk2YVIC-OeQ&quot;&gt;HTTP Crash Course from basics to advanced — Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview&quot;&gt;MDN — An Overview of HTTP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/&quot;&gt;Cloudflare — What Happens in a TLS Handshake?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://letsencrypt.org/&quot;&gt;Let's Encrypt — Free SSL/TLS Certificates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Now you know how the web actually talks. HTTP is the language; HTTPS is the same language, spoken in a locked room. Every API you build, every &lt;code&gt;http.Get&lt;/code&gt; you write, every status code you return — all of it runs on top of what you just learned.&lt;/p&gt;
&lt;p&gt;But what if there was a way to ditch the rigid rules of REST entirely and let the &lt;em&gt;client&lt;/em&gt; decide exactly what data it gets back — not one field more, not one field less? That is the problem &lt;strong&gt;GraphQL&lt;/strong&gt; was built to solve. And it changes how you think about APIs completely. See you in the next one.b&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19748/what-is-http-https</guid>
<pubDate>Fri, 05 Jun 2026 09:00:45 +0000</pubDate>
</item>
<item>
<title>I Was Tired of Installing API Tools Just to Test One Endpoint — So I Found Something Better</title>
<link>https://coderlegion.com/19604/i-was-tired-of-installing-api-tools-just-to-test-one-endpoint-so-i-found-something-better</link>
<description>&lt;p&gt;Let me paint you a picture.&lt;/p&gt;
&lt;p&gt;It's 11 PM. You're integrating a third-party payment API into your app. The documentation is decent, but something isn't working — you keep getting a &lt;code&gt;401 Unauthorized&lt;/code&gt; back, and you have no idea if it's your token, your headers, or just the universe conspiring against you.&lt;/p&gt;
&lt;p&gt;Your options? Fire up Postman (which you haven't opened in three weeks and now wants to update), install Insomnia (if you remember your account password), or try writing a quick curl command in the terminal — knowing full well you'll mess up the escaping on the JSON body and spend another 20 minutes Googling why.&lt;/p&gt;
&lt;p&gt;There's a fourth option. One that requires zero installs, zero accounts, and zero patience for setup.&lt;/p&gt;
&lt;p&gt;But before we get there — let's actually talk about REST APIs, because if you're new to this world, a lot of it can feel unnecessarily mysterious.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-even-is-a-rest-api-&quot;&gt;What Even Is a REST API?&lt;/h3&gt;
&lt;p&gt;Here's the non-textbook version: a REST API is just a way for two systems to talk to each other over the internet using standard web rules.&lt;/p&gt;
&lt;p&gt;When you open Instagram and your feed loads — that's your app making an API call to Instagram's servers. When you book a flight and your seat gets confirmed — that's an API call going to the airline's backend. When Slack notifies you of a new message on your phone — you guessed it.&lt;/p&gt;
&lt;p&gt;REST stands for &lt;strong&gt;Representational State Transfer&lt;/strong&gt;, which sounds incredibly academic and not particularly useful to know. What matters is the practical part: REST APIs use regular HTTP (the same protocol your browser uses), they deal in &lt;strong&gt;resources&lt;/strong&gt; (think: users, orders, products), and they communicate mostly in &lt;strong&gt;JSON&lt;/strong&gt; — a format that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;user&quot;: &quot;sadiq&quot;,
  &quot;status&quot;: &quot;active&quot;,
  &quot;plan&quot;: &quot;pro&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clean. Human-readable. Easy to work with.&lt;/p&gt;
&lt;p&gt;The other thing you need to know is that REST APIs use &lt;strong&gt;HTTP methods&lt;/strong&gt; to express intent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GET&lt;/strong&gt; — &quot;Give me something.&quot; Fetching a list of users, loading a profile, retrieving an order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POST&lt;/strong&gt; — &quot;Create something new.&quot; Submitting a form, registering an account, placing an order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PUT&lt;/strong&gt; — &quot;Replace this thing entirely.&quot; Updating a user's full profile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PATCH&lt;/strong&gt; — &quot;Just change this one part.&quot; Updating only the email address on a profile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DELETE&lt;/strong&gt; — &quot;Get rid of this.&quot; Removing a record.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These five methods cover about 99% of what you'll encounter in real-world API work. The other two — &lt;strong&gt;HEAD&lt;/strong&gt; and &lt;strong&gt;OPTIONS&lt;/strong&gt; — are more behind-the-scenes, usually used for checking if a resource exists or for CORS preflight checks.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-with-testing-apis&quot;&gt;The Problem With Testing APIs&lt;/h3&gt;
&lt;p&gt;Here's something nobody talks about enough: &lt;strong&gt;testing APIs is just hard enough to be annoying&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It's not hard like writing a compiler or implementing a sorting algorithm. It's hard in a friction-filled, setup-heavy, &quot;why am I spending 20 minutes on this&quot; kind of way.&lt;/p&gt;
&lt;p&gt;If you're a backend developer, you probably have curl muscle memory by now. But curl is unforgiving — one misplaced quote or escaped character and your beautifully crafted JSON body turns into garbage. And reading curl output when something goes wrong? Not exactly a pleasant experience.&lt;/p&gt;
&lt;p&gt;Postman is the gold standard for a lot of teams, and for good reason — it's powerful. But powerful also means complex. Collections, environments, workspaces, team sync — it's a lot when you just want to fire off a quick request to see if an endpoint returns what it's supposed to. There's also the matter of it being a desktop app you need to install, update, and sometimes log into.&lt;/p&gt;
&lt;p&gt;The other issue that catches developers off-guard — especially those newer to frontend work — is &lt;strong&gt;CORS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;CORS (Cross-Origin Resource Sharing) is a browser security feature that blocks your JavaScript from calling APIs on different domains unless the API explicitly says &quot;yes, this domain can talk to me.&quot; It exists for genuinely good reasons (preventing malicious websites from making requests on your behalf), but it's also the source of an enormous amount of confusion and pain.&lt;/p&gt;
&lt;p&gt;You'll write what looks like perfect code. You'll test it. You'll get a wall of red text in your browser console that says something like &lt;code&gt;Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy&lt;/code&gt;. And you'll wonder if you've done something horribly wrong.&lt;/p&gt;
&lt;p&gt;You haven't. The API just doesn't allow browser-based requests from your origin. It'll work fine server-to-server — which is exactly why a server-side testing tool is so much more useful than a browser-based one.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;meet-the-tool-that-actually-fixes-this&quot;&gt;Meet the Tool That Actually Fixes This&lt;/h3&gt;
&lt;p&gt;There's a free online REST API tester over at &lt;a rel=&quot;nofollow&quot; href=&quot;https://sadiqbd.com&quot;&gt;sadiqbd.com&lt;/a&gt; — specifically the &lt;a rel=&quot;nofollow&quot; href=&quot;https://sadiqbd.com/developer/rest-api-checker&quot;&gt;REST API Checker&lt;/a&gt; — and honestly, it's become one of those tools I didn't know I needed until I started using it.&lt;/p&gt;
&lt;p&gt;The concept is simple: you paste in your API URL, choose your method, add any headers or a request body, and hit &lt;strong&gt;Send&lt;/strong&gt;. Within seconds you get back the full response — status code, response headers, response body (pretty-printed if it's JSON), and the time it took.&lt;/p&gt;
&lt;p&gt;What makes it different from just opening your browser's Network tab is the &lt;strong&gt;server-side proxying&lt;/strong&gt;. When you use this tool, the request doesn't come from your browser — it comes from their server. That completely bypasses CORS. It doesn't matter if the API has restrictive CORS headers or none at all. You'll get the real response, every time.&lt;/p&gt;
&lt;p&gt;Let me walk through what it actually supports:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP Methods:&lt;/strong&gt; GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. Every method you'll realistically need.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Custom Headers:&lt;/strong&gt; Add as many as you need. Authorization tokens, Content-Type overrides, custom API keys — any header the API expects, you can set it. The Authorization header field especially comes in handy when you're debugging authentication: &lt;code&gt;Bearer your-token-here&lt;/code&gt; goes in just like you'd set it in code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Request Body Formats:&lt;/strong&gt; Three options — JSON (which auto-sets &lt;code&gt;Content-Type: application/json&lt;/code&gt;), raw (for XML, plain text, or anything else), and multipart form-data (for file uploads). This is actually comprehensive enough to cover most real-world API testing scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Response Details:&lt;/strong&gt; You get the status code, response time, body size, all response headers, and the formatted body. If the API returns JSON, it's automatically prettified so you can actually read it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;real-scenarios-where-this-saves-time&quot;&gt;Real Scenarios Where This Saves Time&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1: You're integrating a new API and want to verify it works before writing any code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the fastest use case. Copy the example request from the documentation, paste it in, run it. If it returns what the docs say it should, you know the API is working and your credentials are valid. Now you can write code with confidence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 2: Something's broken in production and you need to isolate whether it's your code or the API.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hit the API directly from the tester with the same payload your code sends. If it works there but not in your app, the bug is in your code. If it fails there too, the API is the problem. Five seconds of testing that would have taken you 20 minutes to debug otherwise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 3: You're getting a 400 Bad Request and have no idea why.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is where the response body matters. Most well-designed APIs return a JSON error object explaining what went wrong — missing field, wrong data type, invalid value. The pretty-printed response makes this readable immediately. Common culprits: missing required fields, wrong data types, or subtly malformed JSON (a trailing comma that's valid in JavaScript but not in strict JSON parsers).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 4: You're testing a webhook.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Webhooks are notoriously painful to debug. With this tool, you can replay a webhook payload to your endpoint and see exactly what it returns — useful for reproducing intermittent failures or testing your error handling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario 5: Comparing staging vs. production.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Swap the URL, keep everything else the same. If staging returns something different from production, you've found your environment drift. This kind of quick comparison is surprisingly hard to do cleanly with other tools.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-few-things-worth-knowing&quot;&gt;A Few Things Worth Knowing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;On API key safety:&lt;/strong&gt; The tool doesn't store your request bodies, headers, or responses. That said, for genuinely sensitive production credentials — the ones that control real money or user data — it's wise to use scoped test keys or short-lived tokens. Not because this tool is untrustworthy, but because that's just good security hygiene regardless of what tool you're using.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On private/internal APIs:&lt;/strong&gt; Since requests are made server-side, they need to reach a publicly accessible URL. Private IPs and loopback addresses (localhost, 192.168.x.x, etc.) are blocked for security reasons. For internal APIs, you'd need a tunneling tool like ngrok to expose them temporarily.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On file uploads:&lt;/strong&gt; The multipart form-data option supports file attachments up to 5 MB each. That covers most practical file upload API testing scenarios — image upload endpoints, document imports, that kind of thing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;beyond-rest-api-testing&quot;&gt;Beyond REST API Testing&lt;/h3&gt;
&lt;p&gt;While you're on &lt;a rel=&quot;nofollow&quot; href=&quot;https://sadiqbd.com&quot;&gt;sadiqbd.com&lt;/a&gt;, it's worth knowing the site is actually a full toolkit for developers and beyond. The developer section alone has a JSON Formatter (genuinely one of the most-used tools when working with APIs), a JWT Decoder (paste in any JWT and instantly see the header, payload, and expiry), a Regex Tester, Hash Generator, Base64 Encoder/Decoder, and more.&lt;/p&gt;
&lt;p&gt;There are also internet tools for DNS lookups, SSL checking, HTTP header inspection, and port scanning — the kind of things you end up Googling every few weeks and wishing were in one place.&lt;/p&gt;
&lt;p&gt;It's the sort of site that earns a browser bookmark pretty quickly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-bigger-picture&quot;&gt;The Bigger Picture&lt;/h3&gt;
&lt;p&gt;Here's what I actually think about when I look at a tool like this: the best developer tools are the ones that collapse friction.&lt;/p&gt;
&lt;p&gt;The gap between &quot;I want to test this API&quot; and &quot;I have my test result in front of me&quot; should be as small as possible. Every minute spent setting up a client, remembering a password, updating software, or wrestling with a curl command is a minute not spent on the actual problem you're trying to solve.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://sadiqbd.com/developer/rest-api-checker&quot;&gt;REST API Checker on sadiqbd.com&lt;/a&gt; doesn't do everything Postman does. It doesn't have saved collections or team collaboration or automated test suites. But it does the thing you need most of the time — send a request, see the response — with literally zero friction. Open a tab, paste a URL, click Send.&lt;/p&gt;
&lt;p&gt;That's it.&lt;/p&gt;
&lt;p&gt;For the quick tests, the late-night debugging sessions, the &quot;let me just verify this works before I write the integration&quot; moments — it's hard to beat.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;REST APIs are everywhere. Understanding them — even at a surface level — makes you dramatically more effective as a developer, whether you're building backends, working on frontend integrations, or just trying to make sense of why something isn't working.&lt;/p&gt;
&lt;p&gt;And having a fast, reliable, no-fuss way to test them? That's not a luxury. It's just practical.&lt;/p&gt;
&lt;p&gt;Bookmark &lt;a rel=&quot;nofollow&quot; href=&quot;https://sadiqbd.com/developer/rest-api-checker&quot;&gt;sadiqbd.com/developer/rest-api-checker&lt;/a&gt;. You'll use it more than you expect.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Found this useful? The full suite of developer tools — JSON formatter, JWT decoder, regex tester, and more — lives at &lt;a rel=&quot;nofollow&quot; href=&quot;https://sadiqbd.com&quot;&gt;sadiqbd.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19604/i-was-tired-of-installing-api-tools-just-to-test-one-endpoint-so-i-found-something-better</guid>
<pubDate>Wed, 03 Jun 2026 18:41:12 +0000</pubDate>
</item>
<item>
<title>HTTP Status Codes You Never Knew Existed</title>
<link>https://coderlegion.com/13888/http-status-codes-you-never-knew-existed</link>
<description>&lt;p&gt;Everyone knows 200 OK, 404 Not Found, and 500 Internal Server Error — but HTTP has over 60 status codes, and some of them are genuinely useful yet barely talked about.&lt;/p&gt;
&lt;p&gt;Here are some hidden gems:&lt;/p&gt;
&lt;p&gt;418 I'm a Teapot ☕&lt;br&gt;
Yes, this is real. Originally an April Fools' joke (RFC 2324), but it's in the spec. Some devs use it to reject requests that &quot;shouldn't&quot; hit a server.&lt;/p&gt;
&lt;p&gt;207 Multi-Status&lt;br&gt;
Returns multiple status codes in one response body. Super useful for bulk operations — e.g., when you upload 10 files and some succeed while others fail.&lt;/p&gt;
&lt;p&gt;409 Conflict&lt;br&gt;
More specific than 400 Bad Request. Use this when a request conflicts with the current state — like trying to create a username that already exists.&lt;/p&gt;
&lt;p&gt;410 Gone&lt;br&gt;
Different from 404. Use 410 when a resource used to exist but has been permanently deleted. Great for SEO — tells crawlers to stop indexing it.&lt;/p&gt;
&lt;p&gt;425 Too Early&lt;br&gt;
Tells the client not to retry yet because the server isn't ready to process it safely. Useful in early-data TLS scenarios.&lt;/p&gt;
&lt;p&gt;103 Early Hints&lt;br&gt;
Sent before the final response to let the browser start preloading resources. Can noticeably improve page load performance.&lt;/p&gt;
&lt;p&gt;Which status codes do you actually use in your projects? Drop them below &lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/13888/http-status-codes-you-never-knew-existed</guid>
<pubDate>Fri, 03 Apr 2026 06:11:29 +0000</pubDate>
</item>
<item>
<title>HTTP vs HTTPS: A Practical Guide for Developers</title>
<link>https://coderlegion.com/11393/http-vs-https-a-practical-guide-for-developers</link>
<description>&lt;h2 id=&quot;http-vs-https-a-practical-guide-for-developers&quot;&gt;HTTP vs HTTPS: A Practical Guide for Developers&lt;/h2&gt;
&lt;p&gt;When working on real-world projects, one mistake beginners often make is ignoring website security. One of the simplest but most important decisions is choosing &lt;strong&gt;HTTPS instead of HTTP&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This short guide explains the difference in a &lt;strong&gt;practical, developer-focused way&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;http-why-it-s-not-safe&quot;&gt;HTTP: Why It’s Not Safe&lt;/h3&gt;
&lt;p&gt;HTTP sends data between the browser and server in &lt;strong&gt;plain text&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Login credentials can be intercepted&lt;/li&gt;
&lt;li&gt;Form data is exposed&lt;/li&gt;
&lt;li&gt;Vulnerable to Man-in-the-Middle (MITM) attacks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes HTTP unsuitable for any production-level project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;https-the-secure-standard&quot;&gt;HTTPS: The Secure Standard&lt;/h3&gt;
&lt;p&gt;HTTPS secures communication using &lt;strong&gt;SSL/TLS encryption&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In practice, HTTPS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Encrypts request and response data&lt;/li&gt;
&lt;li&gt;Protects passwords and user input&lt;/li&gt;
&lt;li&gt;Verifies server identity&lt;/li&gt;
&lt;li&gt;Prevents data tampering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Modern browsers trust HTTPS — and warn users about HTTP.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-visual-reference&quot;&gt;Quick Visual Reference&lt;/h3&gt;
&lt;p&gt; &lt;strong&gt; HTTP vs HTTPS comparison image&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13513868768293330809&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;http-vs-https-quick-comparison-&quot;&gt;HTTP vs HTTPS (Quick Comparison)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature                  &lt;/th&gt;&lt;th&gt;HTTP &lt;/th&gt;&lt;th&gt;HTTPS&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Encryption               &lt;/td&gt;&lt;td&gt;❌ No &lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Security                 &lt;/td&gt;&lt;td&gt;Low  &lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Browser Warning          &lt;/td&gt;&lt;td&gt;Yes  &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Suitable for Login Forms &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;hr&gt;
&lt;h3 id=&quot;when-https-is-mandatory&quot;&gt;When HTTPS Is Mandatory&lt;/h3&gt;
&lt;p&gt;Use HTTPS if your project includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Login / signup systems&lt;/li&gt;
&lt;li&gt;Contact forms&lt;/li&gt;
&lt;li&gt;Admin dashboards&lt;/li&gt;
&lt;li&gt;Payment or user data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short: &lt;strong&gt;almost every real project&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-takeaway&quot;&gt;Final Takeaway&lt;/h3&gt;
&lt;p&gt;HTTP may work for learning demos, but HTTPS is required for real applications.&lt;/p&gt;
&lt;p&gt;If you want your projects to look professional, secure and production-ready — always use &lt;strong&gt;HTTPS by default&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;#webdev #security #https #Web Security #http #beginners #programming #ReactJS #FullStackDevelopment #WebDevelopment #MuhammadYasir #YasirAwan4831 YasirAwaninfo&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11393/http-vs-https-a-practical-guide-for-developers</guid>
<pubDate>Tue, 10 Feb 2026 19:35:17 +0000</pubDate>
</item>
</channel>
</rss>