<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chirag Aggarwal</title>
    <description>The latest articles on DEV Community by Chirag Aggarwal (@chiragagg5k).</description>
    <link>https://dev.to/chiragagg5k</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F963052%2F5823e752-1d03-4f78-96ae-b9beec03752f.png</url>
      <title>DEV Community: Chirag Aggarwal</title>
      <link>https://dev.to/chiragagg5k</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chiragagg5k"/>
    <language>en</language>
    <item>
      <title>How utopia-php/client keeps fixing our memory leaks</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Wed, 05 Aug 2026 06:36:19 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/how-utopia-phpclient-keeps-fixing-our-memory-leaks-57f3</link>
      <guid>https://dev.to/chiragagg5k/how-utopia-phpclient-keeps-fixing-our-memory-leaks-57f3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://chirag.appwrite.network/blog/how-utopia-php-client-keeps-fixing-our-memory-leaks" rel="noopener noreferrer"&gt;my site&lt;/a&gt;, where the three diagrams are interactive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of our billing workers kept dying.&lt;/p&gt;

&lt;p&gt;It would sit flat for hours, then climb into its memory limit in about four minutes and get killed. Kubernetes restarted it, and later the same day it happened again. Six times a day, every day.&lt;/p&gt;

&lt;p&gt;The obvious first move is to look at what PHP is holding on to, and that's where it got strange. When PHP runs out of memory it tells you: a fatal error, a stack trace, the allocation that tipped it over. We had none of that. &lt;code&gt;memory_get_usage()&lt;/code&gt; reported a flat 6 MB the entire time, right up to the moment the process vanished.&lt;/p&gt;

&lt;p&gt;Those two facts together are the whole puzzle. Something was eating memory, and it wasn't the PHP heap. It was cURL, two sockets at a time, and the cause turned out to be a missing keyword.&lt;/p&gt;

&lt;p&gt;We've chased this same shape in more than one Appwrite service now, and the fix has been the same every time: stop building a new HTTP client for every call, and move the library onto &lt;code&gt;utopia-php/client&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually leaking
&lt;/h2&gt;

&lt;p&gt;The worker idles at ~330 MB against a 512Mi limit, which is the flat stretch below. Once a day the newly-due invoice batch runs, about a thousand invoices with several Stripe calls each, and it goes from comfortable to dead in roughly four minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjn9sb1wy1lit8h9q7qw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjn9sb1wy1lit8h9q7qw2.png" alt="Grafana panel titled Memory Usage, Requests and Limits by container, plotting percent of the container limit against time from 12:00 to 11:00 the next morning, with the vertical axis running from 45 to 100 percent. The series enters from the bottom just before 15:00 and holds between 57 and 62 percent for six hours with a repeating sawtooth. Just after 20:45 it turns almost vertical, passes 100 percent, and the series stops." width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Memory as a percentage of the container limit; the axis starts at 45% because nothing happens below it. Six hours flat around 60%, then the batch starts and the line goes vertical: 332 MB, 374, 450, 533, against a 536 MB ceiling.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Luke traced it, and the code turned out to be completely unremarkable. &lt;code&gt;Pay\Adapter::call()&lt;/code&gt; built a &lt;code&gt;new Utopia\Fetch\Client&lt;/code&gt; for every request. In a one-shot FPM request that's fine; the process exits and the OS reclaims everything. In a long-lived worker it's a slow bleed, and the reason is a reference cycle that has nothing to do with the PHP heap.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl_setopt&lt;/code&gt; stores your write callback on the &lt;code&gt;CurlHandle&lt;/code&gt;. A closure declared inside an instance method captures &lt;code&gt;$this&lt;/code&gt;. So the handle holds the closure, the closure holds the adapter, and the adapter holds the handle. Nothing can be freed by refcounting, and &lt;code&gt;__destruct&lt;/code&gt;, with its &lt;code&gt;curl_close&lt;/code&gt;, only runs when PHP's cycle collector fires. By default that means after 10,000 cycle roots have piled up. Until then every request strands an open keep-alive connection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdmvw4glhcw1y90hl65mu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdmvw4glhcw1y90hl65mu.png" alt="Who is holding the cURL handle?" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reduced down, that's this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Curl&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?CurlHandle&lt;/span&gt; &lt;span class="nv"&gt;$handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// the adapter holds the handle&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Nothing in here mentions $this. Declaring a closure inside an&lt;/span&gt;
        &lt;span class="c1"&gt;// instance method binds it anyway, and curl_setopt parks the&lt;/span&gt;
        &lt;span class="c1"&gt;// closure on the handle: adapter -&amp;gt; handle -&amp;gt; closure -&amp;gt; adapter.&lt;/span&gt;
        &lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;CURLOPT_WRITEFUNCTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is one keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;static&lt;/code&gt; closure gets no &lt;code&gt;$this&lt;/code&gt;, so the last edge never forms and refcounting frees the handle as soon as the adapter goes out of scope. Neither callback used &lt;code&gt;$this&lt;/code&gt; in the first place, which is what makes the change safe and also what makes it so easy to miss.&lt;/p&gt;

&lt;p&gt;Two file descriptors and roughly a megabyte of native TLS buffers per request, none of it visible to &lt;code&gt;memory_get_usage()&lt;/code&gt;. Descriptors are capped per process too, so whichever ceiling you reach first decides how the thing dies: the kernel OOMKills you, or you start refusing connections with &lt;code&gt;Too many open files&lt;/code&gt;. I've written that exact closure before, probably more than once, and it would never have occurred to me that &lt;code&gt;static&lt;/code&gt; was load-bearing. Adding it to both callbacks in &lt;code&gt;utopia-php/fetch&lt;/code&gt; took 150 sequential requests from 309 fds and 183 MB RSS to 9 fds and 31 MB, flat.&lt;/p&gt;

&lt;p&gt;We shipped that and moved on, which was the wrong instinct. It fixed the symptom in one library and left three others still hand-rolling their own transport, each free to reinvent the same bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the library is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;utopia-php/client&lt;/code&gt; is a PSR-18 HTTP client for PHP 8.5. It's about 2,400 lines including both transports, and it doesn't try to be clever:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjbze24l6rjnwdkfa6gii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjbze24l6rjnwdkfa6gii.png" alt="The layers of utopia-php/client" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every layer implements the same Adapter interface, so they stack in any order and a caller can replace any of them with a stub.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Utopia\Client&lt;/code&gt; itself does almost nothing at request time. It resolves the URI against a base, fills in default headers, optionally stamps a &lt;code&gt;traceparent&lt;/code&gt;, and hands the request to an adapter. Connection lifetime, TLS, timeouts and error classification all live in the adapter. Anything policy-shaped lives in a decorator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CurlAdapter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withBaseUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api.stripe.com/v1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withBearerAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withConnectionReuse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request\Factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Method&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'customers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which specs it holds itself to
&lt;/h2&gt;

&lt;p&gt;Writing your own HTTP transport in five places means reading the specs badly in five places. Here's what the shared one is on the hook for.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Spec&lt;/th&gt;
&lt;th&gt;What it decides&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PSR-18&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;4xx&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt; are responses; the two-branch exception contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PSR-7 / PSR-17&lt;/td&gt;
&lt;td&gt;Immutable messages and the factories that build them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 9110&lt;/td&gt;
&lt;td&gt;Idempotency, &lt;code&gt;Retry-After&lt;/code&gt;, &lt;code&gt;Authorization&lt;/code&gt;, content negotiation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 9112&lt;/td&gt;
&lt;td&gt;HTTP/1.1 framing, chunked bodies, status-line parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 9113&lt;/td&gt;
&lt;td&gt;HTTP/2, which APNs requires and HTTP/1.1 can't satisfy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 3986&lt;/td&gt;
&lt;td&gt;Base-URI resolution and dot-segment removal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 6750 / 7617&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Bearer&lt;/code&gt; and &lt;code&gt;Basic&lt;/code&gt; credential formats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 7578 / 2046 / 2183&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;multipart/form-data&lt;/code&gt;, boundaries, &lt;code&gt;Content-Disposition&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 1951 / 1952 / 7932 / 8878&lt;/td&gt;
&lt;td&gt;deflate, gzip, br, zstd content codings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFC 8446&lt;/td&gt;
&lt;td&gt;TLS 1.3, and the floor you can pin below it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W3C Trace Context&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;traceparent&lt;/code&gt; propagation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  PSR-18 decides what counts as an error
&lt;/h3&gt;

&lt;p&gt;The sentence that does the most work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Client MUST NOT treat a well-formed HTTP request or HTTP response as an error condition. For example, response status codes in the 400 and 500 range MUST NOT cause an exception and MUST be returned to the Calling Library as normal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;code&gt;429&lt;/code&gt; is not a failure, it's an answer. So &lt;code&gt;sendRequest()&lt;/code&gt; returns it, and only genuine "there is no response" conditions throw. PSR-18 splits those into two branches: &lt;code&gt;RequestExceptionInterface&lt;/code&gt; for a malformed request or response, &lt;code&gt;NetworkExceptionInterface&lt;/code&gt; for a transport that failed. The type answers one question, which is the only one you have at the catch site. Would trying again help?&lt;/p&gt;

&lt;p&gt;The library's own hierarchy keeps that property all the way down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClientExceptionInterface
├── NetworkExceptionInterface — retrying may help
│   └── NetworkException
│       ├── DnsException, TimeoutException, ProtocolException, ProxyException
│       └── ConnectionException
│           └── TlsException
└── RequestExceptionInterface — retrying is pointless
    └── RequestException
        ├── InvalidUriException, InvalidResponseException
        └── AdapterPreconditionException, AdapterInitializationException
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each adapter maps its native error codes into that tree. The cURL adapter matches on &lt;code&gt;CURLE_*&lt;/code&gt; constants, guarded by &lt;code&gt;defined()&lt;/code&gt; so a libcurl build without HTTP/3 doesn't fatal at load.&lt;/p&gt;

&lt;p&gt;Another line from the spec you can find in the code almost verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a Client chooses to decompress the message body then it MUST also remove the &lt;code&gt;Content-Encoding&lt;/code&gt; header and adjust the &lt;code&gt;Content-Length&lt;/code&gt; header.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both adapters negotiate compression for you: the request advertises whatever codecs the transport can decode, and the response arrives as plaintext. Which means the &lt;code&gt;Content-Encoding: gzip&lt;/code&gt; and &lt;code&gt;Content-Length&lt;/code&gt; the server sent are now lies about the body you're holding, so the adapter drops both. Set your own &lt;code&gt;Accept-Encoding&lt;/code&gt; and it gets out of the way entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrying, and why only some requests get to
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Retry&lt;/code&gt; is a decorator, and its default &lt;code&gt;Backoff&lt;/code&gt; strategy reads almost directly off RFC 9110. Only idempotent methods (§9.2.2) are retried, so a lost response can't turn into a double charge. Only transient outcomes are retried: a &lt;code&gt;NetworkExceptionInterface&lt;/code&gt;, or a &lt;code&gt;429&lt;/code&gt; / &lt;code&gt;502&lt;/code&gt; / &lt;code&gt;503&lt;/code&gt; / &lt;code&gt;504&lt;/code&gt;. A numeric &lt;code&gt;Retry-After&lt;/code&gt; (§10.2.3) beats the computed delay, because the server knows things the client doesn't.&lt;/p&gt;

&lt;p&gt;With no &lt;code&gt;Retry-After&lt;/code&gt;, the wait is exponential with full jitter: a value drawn uniformly from &lt;code&gt;[0, ceiling)&lt;/code&gt; rather than the ceiling itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5bzcfs64r1afkjlzgmuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5bzcfs64r1afkjlzgmuh.png" alt="Exponential backoff with and without full jitter" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our workers run as a fleet. A fleet that backs off deterministically comes back at a struggling upstream in lockstep and keeps it struggling.&lt;/p&gt;

&lt;p&gt;Every one of those decisions lives behind a single method, so a library with different rules writes its own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Strategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;RequestInterface&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;?ResponseInterface&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;?ClientExceptionInterface&lt;/span&gt; &lt;span class="nv"&gt;$error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?float&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;utopia-php/storage&lt;/code&gt; does exactly that. S3 signals throttling in an XML body as often as in a status code, so &lt;code&gt;S3\RetryStrategy&lt;/code&gt; parses the body first and retries &lt;code&gt;SlowDown&lt;/code&gt;, &lt;code&gt;ServiceUnavailable&lt;/code&gt;, &lt;code&gt;Throttling&lt;/code&gt; and &lt;code&gt;RequestThrottled&lt;/code&gt;. It also refuses to retry a &lt;code&gt;503&lt;/code&gt; whose body parses cleanly into some other error code, which is the case a status-code-only rule gets wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;withBaseUri()&lt;/code&gt; is not string concatenation
&lt;/h3&gt;

&lt;p&gt;It looks like a convenience until you send &lt;code&gt;../v2/users&lt;/code&gt; and find out which one your client implements. This one does dot-segment removal, and only applies the base when the request URI is actually relative. An absolute URI passes through untouched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything else is a header you'd otherwise hand-roll
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;withBasicAuth()&lt;/code&gt; is RFC 7617's &lt;code&gt;base64(user:pass)&lt;/code&gt;. &lt;code&gt;withBearerAuth()&lt;/code&gt; is RFC 6750's &lt;code&gt;Bearer &amp;lt;token&amp;gt;&lt;/code&gt;. &lt;code&gt;Part::file()&lt;/code&gt; builds an RFC 7578 part with its RFC 2183 &lt;code&gt;Content-Disposition&lt;/code&gt;. &lt;code&gt;withMinTlsVersion(Tls::V1_2)&lt;/code&gt; is an enum each adapter maps to &lt;code&gt;CURLOPT_SSLVERSION&lt;/code&gt; or Swoole's &lt;code&gt;ssl_protocols&lt;/code&gt;. &lt;code&gt;withTracePropagation()&lt;/code&gt; forwards the active &lt;code&gt;utopia-php/span&lt;/code&gt; trace as a &lt;code&gt;traceparent&lt;/code&gt;, and refuses to overwrite one that's already on the request.&lt;/p&gt;

&lt;p&gt;None of these are hard. They're just wrong in slightly different ways in every library that rolls its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four rules underneath
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reuse over recreate
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;withConnectionReuse()&lt;/code&gt; keeps one connection alive per client and reuses it for every request to the same origin. &lt;code&gt;curl_reset()&lt;/code&gt; clears per-request options while preserving the handle's connection cache; the Swoole adapter keeps a kept-alive coroutine client keyed by origin. It's opt-in, on the theory that a client built for one call shouldn't sit on a socket. Any long-lived service wants it on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm85x0nkcg8f08xeckzqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm85x0nkcg8f08xeckzqe.png" alt="Six calls to the same origin, with and without connection reuse" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Pay, that one change was the entire fix. Four hundred requests against a local echo server, before and after:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;fds&lt;/th&gt;
&lt;th&gt;RSS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;client per call&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+800&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+21 MB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reused connection&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+48 kB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you need concurrency rather than sequence, &lt;code&gt;Client\Pool&lt;/code&gt; borrows a client from a &lt;code&gt;utopia-php/pools&lt;/code&gt; pool per request and reclaims it afterwards, so N coroutines share a bounded set of connections instead of opening N of their own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every &lt;code&gt;with*()&lt;/code&gt; returns a clone
&lt;/h3&gt;

&lt;p&gt;In Swoole that's not a style preference. A shared client you can mutate is a cross-request bleed waiting to happen, and cloning means there's no way to reconfigure someone else's client from inside a request handler.&lt;/p&gt;

&lt;p&gt;A default is also only ever a default. &lt;code&gt;withHeaders()&lt;/code&gt; fills in a header the request doesn't already carry and nothing more, so a per-request &lt;code&gt;Content-Type&lt;/code&gt; beats the client-wide one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policy lives in decorators
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Retry&lt;/code&gt; implements the same &lt;code&gt;Adapter&lt;/code&gt; interface it wraps, forwards every configuration helper inward, and overrides only &lt;code&gt;sendRequest()&lt;/code&gt; and &lt;code&gt;stream()&lt;/code&gt;. So retries, pooling and whatever you add stack in any order, and none of them turn into constructor flags on the transport.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;stream()&lt;/code&gt; override is my favorite detail in the library. It counts bytes handed to the sink, and once a single byte has been delivered it stops retrying, because replaying would duplicate data the caller already processed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bounded memory by default
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;stream()&lt;/code&gt; hands each chunk to a sink as it arrives, so SSE and LLM token streams cost the same memory as a ping. Uploads go the same way: cURL pulls the body through a read callback, &lt;code&gt;Part::file()&lt;/code&gt; reads lazily, and Swoole sends files with zero-copy &lt;code&gt;sendfile()&lt;/code&gt;. A seekable body gets rewound before each attempt, which is what makes a streamed upload safe to retry at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it ended up
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;Now&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utopia-php/pay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;new Fetch\Client&lt;/code&gt; per call&lt;/td&gt;
&lt;td&gt;one injected client, &lt;code&gt;Adapter::call()&lt;/code&gt; deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utopia-php/messaging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw &lt;code&gt;curl_*&lt;/code&gt; and &lt;code&gt;curl_multi&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;PSR-18, plus a Swoole pool for batched FCM/APNs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utopia-php/storage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ad-hoc HTTP in the S3 devices&lt;/td&gt;
&lt;td&gt;default client with a stall watchdog and &lt;code&gt;S3\RetryStrategy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utopia-php/fastly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;built on it from day one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;appwrite/appwrite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;per-service HTTP wiring&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;jobs&lt;/code&gt; and &lt;code&gt;screenshots&lt;/code&gt; clients in the DI container&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Pay migration was net &lt;strong&gt;-55 lines&lt;/strong&gt;. &lt;code&gt;Adapter::call()&lt;/code&gt;, &lt;code&gt;handleError()&lt;/code&gt;, and nine &lt;code&gt;METHOD_*&lt;/code&gt; constants went with it — seven of the nine had no caller. A PSR-18 client and a PSR-17 factory already mean "build a request, send it"; the indirection was only ever a second vocabulary for HTTP.&lt;/p&gt;

&lt;p&gt;Messaging is the one worth reading if you're doing this yourself. It kept every adapter's public API identical while swapping &lt;code&gt;curl_multi&lt;/code&gt; for Swoole coroutines over a bounded pool, and added a &lt;code&gt;Closure(): ClientInterface&lt;/code&gt; factory so a caller can inject retries, a proxy, or a stub. The one constraint it documents loudly: your factory must produce a client that can negotiate HTTP/2, because APNs rejects HTTP/1.1 outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we grep for now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Climbing RSS with a flat PHP heap. Count fds, not &lt;code&gt;memory_get_usage()&lt;/code&gt;. &lt;code&gt;ls /proc/&amp;lt;pid&amp;gt;/fd | wc -l&lt;/code&gt; in a loop finds this in five minutes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;new Client()&lt;/code&gt; or &lt;code&gt;curl_init&lt;/code&gt; inside a request path. In a long-lived process a client is a resource with a lifetime, not a local variable.&lt;/li&gt;
&lt;li&gt;Non-static closures handed to &lt;code&gt;curl_setopt&lt;/code&gt;. If it doesn't touch &lt;code&gt;$this&lt;/code&gt;, make it &lt;code&gt;static&lt;/code&gt;. Costs nothing, and it's the whole bug.&lt;/li&gt;
&lt;li&gt;Any utopia library still carrying its own private HTTP stack. That's the rest of the work list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/utopia-php/client" rel="noopener noreferrer"&gt;utopia-php/client&lt;/a&gt;, though development happens in the &lt;a href="https://github.com/utopia-php/monorepo/tree/main/packages/client" rel="noopener noreferrer"&gt;monorepo&lt;/a&gt;. &lt;a href="https://twitter.com/lukebsilver" rel="noopener noreferrer"&gt;@lukebsilver&lt;/a&gt; wrote it and did the Pay migration. I did messaging, which is how I ended up with opinions about APNs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1brpy24ebo7frleo1r8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1brpy24ebo7frleo1r8.png" alt="Internal thread. Luke: the root cause is that Utopia\Pay\Adapter::call() does (new Client())-&gt;fetch(...) per Stripe API call, with a paste showing rss=63212kB fds=7 at start and rss=64696kB fds=47 at i=40, exactly +1 fd per fetch, RSS monotonic, PHP heap flat at 6.0MB throughout. Solution is to migrate to utopia-php/client. Reply: the astronaut meme, wait, solution is migrate to utopia-php/client? always has been." width="799" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Internal consensus, after the third identical incident. The paste is the diagnostic: fds climb one per call, RSS follows, the PHP heap never moves.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it means here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;file descriptor (fd)&lt;/td&gt;
&lt;td&gt;The integer the kernel gives a process to refer to something it has open. Every live socket holds at least one, and each process has a cap (&lt;code&gt;ulimit -n&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;Resident set size: the physical memory a process actually occupies, native allocations included. This is what the kernel measures when it decides to kill you.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OOMKilled&lt;/td&gt;
&lt;td&gt;The kernel terminating a process for exceeding its memory limit. It happens outside PHP, so there's no fatal error and nothing in the logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP heap&lt;/td&gt;
&lt;td&gt;The pool the Zend allocator manages, which is what &lt;code&gt;memory_get_usage()&lt;/code&gt; reports and &lt;code&gt;memory_limit&lt;/code&gt; caps. cURL and OpenSSL allocate outside it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reference cycle&lt;/td&gt;
&lt;td&gt;Objects holding each other so no refcount ever reaches zero. Only PHP's cycle collector can free them, and it runs on its own schedule.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;idempotent method&lt;/td&gt;
&lt;td&gt;A request that can be sent more than once without changing the outcome. &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt;, &lt;code&gt;TRACE&lt;/code&gt; qualify; &lt;code&gt;POST&lt;/code&gt; doesn't.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;full jitter&lt;/td&gt;
&lt;td&gt;Backoff where the wait is drawn uniformly from &lt;code&gt;[0, ceiling)&lt;/code&gt; instead of being the ceiling, so a fleet retrying together spreads out.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;coroutine&lt;/td&gt;
&lt;td&gt;Swoole's userland concurrency. One worker process interleaves many in-flight requests, which is why connection lifetime matters so much.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sink&lt;/td&gt;
&lt;td&gt;The callback &lt;code&gt;stream()&lt;/code&gt; hands each response chunk to as it arrives, instead of buffering the whole body.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APNs / FCM&lt;/td&gt;
&lt;td&gt;Apple and Google's push notification services. APNs is the one that rejects HTTP/1.1.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The specs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;HTTP semantics in &lt;a href="https://www.rfc-editor.org/rfc/rfc9110" rel="noopener noreferrer"&gt;RFC 9110&lt;/a&gt;, message framing in &lt;a href="https://www.rfc-editor.org/rfc/rfc9112" rel="noopener noreferrer"&gt;RFC 9112&lt;/a&gt;, HTTP/2 in &lt;a href="https://www.rfc-editor.org/rfc/rfc9113" rel="noopener noreferrer"&gt;RFC 9113&lt;/a&gt;, and &lt;code&gt;429&lt;/code&gt; in &lt;a href="https://www.rfc-editor.org/rfc/rfc6585" rel="noopener noreferrer"&gt;RFC 6585&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PHP-FIG: &lt;a href="https://www.php-fig.org/psr/psr-18/" rel="noopener noreferrer"&gt;PSR-18&lt;/a&gt; for clients, &lt;a href="https://www.php-fig.org/psr/psr-7/" rel="noopener noreferrer"&gt;PSR-7&lt;/a&gt; for messages, &lt;a href="https://www.php-fig.org/psr/psr-17/" rel="noopener noreferrer"&gt;PSR-17&lt;/a&gt; for factories&lt;/li&gt;
&lt;li&gt;URIs in &lt;a href="https://www.rfc-editor.org/rfc/rfc3986" rel="noopener noreferrer"&gt;RFC 3986&lt;/a&gt;; credentials in &lt;a href="https://www.rfc-editor.org/rfc/rfc6750" rel="noopener noreferrer"&gt;RFC 6750&lt;/a&gt; and &lt;a href="https://www.rfc-editor.org/rfc/rfc7617" rel="noopener noreferrer"&gt;RFC 7617&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Multipart in &lt;a href="https://www.rfc-editor.org/rfc/rfc7578" rel="noopener noreferrer"&gt;RFC 7578&lt;/a&gt;, building on &lt;a href="https://www.rfc-editor.org/rfc/rfc2046" rel="noopener noreferrer"&gt;RFC 2046&lt;/a&gt; and &lt;a href="https://www.rfc-editor.org/rfc/rfc2183" rel="noopener noreferrer"&gt;RFC 2183&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Content codings: &lt;a href="https://www.rfc-editor.org/rfc/rfc1951" rel="noopener noreferrer"&gt;deflate&lt;/a&gt;, &lt;a href="https://www.rfc-editor.org/rfc/rfc1952" rel="noopener noreferrer"&gt;gzip&lt;/a&gt;, &lt;a href="https://www.rfc-editor.org/rfc/rfc7932" rel="noopener noreferrer"&gt;br&lt;/a&gt;, &lt;a href="https://www.rfc-editor.org/rfc/rfc8878" rel="noopener noreferrer"&gt;zstd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc8446" rel="noopener noreferrer"&gt;TLS 1.3&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C Trace Context&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/utopia-php/client" rel="noopener noreferrer"&gt;utopia-php/client&lt;/a&gt;, developed in &lt;a href="https://github.com/utopia-php/monorepo/tree/main/packages/client" rel="noopener noreferrer"&gt;the monorepo package&lt;/a&gt;, on top of &lt;a href="https://github.com/utopia-php/psr7" rel="noopener noreferrer"&gt;utopia-php/psr7&lt;/a&gt; and &lt;a href="https://github.com/utopia-php/pools" rel="noopener noreferrer"&gt;utopia-php/pools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/utopia-php/fetch/issues/22" rel="noopener noreferrer"&gt;fetch#22&lt;/a&gt; — the reference cycle, with the fd and RSS measurements&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/utopia-php/pay/pull/32" rel="noopener noreferrer"&gt;pay#32&lt;/a&gt; — the migration that fixed the OOMKills, and the before/after numbers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/utopia-php/messaging/pull/137" rel="noopener noreferrer"&gt;messaging#137&lt;/a&gt; — &lt;code&gt;curl_multi&lt;/code&gt; swapped for Swoole coroutines over a pool&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS, &lt;a href="https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/" rel="noopener noreferrer"&gt;Exponential Backoff and Jitter&lt;/a&gt;, which is where the full-jitter formula comes from&lt;/li&gt;
&lt;li&gt;PHP manual, &lt;a href="https://www.php.net/manual/en/features.gc.collecting-cycles.php" rel="noopener noreferrer"&gt;Collecting Cycles&lt;/a&gt;, on when the collector actually runs&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://chirag.appwrite.network/blog/how-utopia-php-client-keeps-fixing-our-memory-leaks" rel="noopener noreferrer"&gt;chirag.appwrite.network&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>debugging</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I built the Appwrite MCP server (and decided to hide most of its capabilities)</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Tue, 04 Aug 2026 03:37:36 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/how-i-built-the-appwrite-mcp-server-and-decided-to-hide-most-of-its-capabilities-4jm8</link>
      <guid>https://dev.to/chiragagg5k/how-i-built-the-appwrite-mcp-server-and-decided-to-hide-most-of-its-capabilities-4jm8</guid>
      <description>&lt;p&gt;When Anthropic introduced the Model Context Protocol on November 25, 2024, it got everyone's eyes on it, including Christy, who was Appwrite's Engineering Lead back then. I had just started my role as an "Engineering Intern" and had no idea what a whole new protocol meant, or why it was such a big deal.&lt;/p&gt;

&lt;p&gt;Looking at the surface, I wasn't entirely wrong. MCP is JSON-RPC with a schema and a handshake stapled on. What took us sixteen months was everything stapled around it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03ukr9g7u27dg1gmcfcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03ukr9g7u27dg1gmcfcy.png" alt="Timeline: Nov 2024 MCP launches, Feb 2025 stdio ships, Mar 2025 Streamable HTTP, Apr 2026 981 tools become 4, Jun 2026 hosted merges, Jul 2026 shipped" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Streamable HTTP did not exist when MCP launched. It replaced HTTP+SSE in the 2025-03-26 revision.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The stdio years
&lt;/h2&gt;

&lt;p&gt;Christy had a working stdio server in the repo by February 26, 2025. We already had API keys, so the wiring was simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add appwrite &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;APPWRITE_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR_PROJECT_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;APPWRITE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR_API_KEY&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;APPWRITE_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://cloud.appwrite.io/v1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; uvx mcp-server-appwrite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An API key is scoped to exactly one project by design, so the ceiling was baked into the credential. Switching projects meant editing your editor config. Creating a project was impossible. So was anything at the organization level.&lt;/p&gt;

&lt;p&gt;The credential is the whole difference between the two transports, and everything hard about the hosted version follows from swapping it for a token that belongs to the user instead of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization ate the schedule
&lt;/h2&gt;

&lt;p&gt;By the spec, authorization is genuinely optional:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Authorization is OPTIONAL for MCP implementations. [...] Implementations using an HTTP-based transport SHOULD conform to this specification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a service where one tool call can drop a database, we weren't comfortable treating it as optional. If you use Auth0 or WorkOS, this is a config screen. Appwrite keeps everything in-house, so Matej built the authorization server itself, and I built the resource server plus whatever Cloud was still missing before real clients would work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdhn3rqujm9am5nolkzx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdhn3rqujm9am5nolkzx6.png" alt="Sequence diagram of the OAuth 2.1 handshake between the MCP client, mcp.appwrite.io and Appwrite Cloud" width="799" height="851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Steps 2 through 6 are the part that makes "just paste this URL" work. Nothing is pre-provisioned.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three RFCs carry that flow. Protected Resource Metadata (RFC 9728) is the only real MUST in the whole authorization spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.appwrite.io/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://cloud.appwrite.io/v1/oauth2/console"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scopes_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bearer_methods_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resource Indicators (RFC 8707) put our canonical URI into the token's &lt;code&gt;aud&lt;/code&gt;, so a token minted for another service can't be replayed against us. Dynamic Client Registration (RFC 7591) is what lets a client self-register. Add PKCE with &lt;code&gt;S256&lt;/code&gt;, RFC 8414 discovery, and you have the shape of it.&lt;/p&gt;

&lt;p&gt;The RFCs are documented. What isn't documented is that every client reads them differently, and you find out in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Raycast&lt;/strong&gt; was on the &lt;code&gt;2025-03-26&lt;/code&gt; authorization spec, which looks for &lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt; instead of the protected-resource route. I only found it by putting a logging proxy in front of the server and watching what it actually asked for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; re-authenticated every single run. It listens on an ephemeral loopback port, so the redirect URI never matched. OAuth's native-app BCP (RFC 8252 §7.3) says you must allow any port on &lt;code&gt;127.0.0.1&lt;/code&gt;. We weren't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Our own scope catalog&lt;/strong&gt; broke the flow. All ~118 granular scopes produced a &lt;code&gt;scope&lt;/code&gt; parameter of ~2,680 characters against a validator capped at 2,048. Nobody ever reached a consent screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One warning if you're about to build this: RFC 7591 went from SHOULD to MAY in &lt;code&gt;2025-11-25&lt;/code&gt; and is deprecated as of &lt;code&gt;2026-07-28&lt;/code&gt;, replaced by Client ID Metadata Documents. We shipped that too. This part of the spec is still moving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sessions, and then no sessions
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;2025-06-18&lt;/code&gt; spec let a server hand out an &lt;code&gt;Mcp-Session-Id&lt;/code&gt; alongside the &lt;code&gt;InitializeResult&lt;/code&gt;, with &lt;code&gt;DELETE&lt;/code&gt; to terminate and &lt;code&gt;Last-Event-ID&lt;/code&gt; for resumability. We skipped all of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;StreamableHTTPSessionManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stateless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request carries a bearer token. Verify it, build a client from it, serve the call. Nothing to store, nothing to lose on restart, nothing to make sticky across replicas.&lt;/p&gt;

&lt;p&gt;That turned out to be the right bet for a reason I can take no credit for. The &lt;code&gt;2026-07-28&lt;/code&gt; revision removed sessions from the protocol entirely. &lt;code&gt;Mcp-Session-Id&lt;/code&gt;, the &lt;code&gt;initialize&lt;/code&gt; handshake, the GET SSE stream: all gone. What we do carry is version negotiation, because you don't get to pick your clients' protocol version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Off-topic, but why not REST?
&lt;/h2&gt;

&lt;p&gt;Many intellectuals like myself must have wondered why MCP exists at all. Can't this be 100x simpler with, I don't know, REST?&lt;/p&gt;

&lt;p&gt;The model only knows its training data plus whatever you hand it at runtime. If it has never seen Appwrite, it will never guess this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://&amp;lt;REGION&amp;gt;.cloud.appwrite.io/v1/tablesdb
X-Appwrite-Project: &amp;lt;PROJECT_ID&amp;gt;
X-Appwrite-Key: &amp;lt;API_KEY&amp;gt;
Content-Type: application/json

{ "databaseId": "unique()", "name": "Production" }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint, the header names, the fact that &lt;code&gt;unique()&lt;/code&gt; is a magic value. With MCP the same operation shows up self-describing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tables_db_create"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a database in an Appwrite project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"databaseId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"databaseId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can be happy knowing AI needs a lot more handholding than you do (for now).&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool choice
&lt;/h2&gt;

&lt;p&gt;Every MCP server I looked at ships a small, curated set. Appwrite generates one tool per SDK method, which lands at 981 methods across 38 services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgc37pcad0p8vyuywgkpy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgc37pcad0p8vyuywgkpy.png" alt="Bar chart of tools exposed in tools/list per MCP server: GitHub 90, Supabase 29, Convex 12, Stripe 12, Sentry 9, Appwrite 4 over a 981-method catalog" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Counts as of August 2026. GitHub's 90 are grouped into 22 toolsets with 5 on by default.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's no version of "expose them all" that works, for two unrelated reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The clients won't take them.&lt;/strong&gt; In early 2025 Cursor documented that it "will only send the first 40 tools to the Agent" and truncated silently. Windsurf refused outright above 50.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffq3vrscwxuorh8jm9ilx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffq3vrscwxuorh8jm9ilx.png" alt="Discord report from liviu74: Windsurf error " width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Discord, liviu74, Mar 14 2025. Windsurf refused it; Cursor accepted it and silently dropped tools 41 onward.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That was with per-service flags already in place, which is the part that stings. A community user opened &lt;a href="https://github.com/appwrite/mcp/issues/17" rel="noopener noreferrer"&gt;issue #17&lt;/a&gt;, "Please reduce the number of tools":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cursor has 40 MCP tools limit to use, but Appwrite solely has 195 tools, so it cannot be used with other tools nor even all of Appwrite tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I pointed out you could narrow it with &lt;code&gt;--databases&lt;/code&gt;. The reply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That's quite non-sense. Then do I have to edit MCP parameter settings for each time whenever I do another jobs...? And anyway, &lt;code&gt;--databases&lt;/code&gt; solely has 42 tools, which already exceeds Cursor's recommended limit (40).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Quality falls off well below the caps.&lt;/strong&gt; The numbers converge from unrelated directions. Anthropic puts degradation at "once you exceed 30-50 available tools". OpenAI says "fewer than 20 functions at the start of a turn". Block's Goose recommends 50 or fewer. And the fix measures well: Anthropic's &lt;a href="https://www.anthropic.com/engineering/advanced-tool-use" rel="noopener noreferrer"&gt;advanced tool use&lt;/a&gt; work takes Opus 4 from 49% to 74% on MCP tool-use evals with a search tool enabled, and Opus 4.5 from 79.5% to 88.1%, with 85% fewer tokens on definitions. &lt;a href="https://arxiv.org/abs/2505.03275" rel="noopener noreferrer"&gt;RAG-MCP&lt;/a&gt; more than triples selection accuracy (43.13% against 13.62%).&lt;/p&gt;

&lt;p&gt;The caveat, because it cuts against me: &lt;a href="https://arxiv.org/abs/2508.16260" rel="noopener noreferrer"&gt;MCPVerse&lt;/a&gt; found some agentic models handle big action spaces fine. Claude-4-Sonnet scored 62.3 with an oracle tool set and 62.4 with ~220 tools. Big catalogs aren't fatal. They're a tax you're paying for nothing when the agent needs three tools out of 981.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four tools over 981
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;appwrite_get_context&lt;/code&gt; answers where you are and which projects you can see&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appwrite_search_tools&lt;/code&gt; searches the hidden catalog in natural language&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appwrite_call_tool&lt;/code&gt; calls one of them by name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appwrite_search_docs&lt;/code&gt; searches the Appwrite docs semantically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search narrows at request time, which is why the per-service flags could be deleted entirely. Mutations require &lt;code&gt;confirm_write: true&lt;/code&gt;, and results too large for the conversation become MCP resources instead.&lt;/p&gt;

&lt;p&gt;The scoring behind &lt;code&gt;appwrite_search_tools&lt;/code&gt; is deliberately dumb: token and substring matching against the tool name, description, service and resource, a bonus when the query's inferred verb matches the tool's, a penalty when it doesn't. No embeddings, no index to rebuild, no inference call in the hot path.&lt;/p&gt;

&lt;p&gt;Here's what a client sees:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1vhnchw9xkmp9tj4h47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1vhnchw9xkmp9tj4h47.png" alt="Cursor MCP settings showing the appwrite server connected with 4 tools and 1 resource enabled" width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;981 methods behind 4 tools and 1 resource. The "Logout" link is the OAuth session.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What made me stop second-guessing the design is that we weren't alone. Stripe put its whole API behind &lt;code&gt;stripe_api_search&lt;/code&gt;. Sentry exposes 9 of 46 through &lt;code&gt;search_sentry_tools&lt;/code&gt;. GitHub removed its dynamic toolset tools and looks to be building a search replacement. Three companies with no reason to coordinate landed on search-then-execute in the same window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it landed
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http appwrite https://mcp.appwrite.io/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API key, no project ID, no config editing to switch projects. Projects and organizations are parameters on the call now instead of properties of the credential.&lt;/p&gt;

&lt;p&gt;stdio didn't go away. I removed it in the hosted refactor and put it back two days later, because self-hosted users need it. It runs on a project API key and gets 647 of the 981 methods, since a project key can't reach console-level operations anyway.&lt;/p&gt;

&lt;p&gt;Behind that URL there's also OpenTelemetry, Sentry, Grafana dashboards, and region routing so a project in another Cloud region doesn't return &lt;code&gt;general_access_forbidden&lt;/code&gt;. You end up operating a service, not publishing a package. That's the part I underestimated most.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building one
&lt;/h2&gt;

&lt;p&gt;The transport is not where the time goes. The authorization spec and everything it pulls in is where the months disappear.&lt;/p&gt;

&lt;p&gt;Test against real clients early and expect them to disagree. A logging proxy in front of your server was worth more to me than another pass through the docs.&lt;/p&gt;

&lt;p&gt;Assume the spec moves under you. Between starting and shipping, sessions were removed, RFC 7591 was deprecated, and a stateless revision landed. Anthropic donated MCP to the Agentic AI Foundation in December 2025, so it isn't even one vendor's project anymore.&lt;/p&gt;

&lt;p&gt;And don't hand your API surface over as your tool surface. The architecture this server has today came out of a bug report from a user who was annoyed with us, which I think is the correct way for this to have gone.&lt;/p&gt;

&lt;p&gt;The server is open source at &lt;a href="https://github.com/appwrite/mcp" rel="noopener noreferrer"&gt;github.com/appwrite/mcp&lt;/a&gt;, and the hosted one is at &lt;code&gt;https://mcp.appwrite.io/&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>oauth</category>
      <category>appwrite</category>
    </item>
    <item>
      <title>Managing multiple docker hub accounts using docker-use</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Mon, 25 May 2026 05:08:01 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/managing-multiple-docker-hub-accounts-using-docker-use-b6p</link>
      <guid>https://dev.to/chiragagg5k/managing-multiple-docker-hub-accounts-using-docker-use-b6p</guid>
      <description>&lt;p&gt;Most of the time I'm signed into my work Docker Hub account, and that's fine. Almost everything I build ends up there. Then one weekend I was playing around with &lt;a href="https://github.com/open-runtimes/orchestrator" rel="noopener noreferrer"&gt;openruntimes/orchestrator&lt;/a&gt;, an orchestration service for running containerized workloads with callbacks. I had a PR open with some small tweaks, and I wanted to publish release-candidate images so I could pull them down and actually test end-to-end.&lt;/p&gt;

&lt;p&gt;I didn't want those RC images sitting in my work namespace. So for the first time, I actually needed to switch Docker Hub accounts on my machine, and there isn't a good way to do it. The official answer is &lt;code&gt;docker logout&lt;/code&gt;, then &lt;code&gt;docker login&lt;/code&gt; again. Docker does respect a &lt;code&gt;DOCKER_CONFIG&lt;/code&gt; environment variable that points at a config directory, but you still have to set it yourself every time.&lt;/p&gt;

&lt;p&gt;A few years ago I'd have spent the afternoon hunting for someone else's tool. In the age of AI, you can just build the one you wished existed. So I did.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://github.com/chiragagg5k/docker-use" rel="noopener noreferrer"&gt;&lt;code&gt;docker-use&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;The fix is small. Docker already respects the &lt;code&gt;DOCKER_CONFIG&lt;/code&gt; environment variable, and whatever directory it points at is treated as the active config. So the whole mechanism is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give every account its own config directory.&lt;/li&gt;
&lt;li&gt;Let the shell flip &lt;code&gt;DOCKER_CONFIG&lt;/code&gt; between them by name.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No patches to Docker, no daemon involvement, no new credential store. A directory per account, plus a shell function to switch between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# one-time per account&lt;/span&gt;
docker-use add appwrite &lt;span class="nt"&gt;-u&lt;/span&gt; my-appwrite-username
docker-use add personal &lt;span class="nt"&gt;-u&lt;/span&gt; chiragagg5k

&lt;span class="c"&gt;# day-to-day&lt;/span&gt;
docker-use appwrite

docker-use personal
docker push chiragagg5k/job-sidecar:temp-appwrite-pr-4f081ed-20260521-r2

docker-use &lt;span class="nb"&gt;whoami&lt;/span&gt;   &lt;span class="c"&gt;# which one am I on right now?&lt;/span&gt;
docker-use list     &lt;span class="c"&gt;# all accounts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;add&lt;/code&gt; shells out to a normal &lt;code&gt;docker login&lt;/code&gt;, so 2FA and access tokens behave the way you already expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;There are three pieces.&lt;/p&gt;

&lt;p&gt;Each account lives in its own config directory, at &lt;code&gt;~/.docker-accounts/&amp;lt;name&amp;gt;/config.json&lt;/code&gt;. Names are validated against &lt;code&gt;^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$&lt;/code&gt; so they can't escape the directory or surprise the shell.&lt;/p&gt;

&lt;p&gt;The trickier piece is a shell wrapper that mutates the parent shell. A child process can't change its parent's environment variables, so the binary on its own can't make &lt;code&gt;DOCKER_CONFIG&lt;/code&gt; stick in your shell. The workaround is a shell function, generated by &lt;code&gt;docker-use init zsh&lt;/code&gt;, that you source in your rc file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker-use init zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wrapper calls the binary, reads the printed config path, and assigns it to &lt;code&gt;DOCKER_CONFIG&lt;/code&gt; in the current shell. It's a plain assignment, not an eval on the path itself, so a weird config name can't smuggle anything into your shell. &lt;code&gt;zsh&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, and &lt;code&gt;fish&lt;/code&gt; all work.&lt;/p&gt;

&lt;p&gt;The last piece is keeping Docker's credential helpers in place. When &lt;code&gt;docker login&lt;/code&gt; finishes, the resulting &lt;code&gt;config.json&lt;/code&gt; often contains &lt;code&gt;"credsStore": "osxkeychain"&lt;/code&gt; (or &lt;code&gt;secretservice&lt;/code&gt; on Linux). If you wipe that field, Docker Desktop's keychain integration breaks and the next push prompts you for a password. So during &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;docker-use&lt;/code&gt; reads the config Docker just wrote and preserves the credential helper settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I'd flag if you read the source
&lt;/h2&gt;

&lt;p&gt;A few decisions that weren't obvious going in.&lt;/p&gt;

&lt;p&gt;The shell wrapper assigns the config path directly instead of eval-ing whatever the binary prints. Eval-ing user-controllable output tends to age badly.&lt;/p&gt;

&lt;p&gt;Account names get validated before any filesystem operation runs. &lt;code&gt;docker-use add ../etc -u …&lt;/code&gt; is not a debugging session I want to have.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;remove&lt;/code&gt; asks for confirmation before deleting. It's a destructive action on credentials, so the extra keystroke is worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap chiragagg5k/tools
brew &lt;span class="nb"&gt;install &lt;/span&gt;docker-use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then drop this into your &lt;code&gt;~/.zshrc&lt;/code&gt; (or &lt;code&gt;~/.bashrc&lt;/code&gt;, or &lt;code&gt;~/.config/fish/config.fish&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker-use init zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Binaries for other platforms are on the &lt;a href="https://github.com/chiragagg5k/docker-use/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;If you end up using it, I'd love to hear what works and what doesn't. Open an issue or reach out directly.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How we solved logging at Appwrite</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Sun, 19 Apr 2026 11:56:39 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/how-we-solved-logging-at-appwrite-5612</link>
      <guid>https://dev.to/chiragagg5k/how-we-solved-logging-at-appwrite-5612</guid>
      <description>&lt;p&gt;A few weeks back I came across a post by &lt;a href="https://twitter.com/boristane" rel="noopener noreferrer"&gt;@boristane&lt;/a&gt; sharing a website he made, &lt;a href="https://loggingsucks.com" rel="noopener noreferrer"&gt;loggingsucks.com&lt;/a&gt;. It caught my eye because it had been shared by my favorite tech YouTuber, &lt;a href="https://twitter.com/theo" rel="noopener noreferrer"&gt;@theo&lt;/a&gt;. Like most people, I was really inspired by the article and shared it with my team. &lt;a href="https://twitter.com/lukebsilver" rel="noopener noreferrer"&gt;@lukebsilver&lt;/a&gt;, Appwrite's Engineering Lead, was also inspired by it and decided to work on a new PHP library, &lt;code&gt;utopia-php/span&lt;/code&gt;, to fix logging throughout the Appwrite codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we had
&lt;/h2&gt;

&lt;p&gt;Before this, Appwrite used a combination of two different libraries targeting logging in two different areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;utopia-php/console&lt;/code&gt; — a very simple wrapper library around stdout logging using functions like &lt;code&gt;Console::success()&lt;/code&gt;, &lt;code&gt;Console::error()&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utopia-php/logger&lt;/code&gt; — an adapter-based library to push error logs to monitoring systems like Sentry, AppSignal, Raygun, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined, these libraries served their purpose for a long time, but we often ran into problems when debugging production issues, the same ones the original article discusses in detail. I'd highly recommend going through &lt;a href="https://loggingsucks.com" rel="noopener noreferrer"&gt;that article&lt;/a&gt; first so I don't repeat it all here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our solution
&lt;/h2&gt;

&lt;p&gt;Funnily enough, the first tricky problem was deciding on a name. "Logger" was already taken, so we had to be creative. The word "Span" captured exactly what we were trying to solve: a fundamental unit of work with a named, timed operation alongside various attributes, errors, trace IDs, etc.&lt;/p&gt;

&lt;p&gt;The first step was to move away from simple log lines to &lt;strong&gt;structured logging&lt;/strong&gt;. Span enforces this by only exposing a single primary method, &lt;code&gt;add()&lt;/code&gt;, which accepts a key-value pair.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Console&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Deleting project &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (type=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, region=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'region'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'project.id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'project.type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'project.region'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'region'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This massively improved the queryability of our logs — one of the things we struggled with most when going through logs in production.&lt;/p&gt;

&lt;p&gt;We also wanted the library to be extremely simple to use. Earlier, with "logger", we had to hop through various dependency injection loops just to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;Message&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Document&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Log&lt;/span&gt; &lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ← has to be injected just to add a tag&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'projectId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nv"&gt;$log&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...actual work...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Span, it's much simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;Message&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Document&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'projectId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...actual work...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why not just make the logger methods static?
&lt;/h2&gt;

&lt;p&gt;Because Appwrite's codebase leverages coroutines (via Swoole) for concurrency between requests, similar to goroutines in Go. A naive static implementation would leak state across concurrent requests. Span solves this by allowing you to choose the storage type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;setStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Storage\Coroutine&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Exporters
&lt;/h2&gt;

&lt;p&gt;To combine both logger and console capabilities, Span exposes built-in Exporters, which, as the name suggests, export the logs to not just stdout but any supported adapter. The library currently supports three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stdout&lt;/strong&gt;: basic usage. Dumps the output as plain JSON:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker.deletes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"span.trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7a3f9c2b4e1d8f06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"span.duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project.id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"67f3a9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project.type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"projects"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project.region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fra"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pretty&lt;/strong&gt;: JSON dumps are very useful in production where you have OpenTelemetry or other monitoring set up, but locally you just want things to be readable:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker.deletes · 1.92s · 7a3f9c2b

  project.id      67f3a9
  project.type    projects
  project.region  fra

  ────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sentry&lt;/strong&gt;: since Sentry is primarily an error tracking system, Span also exposes a callable "sampler" that lets you filter which logs get exported to a particular exporter:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;addExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dsn&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'...'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// Sampler: drop noisy expected errors, keep everything else.&lt;/span&gt;
    &lt;span class="n"&gt;sampler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Span&lt;/span&gt; &lt;span class="nv"&gt;$span&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$span&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ExecutorException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$error&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isPublishable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;One massive improvement we saw was with error logs. Before, we had very verbose and noisy errors that were often hard to make sense of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Error] Timestamp: 2026-04-17T10:32:16+00:00
[Error] Type: Utopia\Database\Exception\Timeout
[Error] Message: Query took too long
[Error] File: /usr/src/code/src/Appwrite/Cloud/Platform/Workers/Deletes.php
[Error] Line: 214
Trace: #0 /usr/src/code/app/worker.php(828): ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"worker.deletes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"span.trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7a3f9c2b4e1d8f06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"span.duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project.id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"67f3a9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error.type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Utopia&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Database&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Exception&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error.message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Query took too long"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error.file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/src/code/src/Appwrite/Cloud/Platform/Workers/Deletes.php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error.line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error.trace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/src/code/app/worker.php"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;828&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"action"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're writing PHP in 2026, give &lt;a href="https://github.com/utopia-php/span" rel="noopener noreferrer"&gt;utopia-php/span&lt;/a&gt; a shot. And a massive shoutout to &lt;a href="https://twitter.com/lukebsilver" rel="noopener noreferrer"&gt;@lukebsilver&lt;/a&gt;, who actually built the library. I just learned from him and wanted to share what I picked up.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>php</category>
      <category>beginners</category>
    </item>
    <item>
      <title>JStack + Appwrite: A Match Made in Heaven for Modern Web Development</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Sun, 06 Jul 2025 15:27:22 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/jstack-appwrite-a-match-made-in-heaven-for-modern-web-development-1cln</link>
      <guid>https://dev.to/chiragagg5k/jstack-appwrite-a-match-made-in-heaven-for-modern-web-development-1cln</guid>
      <description>&lt;p&gt;If I had a penny for each time a youtuber has launched his own tech stack, I would have 2 pennies, which isn't much but its weird that it happened twice.&lt;/p&gt;

&lt;p&gt;I am talking about &lt;a href="https://create.t3.gg/" rel="noopener noreferrer"&gt;T3 Stack&lt;/a&gt; launched a while back by everyone's favourite Theo, but recently a new player has entered the market called &lt;a href="https://jstack.app/" rel="noopener noreferrer"&gt;JStack&lt;/a&gt;, by Josh who is the lead Devrel at Upstash. To be fair, its not even that recent, but as always &lt;del&gt;I am late to the party&lt;/del&gt; I try to give a framework time to mature and gather feedback from community before giving it a shot.&lt;/p&gt;

&lt;p&gt;So, did I prefer JStack over T3 stack? Did it have more compatibility with Appwrite, my favourite backend provider? Can it be hosted on Appwrite Sites? Let's find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let's start with initialising the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx create-jstack-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Options selected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;┌   jStack CLI 
│
◇  What will your project be called?
│  testing-jstack-appwrite
│
◇  Which database ORM would you like to use?
│  None
│
◇  Should we run &lt;span class="s1"&gt;'bun install'&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;you?
│  Yes

Using: bun

✔ testing-jstack-appwrite scaffolded successfully!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's just quickly run a dev server to see what we get out of the box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd cd &lt;/span&gt;testing-jstack-appwrite
bun dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9c9q9ae61yzugugap9b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9c9q9ae61yzugugap9b.png" alt="JStack landing page" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialising Project
&lt;/h2&gt;

&lt;p&gt;On skipping the ORM option, the stack still sets up a &lt;code&gt;/src/server&lt;/code&gt; folder with an example posts router. But it only mocks the DB using an array which does not persist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Mocked DB&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We skipped the ORM option because Appwrite provides built-in schema management through its SDK, eliminating the need for a separate ORM layer. To get started, let's head over to &lt;a href="https://cloud.appwrite.io/" rel="noopener noreferrer"&gt;https://cloud.appwrite.io&lt;/a&gt; to set up our project. &lt;/p&gt;

&lt;p&gt;If you are using Appwrite for the first time, I highly recommend you check out our &lt;a href="https://appwrite.io/docs/quick-starts/web" rel="noopener noreferrer"&gt;Start with Web docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a quick setup guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new project -&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6w0nn7x3pt9x8tott78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6w0nn7x3pt9x8tott78.png" alt="New project model (Appwrite)" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a new web platform and select Next.js -&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2cvx6dkh8zdrntpy4js.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2cvx6dkh8zdrntpy4js.png" alt="New platform screen (Appwrite)" width="800" height="333"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to project overview and grab your project's ID and region-specific endpoint:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjom1n3wjbri8qpko7z7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjom1n3wjbri8qpko7z7a.png" alt="Project ID and endpoint (Appwrite)" width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new &lt;code&gt;.env&lt;/code&gt; file in your project directory and paste in those values:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_APPWRITE_PROJECT_ID=686a271700323696d223
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://fra.cloud.appwrite.io/v1
NEXT_PUBLIC_APP_DOMAIN=localhost # we will change it later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Now, let's initialise the Appwrite SDK in the project using:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;bun&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This covers up on how to initialize Appwrite in normal Next.js project. Now we need to configure our Appwrite databases according to the project. For this demo, let's create a &lt;code&gt;Posts&lt;/code&gt; collection to shift the mock database JStack uses as an example to Appwrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining Schema
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Go to the Appwrite Console &amp;gt; Databases &amp;gt; Create Database. We will call it &lt;code&gt;main&lt;/code&gt; , and keep it' ID as main as well:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsmpij8n1yqok80pfm15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsmpij8n1yqok80pfm15.png" alt="Create database (Appwrite)" width="800" height="800"&gt;&lt;/a&gt;    &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Similarly, create a collection &lt;code&gt;posts&lt;/code&gt; and keep its ID as &lt;code&gt;posts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Each document in Appwrite already has an unique ID attached to it, so the only attribute we need is &lt;code&gt;name&lt;/code&gt; for now:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaheyx67pondngmv139t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaheyx67pondngmv139t.png" alt="Create string attribute (Appwrite)" width="800" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lastly, you will need to define who can access your collection. To learn more about it, check out the docs for &lt;a href="https://appwrite.io/docs/advanced/platform/permissions" rel="noopener noreferrer"&gt;Appwrite Permissions&lt;/a&gt;. For now, we will set it to any:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2m4jl1939fxqa93olacw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2m4jl1939fxqa93olacw.png" alt="Create permissions" width="800" height="191"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Syncing Types
&lt;/h2&gt;

&lt;p&gt;That's it, we are all set with configuration on the Appwrite Console. Now let's set it up in our project. I will also take the help of the Appwrite CLI to help me set up things faster. You can learn more about how to install it by following the installation docs.&lt;/p&gt;

&lt;p&gt;Once done, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;appwrite init project

? How would you like to start? Link directory to an existing project
? Choose your organization 67610b8ee51f147ca943
? Choose your Appwrite project. &lt;span class="o"&gt;[&lt;/span&gt;object Object]
✓ Success: Project successfully linked. Details are now stored &lt;span class="k"&gt;in &lt;/span&gt;appwrite.json file.
Would you like to pull all resources from project you just linked? Yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once done, let's utilize the last Types Generation feature to sync our defined types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;appwrite types src/types       

ℹ Info: Detected language: ts
ℹ Info: Directory: src/types does not exist, creating...
ℹ Info: Found 1 collections: posts
ℹ Info: Found 1 attributes across all collections
ℹ Info: Added types to src/types/appwrite.d.ts
✓ Success: Generated types &lt;span class="k"&gt;for &lt;/span&gt;all the listed collections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appwrite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * This file is auto-generated by the Appwrite CLI. 
 * You can regenerate it by running `appwrite types -l ts src/types`.
 */&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously pretty small for now, but a really helpful feature once the project expands and more collections are defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Code
&lt;/h2&gt;

&lt;p&gt;Final steps are to connect the Appwrite backend with our tech stack. For that let's create a simple &lt;code&gt;appwrite.ts&lt;/code&gt; file in &lt;code&gt;src/lib&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_APPWRITE_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_APPWRITE_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Databases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can modify the original &lt;code&gt;post-router.ts&lt;/code&gt; file to use the defined database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicProcedure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../jstack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/appwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/types/appwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DATABASE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POSTS_COLLECTION_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;router&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicProcedure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listDocuments&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;POSTS_COLLECTION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superjson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicProcedure&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createDocument&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;POSTS_COLLECTION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superjson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And done! Now your JStack application is using Appwrite as its backend provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start the dev server if not already by running:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;bun&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Your application should have started on: &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new post.&lt;/li&gt;
&lt;li&gt;You should be able to see it in recent posts:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foy9htyvp77qqct8grhzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foy9htyvp77qqct8grhzf.png" alt="Recent posts image" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Also, the data should be visible in your Appwrite Console:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foce30jvc16lb80e7v9qu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foce30jvc16lb80e7v9qu.png" alt="Appwrite console showing data" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying your application
&lt;/h2&gt;

&lt;p&gt;Until recently, to deploy your Next.js application, you had pretty low choices. But no more &lt;a href="https://appwrite.io/products/sites" rel="noopener noreferrer"&gt;Appwrite Sites&lt;/a&gt;. Now, you can have your backend and frontend hosted on Appwrite.&lt;/p&gt;

&lt;p&gt;Check out these docs on how to get started with Sites - &lt;a href="https://appwrite.io/docs/advanced/self-hosting/sites" rel="noopener noreferrer"&gt;https://appwrite.io/docs/advanced/self-hosting/sites&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's deploy your application using Sites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to Appwrite Console &amp;gt; Sites &amp;gt; Create Site.&lt;/li&gt;
&lt;li&gt;You can upload a tar file directly, or connect to a GitHub repository (I prefer the GitHub option for automatic deployments).&lt;/li&gt;
&lt;li&gt;Select the repository:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6ww9m9mb2z7w8hc5ggk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6ww9m9mb2z7w8hc5ggk.png" alt="Selecting repository screen" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can keep all the settings as the default ones, just make sure to upload the environment variables we defined earlier:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsthkangby5vgqfnntio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsthkangby5vgqfnntio.png" alt="Add environment variables screen" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Depending on the domain you are assigned / plan to use, you also need to update the &lt;code&gt;APP_DOMAIN&lt;/code&gt; variable we defined earlier. For me I will keep it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_APP_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;jstack&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Click on deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And done! Your application should be live 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfxrz2dfa7nhwu26n93o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfxrz2dfa7nhwu26n93o.png" alt="Deployment successful screenshot" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can checkout the demo application here - &lt;a href="https://jstack-appwrite-template.appwrite.network/" rel="noopener noreferrer"&gt;https://jstack-appwrite-template.appwrite.network/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Let's start by answering the question the blog began with: Do I prefer JStack over T3 Stack? I'm sorry, Theo, but I do.&lt;/p&gt;

&lt;p&gt;JStack solves the fundamental problems I have always had with Next.js:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono&lt;/a&gt; instead of Next.js's inbuilt convention for defining API routes.&lt;/li&gt;
&lt;li&gt;Using &lt;a href="https://tanstack.com/" rel="noopener noreferrer"&gt;TanStack&lt;/a&gt; query out of the box. Trust me, you should start now if you are not using it.&lt;/li&gt;
&lt;li&gt;Type safe and uses &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most noticeable difference between T3 Stack and JStack is how light they are, mainly due to the inclusion of the tRPC protocol in T3 Stack. Although JStack also uses tRPC, it seems like a much simpler implementation of it. Most projects do not require the complexity tRPC adds, and in my opinion it makes the code 10x more complicated to maintain.&lt;/p&gt;

&lt;p&gt;So, give JStack a shot if you are starting with a new project (or want to spend a weekend migrating your existing stack to it). Huge shoutout to &lt;a href="https://www.youtube.com/@joshtriedcoding" rel="noopener noreferrer"&gt;Josh&lt;/a&gt; for creating this wonderful stack. And lastly, give Appwrite a chance to be your next all in one cloud platform, both for your backend and frontend needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;JStack - &lt;a href="https://jstack.app/" rel="noopener noreferrer"&gt;https://jstack.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Appwrite Docs - &lt;a href="https://appwrite.io/docs" rel="noopener noreferrer"&gt;https://appwrite.io/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Github repository - &lt;a href="https://github.com/ChiragAgg5k/jstack-appwrite-template" rel="noopener noreferrer"&gt;https://github.com/ChiragAgg5k/jstack-appwrite-template&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Creating Knowledge bases using MindsDB</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Wed, 25 Jun 2025 03:35:39 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/creating-knowledge-bases-using-mindsdb-1a33</link>
      <guid>https://dev.to/chiragagg5k/creating-knowledge-bases-using-mindsdb-1a33</guid>
      <description>&lt;p&gt;Whenever we think we knowledge base, what is the first thing that comes to our mind? &lt;/p&gt;

&lt;p&gt;For me it had always been a &lt;strong&gt;library&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hundreds of thousands of meticulously organised lines of pure knowledge. In digital form, this knowledge is what we call as data. The internet is filled with data, and with the arrival of AI harnessing its power has become easier than ever before.&lt;/p&gt;

&lt;p&gt;But what if you wanted your own library, &lt;em&gt;your own knowledge base&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;In this blog, we will discuss how you can do just that, and use this library of yours to ask anything you would like using simple natural language. We will create a knowledge base of various research papers, ask it some complex questions and for fun, stress test the hell out of it.&lt;/p&gt;

&lt;p&gt;So let’s begin!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a knowledge base
&lt;/h2&gt;

&lt;p&gt;As an example lets create &lt;strong&gt;Scholar Map&lt;/strong&gt;, an AI-powered research assistant where you can store any research paper and query for them in simple natural language.&lt;/p&gt;

&lt;p&gt;To get started, first setup mindsdb self-hosted instance by following these docs - &lt;a href="https://docs.mindsdb.com/setup/self-hosted/docker" rel="noopener noreferrer"&gt;https://docs.mindsdb.com/setup/self-hosted/docker&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure the instance is available on &lt;a href="http://127.0.0.1:47334/" rel="noopener noreferrer"&gt;&lt;code&gt;http://127.0.0.1:47334&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone the repository:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ChiragAgg5k/scholar-map.git
&lt;span class="nb"&gt;cd &lt;/span&gt;scholar-map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Install &lt;code&gt;uv&lt;/code&gt; package manager if not already by following these &lt;a href="https://docs.astral.sh/uv/getting-started/installation/" rel="noopener noreferrer"&gt;installation docs&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sync up dependencies:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Start the application using:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│  Scholar Map                                                                                                                                                                  │
╰───────────────────────────────────────────────────────────────── Research Paper Knowledge Management System ──────────────────────────────────────────────────────────────────╯
Connecting to MindsDB...
Attempting to connect to MindsDB server...
Connection established successfully!
Setting up research papers knowledge base...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script will automatically run the configuration code and setup knowledge base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/4ZksrrIPWp4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
    </item>
    <item>
      <title>From student to full-time Platform Engineer at Appwrite</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Sat, 21 Jun 2025 14:52:08 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/from-student-to-full-time-platform-engineer-at-appwrite-49ci</link>
      <guid>https://dev.to/chiragagg5k/from-student-to-full-time-platform-engineer-at-appwrite-49ci</guid>
      <description>&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;For those who are not really interested in reading some random university student’s story, here is a short TLDR. I worked at Appwrite as an Engineering Intern for 6 months and just got offered a full-time position as a Platform Engineer, while still being in University for another year :D&lt;/p&gt;

&lt;p&gt;The story ahead covers how while drowning in a sea of desperation a light of hope changed everything for me, how my experience was working for a fully remote OSS company, and the challenges I faced while working in PHP, a language I had never touched before this :p&lt;/p&gt;




&lt;h2&gt;
  
  
  Pre-Interview
&lt;/h2&gt;

&lt;p&gt;Back in September-November 2024, I was kind of at the lowest point of my engineering journey yet. I had concluded my last internship around August when the project I was working on abruptly closed, and since then I was on a constant hunt for my next one.&lt;/p&gt;

&lt;p&gt;Silence. From all the mediums I tried  - LinkedIn, Upwork, AngelList, cold DMs, and what not.&lt;/p&gt;

&lt;p&gt;The thing is that once you start “working”, although it was an unpaid one, it’s pretty hard to go back to doing… nothing. “Nothing” is the wrong word I guess, I was still working on OSS, building projects, and trying to learn new things. But if someone asked what I was up to nowadays… i drew a blank.&lt;/p&gt;

&lt;p&gt;Then one day I was just hanging out on Discord and saw a notification from a bright red server I usually didn’t check much. Hey, it’s another job opening, so I applied like usual. Another month passed, silence again. But then I saw an email from Emma, our People’s person at Appwrite.&lt;/p&gt;

&lt;p&gt;“We wanna have a chat with you.”&lt;/p&gt;

&lt;p&gt;This was a very unusual email for me. Usually, companies like to give an assignment for the first round, but here it was a direct meeting scheduled… so I already knew something was different this time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interview(s)
&lt;/h2&gt;

&lt;p&gt;Of course, I am not allowed to share the entire interview experience without getting into a call with Emma after this is published. So, I just want to share one question that has stuck with me since then,&lt;/p&gt;

&lt;p&gt;“Looking at your resume, you don’t seem to have much if any experience with PHP. And Appwrite’s entire backend is in PHP. Will you be able to handle this?” - Torsten, Product Engineer at Appwrite.&lt;/p&gt;

&lt;p&gt;Stunned. Before going to the interview I did see the codebase was written in PHP. I had never touched it before. No contributions, No issues filed. So I assumed the position I was applying for might have nothing related to this codebase… maybe a new project altogether… right? Nope. &lt;/p&gt;

&lt;p&gt;My answer? It was somewhere along the lines of “I will figure it out, trust me, bro”.&lt;/p&gt;

&lt;p&gt;Very bad answer I know. But it was the best I could come up with. After the interview, I went on a grind. Reading the codebase, running it, and understanding its architecture (in that process I even wrote an article - &lt;a href="https://dev.to/chiragagg5k/architecture-patterns-for-beginners-mvc-mvp-and-mvvm-2pe7"&gt;Architecture Patterns for Beginners: MVC, MVP, and MVVM&lt;/a&gt;). Long story short I tried to learn as much about Appwrite’s codebase as was physically possible in that timeframe from the next interview scheduled.&lt;/p&gt;

&lt;p&gt;And Tada~ It worked. I got the offer!&lt;/p&gt;

&lt;h2&gt;
  
  
  Experience
&lt;/h2&gt;

&lt;p&gt;Working at Appwrite is unlike working in any other place. From the first day on, even though you are an Intern, you are never treated like one. I was introduced to the entire codebase in my first week and assigned to issues not from the pending log, but actual production issues that users were facing.&lt;/p&gt;

&lt;p&gt;You can message anyone. At any point.&lt;/p&gt;

&lt;p&gt;Work on any issue that you like.&lt;/p&gt;

&lt;p&gt;There is a padding issue you saw on the website? Knock yourself out and raise the PR.&lt;/p&gt;

&lt;p&gt;The beautiful thing about Appwrite being open source is that you could do this from the very start. It’s just now I could bug anyone on the team more freely if I was stuck on any issue.&lt;/p&gt;

&lt;p&gt;It’s hard to summarise how much work I did in the past 6 months, but here are some notable ones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;200+ PRs merged in the Appwrite organization’s repositories, including main, website, SDK, MCP, etc. repositories.&lt;/li&gt;
&lt;li&gt;Uptime monitoring for the &lt;a href="https://appwrite.io/docs/tooling/assistant" rel="noopener noreferrer"&gt;Appwrite Assistant&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Adding a monthly limit to the number of free Phone OTPs allowed.&lt;/li&gt;
&lt;li&gt;Fixing Amazon and Slack OAuth adapters. Add Figma OAuth adapter.&lt;/li&gt;
&lt;li&gt;Making several changes to SDKs and making releases.&lt;/li&gt;
&lt;li&gt;Adding Types generation to Appwrite CLI.&lt;/li&gt;
&lt;li&gt;Worked on the DevKeys feature in the latest Appwrite release 1.7&lt;/li&gt;
&lt;li&gt;Added 2 blogs on the website - self-hosting using coolify and announcement for &lt;a href="https://appwrite.io/blog/author/chirag-aggarwal" rel="noopener noreferrer"&gt;DevKeys&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Taking leadership on the &lt;a href="https://github.com/appwrite/synapse" rel="noopener noreferrer"&gt;Synapse&lt;/a&gt; project, operating system gateway for remote serverless environments.&lt;/li&gt;
&lt;li&gt;Adding new phone OTP adapter - Inforu, which reduces pricing for phone OTPs for Israeli users by 11x.&lt;/li&gt;
&lt;li&gt;and… a lot more.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Honestly looking back at it, I never realized I worked on so much. It feels like I had just joined Appwrite yesterday. &lt;/p&gt;

&lt;h2&gt;
  
  
  Thank You
&lt;/h2&gt;

&lt;p&gt;At last, I wanna thank the entire Appwrite team for treating me like part of the team since day one, and for giving me this opportunity. Let’s build like a team of hundreds_&lt;/p&gt;




&lt;p&gt;&lt;em&gt;You can check out my profile to learn more about me - &lt;a href="https://www.chiragaggarwal.tech/" rel="noopener noreferrer"&gt;https://www.chiragaggarwal.tech/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hit me up on any socials if you have any doubts, I am always up for a chat :D&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
    </item>
    <item>
      <title>Automate anything - Making research analysis effortless</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Sat, 14 Jun 2025 05:13:48 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/automate-anything-making-research-analysis-effortless-250a</link>
      <guid>https://dev.to/chiragagg5k/automate-anything-making-research-analysis-effortless-250a</guid>
      <description>&lt;p&gt;Starting with a research project can be overwhelming.&lt;/p&gt;

&lt;p&gt;There are so many data points to consider, so many hours of reading to do, and within all that chaos, the hardest piece of the puzzle to find is a &lt;strong&gt;starting point&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I faced this first hand when last year I had to work on multiple papers to earn a single credit point for my engineering degree. Unfortunately why universities force this pointless system is not the spotlight of today’s discussion.&lt;/p&gt;

&lt;p&gt;Instead, we are going to discuss how you can cheat on this mundane activity using Runner H.&lt;/p&gt;

&lt;p&gt;The article will redefine how you interact with AI, helping you put together analysis, execution, summarisation and integration with a single prompt.&lt;/p&gt;

&lt;p&gt;So, let’s dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Researcher’s Dilemma
&lt;/h2&gt;

&lt;p&gt;Whenever you start with a research project, the first question that comes to mind is: &lt;strong&gt;"Has this been researched before?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answering this question is crucial for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Avoiding duplication of work&lt;/li&gt;
&lt;li&gt;Finding gaps in the existing study&lt;/li&gt;
&lt;li&gt;Not claiming something as noble when it has already been done before&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, doing this is a very manual process, and requires hours and hours of find papers, reading them and doing accurate analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet Runner H
&lt;/h2&gt;

&lt;p&gt;Ever tried using Perplexity or ChatGPT in research mode? &lt;/p&gt;

&lt;p&gt;Think of Runner H as that, but on &lt;strong&gt;steroids&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Runner H is an advanced AI-powered web automation tool that lets you perform multi-step online tasks using natural language instructions. It seamlessly integrates with services like Slack, Notion, Gmail, and Excel, enabling you to create complex workflows, no coding experience required.&lt;/p&gt;

&lt;p&gt;For our purpose, we can use Runner H to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Quickly identify similar research papers with a Google Scholar scan.&lt;/li&gt;
&lt;li&gt;Assess similarities between them and your topic using AI.&lt;/li&gt;
&lt;li&gt;Scan the internet to find gaps between the existing work.&lt;/li&gt;
&lt;li&gt;Export the findings in a structured format.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The prompt
&lt;/h2&gt;

&lt;p&gt;Below, I have crafted a prompt that you can copy and paste into Runner H, replace with the topic you need to research, and voila~, see the magic unfold:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Analyse if my research idea, {YOUR_RESEARCH_IDEA}, has been previously explored. Search Google Scholar for similar research papers. For each, extract the title, authors, publication date, abstract, journal/conference, and similarity percentage to my idea, and note any research gaps mentioned or left open. Create an Excel file summarising all findings.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Just replace &lt;code&gt;{YOUR_RESEARCH_IDEA}&lt;/code&gt; with the idea you want to research on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Let’s try researching on a topic:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“The impact of generative AI tools on student learning outcomes in higher education”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/kFZjj8X33F0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So Runner H after doing all the analysis and hard work for us was able to generate a structured output. Unfortunately, even though prompt specifically asks for Excel format, most of the times for me it generated a PDF format for me. Still Runner H is still in early phases and hopefully will be improved in future by the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what makes Runner H different?
&lt;/h2&gt;

&lt;p&gt;One of the most fascinating differentiations I found in Runner H compared to other agentic tools was its ability to search, summarise and synthesize as well. Instead of just dumping in raw results, it evaluates the relevance of each paper, highlights the overlaps and gaps in the study, and presents it in an actionable format.&lt;/p&gt;

&lt;p&gt;Using Runner H has completely transformed my approach to starting a research topic. It has bridged me to the starting point where I used to struggle the most. For students, academic professionals, and anyone doing any sort of research work, Runner H is a game changer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Happy researching! If you have questions or want to share your own experiences with Runner H, drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>runnerhchallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Vibe coding an Email Ticket Automater using Postmark</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Sat, 31 May 2025 02:44:00 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/vibe-coding-an-email-ticket-automater-using-postmark-l11</link>
      <guid>https://dev.to/chiragagg5k/vibe-coding-an-email-ticket-automater-using-postmark-l11</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Support tickets…&lt;/p&gt;

&lt;p&gt;Nobody really loves either writing or much worse reading them. And managing them is much much worse than managing my laundry, and that is a really high bar to pass.&lt;/p&gt;

&lt;p&gt;So, in a world where we all are writing and summarising emails for the sake of maintaining formality, automating support tickets seems like the logical next step.&lt;/p&gt;

&lt;p&gt;And sure there are companies that do this… not really going to name them, but my vision here was to just create something that takes the unorganized text and convert it to organized boxes, while still being pretty to look at… &lt;em&gt;cough&lt;/em&gt; Jira &lt;em&gt;cough.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Meet EmailTicket. Firstly hoping that marks are not being deducted for the lack of naming creativity, it's here to do exactly what it says. Create tickets based on emails. Sounds simple right? Well kind of… but with some headaches.&lt;/p&gt;

&lt;p&gt;Historically, when creating tickets you would have to ask the user a bunch of information like whether it is a billing issue, a bug, or feedback, when did the issue start, etc. &lt;/p&gt;

&lt;p&gt;After creation, you would have to manually keep track of information like how urgent is it. Has the ticket been closed or is it still in progress? etc. etc.&lt;/p&gt;

&lt;p&gt;You know… the boring stuff.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyg0bvs81e953d8r4bu37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyg0bvs81e953d8r4bu37.png" alt="Ticket Modal"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Image captured during the pre-AI times (just kidding it was created using ChatGPT)
  &lt;p&gt;&lt;/p&gt;




&lt;p&gt;But with AI, we no longer need to take care of that stuff. Just give it a random block of text and out comes structured beauty (It’s like a dream come true for data scientists honestly).&lt;/p&gt;

&lt;p&gt;This is pretty much what Email Ticket does for now. It’s highly inspired by platforms like HelpScout (an amazing tool btw do check it out) and in the future can extend a lot more if good guys like the ones from Postmark support it :D&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/yvApeUgQcVs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Checkout the live link here - &lt;a href="https://email-ticket-automator.vercel.app/" rel="noopener noreferrer"&gt;https://email-ticket-automator.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your emails are stuck on processing/waiting, It’s highly likely that I ran out of the 100 free emails. So… don’t sweat too much about it 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ChiragAgg5k" rel="noopener noreferrer"&gt;
        ChiragAgg5k
      &lt;/a&gt; / &lt;a href="https://github.com/ChiragAgg5k/email-ticket-automator" rel="noopener noreferrer"&gt;
        email-ticket-automator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI-powered helpdesk platform leveraging email-parsing by Postmark
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;📧 Email Ticket&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A modern, AI-powered helpdesk platform that transforms email support into an organized ticket management system. Built with React, TypeScript, Postmark, and Appwrite for seamless customer support experiences.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ChiragAgg5k/email-ticket-automator/blob/main/assets/banner-cropped.png?raw=true"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FChiragAgg5k%2Femail-ticket-automator%2Fraw%2Fmain%2Fassets%2Fbanner-cropped.png%3Fraw%3Dtrue" alt="Email Ticket Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🏹 Quick Start&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Prerequisites&lt;/h3&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 18+ and npm&lt;/li&gt;
&lt;li&gt;Postmark account for email parsing&lt;/li&gt;
&lt;li&gt;Appwrite account for backend services&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Installation&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clone the repository&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/ChiragAgg5k/email-ticket-automator.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; email-ticket-automator&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install dependencies&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;bun i &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or npm install&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure Appwrite&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an Appwrite project at &lt;a href="https://cloud.appwrite.io" rel="nofollow noopener noreferrer"&gt;cloud.appwrite.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Update the configuration in &lt;code&gt;src/lib/appwrite.ts&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight highlight-source-ts notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;client&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;setEndpoint&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"YOUR_APPWRITE_ENDPOINT"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-s1"&gt;client&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;setProject&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"YOUR_PROJECT_ID"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Start the development server&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;bun dev&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open your browser&lt;/strong&gt;
Navigate to &lt;code&gt;http://localhost:8080&lt;/code&gt; to see the application&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛠️ Tech Stack&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Frontend&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; - Modern React with hooks and functional components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; - Type-safe development experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite&lt;/strong&gt; - Fast build tool and development server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; - Utility-first CSS framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;shadcn/ui&lt;/strong&gt; - High-quality, accessible UI components&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Backend &amp;amp; Services&lt;/h3&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ChiragAgg5k/email-ticket-automator" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;So most of the application is vibe-coded using Lovable. Was it because I am lazy? Probably yes. But that’s not the exact reason. In my opinion tools like Lovable, V0, Firebase Studio (god there are so many now), etc. are perfectly fine to use to get an initial MVP of your idea up and running.&lt;/p&gt;

&lt;p&gt;And in a world where AI is dominating the entire development process, not utilizing these tools is just slowing you down.&lt;/p&gt;

&lt;p&gt;So let’s talk about the 10% of coding I actually did.&lt;/p&gt;

&lt;p&gt;The first was integration with a database. Or more specifically a Backend as a Service because who codes their own backend nowadays? So naturally I went with Appwrite. Lovable refused to do this integration, but thankfully Appwrite is extremely easy to set up. I used it to implement Authentication flow, as well as Database for ticket storing and retrieval.&lt;/p&gt;

&lt;p&gt;Next was the start of the application, Postmark. Postmark made the entire process of receiving an email, parsing it into JSON, and receiving it just via an API call so easy, I forgot dealing with emails was such an annoying task.&lt;/p&gt;

&lt;p&gt;Firstly, I needed to A) Either get inbound domain forwarding setup, or B) Get my account approved to be able to send emails to the default inbound email address with no domain restrictions. I tried both techniques and luckily got my account approved in 2 days!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flid9dtnuzzd6gktuziv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flid9dtnuzzd6gktuziv5.png" alt="Postmark Email"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not so luckily I was never able to set my inbound domain even tho I had my MX record set properly and could verify it using &lt;a href="https://www.whatsmydns.net/" rel="noopener noreferrer"&gt;https://www.whatsmydns.net/&lt;/a&gt; (You can check my comment here complaining about it - &lt;a href="https://dev.to/chiragagg5k/comment/2o6hi"&gt;https://dev.to/chiragagg5k/comment/2o6hi&lt;/a&gt;). So something to note down @Postmark team.&lt;/p&gt;

&lt;p&gt;To utilize the API seamlessly without ever needing to touch a single line of actual backend code, I came up with this flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Appwrite triggers a “create” document/ticket event.&lt;/li&gt;
&lt;li&gt;The event triggers a function that then calls the Postmark’s API for sending an inbound email.&lt;/li&gt;
&lt;li&gt;Postmark listens to the inbound email and triggers an email parsing flow.&lt;/li&gt;
&lt;li&gt;After parsing is done it returns the output to a webhook URL which is just an Appwrite function.&lt;/li&gt;
&lt;li&gt;The Appwrite function finally processes the parsed content and stores it back to the database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsmi3ogtpgd07jqm5k9g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsmi3ogtpgd07jqm5k9g.png" alt="Flow Diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally time for structured outputs. Almost all LLMs offer a way to generate structured outputs now in their APIs. Here are some docs I found for this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://docs.perplexity.ai/guides/structured-outputs" rel="noopener noreferrer"&gt;https://docs.perplexity.ai/guides/structured-outputs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses" rel="noopener noreferrer"&gt;https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency" rel="noopener noreferrer"&gt;https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;and on and on…&lt;/p&gt;

&lt;p&gt;(a small plug would be to check this repo I worked on which allows you to use any LLM with structured outputs and consistent syntax in PHP - &lt;a href="https://github.com/utopia-php/agents" rel="noopener noreferrer"&gt;https://github.com/utopia-php/agents&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;You can check the &lt;a href="https://github.com/ChiragAgg5k/email-ticket-automator/blob/main/functions/email-parsed-webhook/src/main.js" rel="noopener noreferrer"&gt;repository code&lt;/a&gt; itself to see which one I picked because I might have changed it by the time you are reading it :p&lt;/p&gt;

&lt;p&gt;That’s it then. Let me know in the comments how was your experience using EmailTicket Automater. You can reach out to me on any of my social media links if you have any queries or just wanna have a casual talk. A big shoutout to Postmark for arranging this contest and letting me have fun with their platform, as well as my company Appwrite for supporting the application’s backend :D&lt;/p&gt;

&lt;p&gt;Bye!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Focus on the product, not the tech stack</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Wed, 14 May 2025 16:48:53 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/focus-on-the-product-not-the-tech-stack-15d</link>
      <guid>https://dev.to/chiragagg5k/focus-on-the-product-not-the-tech-stack-15d</guid>
      <description>&lt;p&gt;Recently I got this DM,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdnfiao9st34jlhfpw28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdnfiao9st34jlhfpw28.png" alt="Twitter DM" width="788" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“Which tech stack should I learn?”&lt;/p&gt;

&lt;p&gt;“Should I use Next.js or Svelte?”&lt;/p&gt;

&lt;p&gt;“Is Python better for the backend than node?”&lt;/p&gt;

&lt;p&gt;I am sure we have asked or been asked questions like this several times in our tech journeys. As someone who is just starting out, I think it seems like a very important thing to ask. You don’t wanna waste your time learning a technology that might become worthless soon? Or isn’t fast enough? or isn’t the “industry standard”… right?&lt;/p&gt;

&lt;p&gt;Or should it matter?&lt;/p&gt;

&lt;h2&gt;
  
  
  Languages are just tools
&lt;/h2&gt;

&lt;p&gt;Back in 2003 when Facebook was originally founded, its backend was written in PHP. Was it the lack of options? You can say so. Back then they certainly didn’t have a plethora of frameworks or hell even languages to choose from. But what about now? Realistically PHP should be dead by now…&lt;/p&gt;

&lt;p&gt;Right?&lt;/p&gt;

&lt;p&gt;But if you look at the &lt;a href="https://kinsta.com/php-market-share/" rel="noopener noreferrer"&gt;statistics by Kinsta&lt;/a&gt;, around 79.2% of the internet still relies on PHP to some degree. Okay, but that number has to be declining for sure!&lt;/p&gt;

&lt;p&gt;But wait...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiytuwpkuplv3pxtq2ypn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiytuwpkuplv3pxtq2ypn.png" alt="PHP Trend Chart" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nope, it has been on a steady increase since 2011, peaking at 80.6% in 2015, and maintaining a steady line.&lt;/p&gt;

&lt;p&gt;But how?&lt;/p&gt;

&lt;p&gt;It’s because language is just a tool. It does not matter how many new bleeding edge languages enter the market increasing the performance metrics by some fractions of seconds, as long as a &lt;del&gt;tool&lt;/del&gt; language sufficiently fulfills its job, it will stay relevant. PHP has done a great job at that. It’s constantly evolving as well, something which is direly required to stay alive in the software world.&lt;/p&gt;

&lt;p&gt;So next time if someone asks you “Which language should I choose?”, ask them two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which language are you more comfortable with?&lt;/li&gt;
&lt;li&gt;Do the other languages provide a set of features more beneficial for your use case? Even if so are they worth the effort to learn? (arguments like uh it's 0.001sec faster should not be relevant)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  It’s OK to not upgrade
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmggrfh7nqkijd465dn8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmggrfh7nqkijd465dn8r.png" alt="Updating dependencies meme" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;&lt;br&gt;Image from: &lt;a href="https://foreverealize.me/posts/how-to-update-dependencies/" rel="noopener noreferrer"&gt;https://foreverealize.me/posts/how-to-update-dependencies/&lt;/a&gt;
  &lt;p&gt;&lt;/p&gt;

&lt;p&gt;I was once asked, “Why is your website still at Next version 13 when 15 has arrived?”&lt;/p&gt;

&lt;p&gt;To which I ask, why does it matter? Sure software is meant to be ever-evolving, but that doesn’t necessarily mean it's a good idea to always hop on the upgrade bandwagon without looking at the consequences.&lt;/p&gt;

&lt;p&gt;This in this case will be a complete re-write since Next 15 introduces a ton of breaking changes.&lt;/p&gt;

&lt;p&gt;And it's OK not to upgrade every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Judge a product by its usefulness, not complexity
&lt;/h2&gt;

&lt;p&gt;A lot of people especially those building projects just for improving their resume will try to make them extremely complex. It will be a wobbly mess of high-end sounding technologies like SSR, TRPC, GraphQL, Kubernetes, Redis, etc.&lt;/p&gt;

&lt;p&gt;Sure I do understand the thinking behind this, it does show your ability to work with complex technologies.&lt;/p&gt;

&lt;p&gt;But in the age of AI and the really fast-moving pace of technology, it's pretty trivial for someone to learn technologies on the go and incorporate them into their project. Real engineering will always be that “&lt;strong&gt;solves a problem, not create one&lt;/strong&gt;”. Solving problems always requires asking questions first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does your project really need TRPC or GraphQL? Why not just use REST like everyone does bruh?&lt;/li&gt;
&lt;li&gt;Do I really need Kubernetes for those 5 concurrent connections?&lt;/li&gt;
&lt;li&gt;Does my really interactive website need SSR where then each button click will be a server action and take ages to process any action?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ikoimzgzcyvgo9c1ot8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ikoimzgzcyvgo9c1ot8.png" alt="Tutorial hell meme" width="506" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And please, stop building clones. It does not matter: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many services you use in it&lt;/li&gt;
&lt;li&gt;how much knowledge you gained from creating it&lt;/li&gt;
&lt;li&gt;how complex the project was to make&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The YouTube clone you made by following a random Youtuber’s 8-hour-long guide is not going to help you in any way.&lt;/p&gt;

&lt;p&gt;Quoting &lt;a href="https://www.youtube.com/@t3dotgg" rel="noopener noreferrer"&gt;Theo&lt;/a&gt; (idk where he said tho):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To build a great product, you need to first be a customer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want project ideas, look around you. Try to solve problems you face every day. If you just try looking, you WILL find many.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;This blog is a summary of my (I admit very short-lived) experience of working in tech till now. I always tried not to treat tech as something I specialize in, but as a tool that helps me solve problems. &lt;/p&gt;

&lt;p&gt;I started off my journey in web with a simple problem, I just wanted a website of my own because it would be cool :D&lt;/p&gt;

&lt;p&gt;Then a simple problem gave birth to more problems with a chain reaction ongoing, to me finding myself working with PHP, something I would have never imagined doing in a million years before. &lt;/p&gt;

&lt;p&gt;So…. that’s it. Keep building and showcasing your work, and most importantly, have fun at it!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Debugging with Source Maps: A Comprehensive Guide</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Sun, 09 Mar 2025 18:44:24 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/debugging-with-source-maps-a-comprehensive-guide-3dhp</link>
      <guid>https://dev.to/chiragagg5k/debugging-with-source-maps-a-comprehensive-guide-3dhp</guid>
      <description>&lt;p&gt;We all have been there, working on a new feature for 10+ hours straight, and everything is going well. You build your project and push the code to production. And boom, a new production error alert! Everyone in your team - whether at a company or a hackathon - starts to look for someone to blame. Found it; it was from you. But none of your test suites resulted in an error. Everything with the code itself looks excellent.&lt;/p&gt;

&lt;p&gt;You check the logs. The error is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Uncaught Error: Cannot &lt;span class="nb"&gt;read &lt;/span&gt;property &lt;span class="s1"&gt;'xyz'&lt;/span&gt; of undefined at app.min.js:1:45678
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You think to yourself, what the heck is &lt;code&gt;app.min.js:1:45678&lt;/code&gt; supposed to mean? There was no file like that in the entire source code? Your file was called &lt;code&gt;app.js&lt;/code&gt;. And it's 45678 characters long! That's impossible to debug!! Still, you try to open the file and potentially find the root cause of the error. It's a mess. The entire file is filled with random gibberish you are unable to understand. What should you do?&lt;/p&gt;

&lt;p&gt;Now, this is where Source Maps come into play. Source Maps allow you to map the minified code in your production environment, &lt;em&gt;aka the random gibberish you just saw&lt;/em&gt;, with the actual source code, allowing you to pinpoint the root cause of the error in your source code and debug it effectively.&lt;/p&gt;

&lt;p&gt;In this blog, we will detail what source maps are, why and how they are created, and give some tips on effectively using source maps to debug your code. Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why is source code minified?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before we delve into Source Maps, let's first decode what happened to your clean, formatted and linked source code and why it looks nothing like it on the browser.&lt;/p&gt;

&lt;p&gt;The simple answer is minification.&lt;/p&gt;

&lt;p&gt;Minification is the process of converting your source code into production-ready code without changing any of its functionality. This is typically done by the bundler you are using, such as Webpack. To learn more about bundlers, you can check out this awesome &lt;a href="https://snipcart.com/blog/javascript-module-bundler" rel="noopener noreferrer"&gt;guide on Javascript bundlers&lt;/a&gt; by &lt;a href="https://snipcart.com/" rel="noopener noreferrer"&gt;Snipcart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Simply put, bundlers optimise your source code by stripping out whitespaces, comments, and redundant code and even removing or renaming variables for shorter alternatives. This makes your code super efficient and much smaller in size.&lt;/p&gt;

&lt;p&gt;Why does this happen?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved load times&lt;/strong&gt; - Smaller file sizes lead to better website loading times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Obfuscation—&lt;/strong&gt;Although it won't make your code entirely illegible, it does make it harder for regular users to understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser Performance&lt;/strong&gt; - The code is altered in a way that's easy for browser engines to parse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of what a minified React app code looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykp4yriiyci828833oct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykp4yriiyci828833oct.png" alt="Bundle.js file containing minified code" width="800" height="276"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;bundle.js file created by Webpack on building a simple React app, even the text is overflowing from the terminal screenshot!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What are source maps?
&lt;/h2&gt;

&lt;p&gt;Source maps are files whose names end with &lt;code&gt;.map&lt;/code&gt; and that map the minified code to your actual source code. Examples of such files can be &lt;code&gt;example.min.js.map&lt;/code&gt; or for css, &lt;code&gt;styles.css.map&lt;/code&gt;. They are explicitly generated only by build tools like Webpack, Vite, Rollup, Parcel etc. Since source maps are only required for debugging purposes, these tools usually have the option to generate source maps off by default. For example to enable it in Webpack, you can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// add this to your package.json file&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build:dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webpack --mode development --devtool source-map&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or add it to your &lt;code&gt;webpack.config.js&lt;/code&gt;  file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;devtool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source-map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...rest of your config&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A source map includes crucial information on how the mapping is done, including the actual source file name, the content it includes, various variable name the source code has, name of the minified code file etc.&lt;/p&gt;

&lt;p&gt;Here is a format of how a typical source map file looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mappings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AAAA,SAAQA,MAAMA,QAAQ,OAAO;AAC7B,SAAQC...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sourcesContent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;import React from 'react';&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;import { createRoot } from 'react-dom/...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;names&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;React&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createRoot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;useState&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bundle.js.map&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important section here is &lt;code&gt;mappings&lt;/code&gt;. This uses a special kind of encoding called &lt;a href="https://developer.chrome.com/blog/sourcemaps#base64-vlq-and-keeping-the-source-map-small" rel="noopener noreferrer"&gt;VLQ base 64 encoded string&lt;/a&gt; to map the lines and locations to compiled file and its corresponding original file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualising source maps
&lt;/h2&gt;

&lt;p&gt;"Okay, great!" I hear you saying. "How is this actually helpful? I still can't read the source maps and manually decode the mappings."&lt;/p&gt;

&lt;p&gt;That's a great question! This brings me to the main highlight of this blog—source map visualisers. These tools allow you to see the mappings in a visual manner to locate and debug the problem effectively. There are many source map visualisers on the market, but today, we will be focusing on Sokra &amp;amp; Paulirish's source map visualization. You can find the source code for this on their &lt;a href="https://github.com/sokra/source-map-visualization/" rel="noopener noreferrer"&gt;Github Repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a side-by-side comparison of how your code (on the right-hand side) can look like a jumbled mess when minified (on the left-hand side). However, the colour-coded mapping of the visualiser helps us map these two codes by hovering over them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpgftukud1078hx2iui4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpgftukud1078hx2iui4.png" alt="Comparison between minified code and source code" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Working Example
&lt;/h2&gt;

&lt;p&gt;Let’s create a simple React app and play around with it’s sourcemaps!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with creating a project directory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-project
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Init a new project:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the following dependencies into &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"react-counter-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Simple React Counter App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack serve --mode development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --mode production"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react-dom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18.2.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@babel/core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.23.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@babel/preset-env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.23.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@babel/preset-react"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^7.22.15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"babel-loader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^9.1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"css-loader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^6.8.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"html-webpack-plugin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.5.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"style-loader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.3.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"webpack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.88.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"webpack-cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.1.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"webpack-dev-server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.15.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;src/index.js&lt;/code&gt; file with following React code:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRoot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Counter: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Decrement&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// New React 18 createRoot API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add styling by adding a &lt;code&gt;src/styles.css&lt;/code&gt; file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* src/styles.css */&lt;/span&gt;
&lt;span class="nc"&gt;.app&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4CAF50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#45a049&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now define the webpack config by creating a &lt;code&gt;webpack.config.js&lt;/code&gt; file in the root folder:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// webpack.config.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HtmlWebpackPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html-webpack-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bundle.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;js|jsx&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/node_modules/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;css$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HtmlWebpackPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;devServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;static&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now you can start the application by running:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;webpack serve &lt;span class="nt"&gt;--mode&lt;/span&gt; development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how it should look (very basic i know :D):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdpawfyjl38iit5x40seg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdpawfyjl38iit5x40seg.png" alt="Basic UI Image" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find the source maps using your browser's dev tools. The format can look different depending on the browser you are using. Here, I am using Zen, but the format should look similar for all browsers.&lt;/p&gt;

&lt;p&gt;You can do so by right-clicking anywhere on the page and clicking on &lt;strong&gt;Inspect Element&lt;/strong&gt;. Then, go to the &lt;strong&gt;Sources&lt;/strong&gt; section of your browser and find the source file. Here on Zen, it's available in the debugger section since it's mainly used for debugging purposes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55ye3ujrmwuhjw9o62zo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55ye3ujrmwuhjw9o62zo.png" alt="Showing source map in dev tools" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you can load this in the &lt;a href="https://sokra.github.io/source-map-visualization/" rel="noopener noreferrer"&gt;source-map-visualization&lt;/a&gt;. It will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F241myprsrlt92gf2a25b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F241myprsrlt92gf2a25b.png" alt="Showing a visualization of source map" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On right you can skip all the React code and skip to the section that contains just your code. On hovering over each section of your code you will see exactly which part of the minified code it maps to!&lt;/p&gt;

&lt;p&gt;It can look pretty confusing at first, but try hovering over various elements in the UI and you will see how intuitive it actually is. For eg. in this example code,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;----&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Counter: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;....&lt;/span&gt; &lt;span class="c1"&gt;// so on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hovering over &lt;code&gt;React.useState&lt;/code&gt; reveals that it maps to a &lt;code&gt;createElement&lt;/code&gt; in the minified code. So our bundler, Webpack, in this case, optimised our code by directly converting our state into a javascript element and directly modifying it in subsequent code. This makes our application much, much more performant and reduces the file sizes the browser has to load!&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;While creating the example app, you may have noticed we had to explicitly add the flag &lt;code&gt;--mode development&lt;/code&gt; to the Webpack run command. This is because source maps are supposed to be used for debugging purposes only, and can lead to security concerns when used in production, including:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exposing Source Code&lt;/td&gt;
&lt;td&gt;Source maps reveal your original code, including comments and logic&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;hidden-source-map&lt;/code&gt; or &lt;code&gt;nosources-source-map&lt;/code&gt; in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP Protection&lt;/td&gt;
&lt;td&gt;Intellectual property may be exposed via full source maps&lt;/td&gt;
&lt;td&gt;Deploy source maps to secure, authenticated location&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File Size&lt;/td&gt;
&lt;td&gt;Source maps can be large, affecting download performance&lt;/td&gt;
&lt;td&gt;Generate maps only in development, or serve separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server Configuration&lt;/td&gt;
&lt;td&gt;CORS issues may prevent source map loading&lt;/td&gt;
&lt;td&gt;Configure proper &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; headers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are also tools like &lt;strong&gt;Sentry&lt;/strong&gt; or &lt;strong&gt;Rollbar&lt;/strong&gt; which use your source maps for better error reports without violating any of the security concerns. Tools like these are considered best practise for production environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Source maps are a mind-blowing feature that lets you map your source code precisely to the minified code loaded by your browsers, which is generated by bundlers like Webpack for performance and speed. We explored how debugging can be made easy using this feature and tools like source map visualisers to aid in the process.&lt;/p&gt;

&lt;p&gt;The web is built on top of layers and layers of abstractions done by tools like bundlers, but when things go catastrophically wrong, we might discover that these abstractions are not always hundred percent perfect, and hence, we need to take out our tools, look under the hood, and find the fix ourselves.&lt;/p&gt;

&lt;p&gt;To learn more about package managers like NPM, Bun, PNPM, and yarn, you can check out my other article, &lt;a href="https://www.chiragaggarwal.tech/blog/mastering-npm-a-comprehensive-guide-to-package-management" rel="noopener noreferrer"&gt;Mastering npm: A Comprehensive Guide to Package Management&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Thanks to these fantastic references by the Google Chrome dev team that helped me learn about source maps myself and in writing this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/devtools/javascript/source-maps" rel="noopener noreferrer"&gt;Debug your original code instead of deployed with source maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=FIYkjjFYvoI" rel="noopener noreferrer"&gt;What are source maps? #DevToolsTips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=SkUcO4ML5U0&amp;amp;t=241s" rel="noopener noreferrer"&gt;Using source maps in DevTools #DevToolsTips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Writing Event-Driven Serverless Code to Build Scalable Applications</title>
      <dc:creator>Chirag Aggarwal</dc:creator>
      <pubDate>Sun, 26 Jan 2025 17:12:20 +0000</pubDate>
      <link>https://dev.to/chiragagg5k/writing-event-driven-serverless-code-to-build-scalable-applications-2mol</link>
      <guid>https://dev.to/chiragagg5k/writing-event-driven-serverless-code-to-build-scalable-applications-2mol</guid>
      <description>&lt;p&gt;Serverless isn't just trendy—it's rewriting how software scales.  &lt;/p&gt;

&lt;p&gt;Netflix streams billions of hours without servers. Coca-Cola automates workflows without infrastructure. Figma and T-Mobile ditch downtime. What do they know that you don't?  &lt;/p&gt;

&lt;p&gt;The secret? Event-driven serverless code. It's the backbone of apps that scale instantly, cut costs, and survive traffic spikes. No servers. No guesswork. Just code that reacts.  &lt;/p&gt;

&lt;p&gt;This isn't hype—it's a blueprint. Ready to build smarter? Let's break down how event-driven serverless turns scalability from a challenge into a reflex.  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Brief Intro to Serverless&lt;/strong&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Spoiler alert: servers are still there.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are multiple definitions for this term online, often filled with complex jargon. The best way I like to define it is:&lt;br&gt;&lt;br&gt;
&lt;em&gt;A "fashion" of deploying your code where YOU don't have to think about the servers running your code.&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;Let's take an example:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F884rktyfom0qj4zwatzm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F884rktyfom0qj4zwatzm.png" alt="Serverless Deployment Diagram" width="800" height="998"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Take Bob. He built &lt;em&gt;mytrashcode.com&lt;/em&gt; but panicked at "server setup." He's a developer, not a sysadmin. Instead, he uploaded his code to a cloud provider. They handled security, scaling, and traffic—his site went live. No late-night server meltdowns. No panic during traffic surges. Done.  &lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Why Can't I Manage My Own Servers?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Managing your own servers usually takes one of two paths. You either run physical hardware—like turning an old laptop into a DIY server—or rent a Virtual Private Server (VPS) from providers like DigitalOcean Droplets, Azure VMs, or AWS Lightsail. These fall under IaaS (Infrastructure as a Service), where the cloud company provides the bare-metal infrastructure, but the rest—updates, scaling, security—is entirely up to you.  &lt;/p&gt;

&lt;p&gt;Does this mean self-managing servers is impossible?  &lt;/p&gt;

&lt;p&gt;Not at all. Plenty of teams still do it. But managing your own servers comes with a lot of... challenges, including:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Knowing how to manage &lt;strong&gt;infrastructure&lt;/strong&gt;/hardware.
&lt;/li&gt;
&lt;li&gt;Setting up &lt;strong&gt;auto-scaling&lt;/strong&gt; and downscaling.
&lt;/li&gt;
&lt;li&gt;Periodically applying &lt;strong&gt;system patches&lt;/strong&gt; and updates to avoid exposing vulnerabilities.
&lt;/li&gt;
&lt;li&gt;Configuring proxies, SSL certificate generation, &lt;strong&gt;network settings&lt;/strong&gt;, etc.
&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Dividing Your Code into Functions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Serverless code doesn't need to be monolithic, i.e., all code doesn't need to be in the same place. It can be a collection of bite-sized, event-triggered functions.  &lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Function&lt;/strong&gt; is nothing but a set of code that performs a specific task. When writing your entire code serverless, you'll find that you can divide your code into various functions, each handling a specific part of your application. Let's understand this more deeply with an example:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc9zdithhna5sjs2gup4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc9zdithhna5sjs2gup4.png" alt="Functions Diagram" width="800" height="533"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;When Bob first logs into &lt;em&gt;mytrashcode.com&lt;/em&gt; as a new user, the system triggers a "send welcome email" function before redirecting him. Subsequent logins bypass this function entirely, routing him straight to the dashboard. This separation serves a critical purpose—while 99% of users interact solely with the dashboard, isolating secondary functions (like email triggers) enables independent scaling.  &lt;/p&gt;

&lt;p&gt;Though trivial in this example, the cost implications compound dramatically at scale. Each decoupled function operates on its own resource allocation curve—high-frequency features like dashboard access demand consistent infrastructure, while one-time actions (welcome emails) can scale down during inactive periods. This modular approach prevents overprovisioning for rarely triggered events, even before considering complex systems with hundreds of interdependent functions.  &lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Where to Deploy???&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Okay, so just a quick recap—we now know:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploying serverless is great!
&lt;/li&gt;
&lt;li&gt;Dividing your code into functions is modular and scalable.
&lt;/li&gt;
&lt;li&gt;Functions can be triggered by events.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, where do you deploy this architecture? Leading platforms like AWS Lambda, Azure Functions, and Google Cloud Functions support it, but we'll focus on Appwrite Functions.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9sqdus26z0ygxclj0ms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9sqdus26z0ygxclj0ms.png" alt="Function Deployment Options" width="800" height="297"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Appwrite, an open-source Backend-as-a-Service (BaaS), bundles authentication, databases, storage, and serverless functions into a single toolkit. This tight integration streamlines deployment—instead of managing fragmented cloud services, Appwrite centralizes backend logic, letting you deploy event-driven functions with minimal overhead. For developers prioritizing simplicity without sacrificing scalability, this unified approach reduces operational friction significantly.  &lt;/p&gt;

&lt;p&gt;So, let's deploy our function!  &lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Deploying Your First Function&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before writing code, set up your backend on Appwrite:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;appwrite.io&lt;/a&gt; and register or log in.
&lt;/li&gt;
&lt;li&gt;Create an organization (if new).
&lt;/li&gt;
&lt;li&gt;Create a new project.
&lt;/li&gt;
&lt;li&gt;Copy your &lt;strong&gt;Project ID&lt;/strong&gt; for later use.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let's simulate a server-side project using the &lt;code&gt;node-appwrite&lt;/code&gt; package:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a project directory:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-project  
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Install the &lt;a href="https://appwrite.io/docs/tooling/command-line/installation" rel="noopener noreferrer"&gt;Appwrite CLI&lt;/a&gt; and initialize your project:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;  
appwrite init  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Install dependencies:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;dotenv node-appwrite  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Create your function using the Appwrite CLI:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;appwrite init &lt;span class="k"&gt;function&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For the runtime, I selected &lt;strong&gt;Node 20&lt;/strong&gt;, but you can choose any runtime.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write your main function in &lt;code&gt;src/main.js&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Users&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-appwrite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cloud.appwrite.io/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createEmailPasswordSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add a &lt;code&gt;start&lt;/code&gt; script in &lt;code&gt;package.json&lt;/code&gt; to run &lt;code&gt;node src/main.js&lt;/code&gt;.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file with the required environment variables.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This function simulates a new user creation and login, logging the session details.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Replace the email IDs with actual emails to receive the email.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpa9qh5cp756w6a5jpjrt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpa9qh5cp756w6a5jpjrt.png" alt="Functions Architecture" width="800" height="419"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Now, let's set up the function logic. Navigate to &lt;code&gt;functions/your-function&lt;/code&gt; where your function resides.  &lt;/p&gt;

&lt;p&gt;For this demo, we'll use &lt;a href="https://resend.com" rel="noopener noreferrer"&gt;Resend&lt;/a&gt; to send emails:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the &lt;code&gt;resend&lt;/code&gt; package:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;resend  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Update &lt;code&gt;src/main.js&lt;/code&gt; with this code:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://appwrite.io/docs/advanced/platform/events&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Resend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESEND_API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello@yourdomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hi, its nice to meet you!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email sent successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email sent successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You need to set up an account on Resend to get the API Key. Resend also requires you to connect to your own domain to send emails. You can read more about it on the &lt;a href="https://resend.com/docs/introduction" rel="noopener noreferrer"&gt;Resend docs&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;Now, let's push the created function to the console using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;appwrite push functions  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final step is to set up the event that connects the two pieces of code together using the &lt;code&gt;users.*.create&lt;/code&gt; event:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Appwrite console and navigate to your created project.
&lt;/li&gt;
&lt;li&gt;Navigate to the Functions tab.
&lt;/li&gt;
&lt;li&gt;You should see your newly created function there—click on it.
&lt;/li&gt;
&lt;li&gt;Go to its settings and under the Events section.
&lt;/li&gt;
&lt;li&gt;Add a new event to trigger this function: &lt;code&gt;users.*.create&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb7etulqcgb31sx5at2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb7etulqcgb31sx5at2t.png" alt="Appwrite Console showing Events" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And... voila! Your program is done. If everything is set up correctly, running your main script should send the newly created user an invitation email. Try it using:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In conclusion, serverless architecture is more than just a passing trend—it's a transformative approach to building and scaling modern applications.  &lt;/p&gt;

&lt;p&gt;Platforms like Appwrite further simplify the process, offering a unified backend solution that integrates seamlessly with serverless functions. Whether you're a solo developer like Bob or part of a larger team, adopting serverless can turn scalability from a daunting challenge into an effortless reflex.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjnuiov1eikg0u26hgpyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjnuiov1eikg0u26hgpyt.png" alt="Conclusion Image" width="800" height="493"&gt;&lt;/a&gt; &lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thanks for reading!&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;You can also connect with me here: &lt;a href="https://www.chiragaggarwal.tech/" rel="noopener noreferrer"&gt;https://www.chiragaggarwal.tech/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>serverless</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
