<?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: Roman Onishchenko</title>
    <description>The latest articles on DEV Community by Roman Onishchenko (@romanindev).</description>
    <link>https://dev.to/romanindev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3838982%2F48bd1dcf-0252-4b7f-acba-8c3d375a98a2.jpeg</url>
      <title>DEV Community: Roman Onishchenko</title>
      <link>https://dev.to/romanindev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/romanindev"/>
    <language>en</language>
    <item>
      <title>I got tired of rewriting the same HTTP client — so I built one</title>
      <dc:creator>Roman Onishchenko</dc:creator>
      <pubDate>Mon, 23 Mar 2026 00:41:00 +0000</pubDate>
      <link>https://dev.to/romanindev/i-got-tired-of-rewriting-the-same-http-client-so-i-built-one-5cf9</link>
      <guid>https://dev.to/romanindev/i-got-tired-of-rewriting-the-same-http-client-so-i-built-one-5cf9</guid>
      <description>&lt;p&gt;At some point, I noticed a pattern.&lt;/p&gt;

&lt;p&gt;Almost every project I worked on had the same thing:&lt;/p&gt;

&lt;p&gt;An HTTP client.&lt;/p&gt;

&lt;p&gt;Not just a simple one — but something with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retries
&lt;/li&gt;
&lt;li&gt;authentication
&lt;/li&gt;
&lt;li&gt;logging
&lt;/li&gt;
&lt;li&gt;error handling
&lt;/li&gt;
&lt;li&gt;timeouts
&lt;/li&gt;
&lt;li&gt;request lifecycle logic
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And every time, it was slightly different.&lt;/p&gt;

&lt;p&gt;Not better. Just different.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;When services talk to each other, things get messy quickly.&lt;/p&gt;

&lt;p&gt;Requests fail, timeouts happen, retries behave unpredictably, errors are inconsistent.&lt;/p&gt;

&lt;p&gt;And instead of solving business problems, you end up rewriting infrastructure code.&lt;/p&gt;

&lt;p&gt;Again and again.&lt;/p&gt;




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

&lt;p&gt;I didn’t want another "just fetch wrapper".&lt;/p&gt;

&lt;p&gt;I wanted something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;predictable
&lt;/li&gt;
&lt;li&gt;explicit
&lt;/li&gt;
&lt;li&gt;easy to reason about
&lt;/li&gt;
&lt;li&gt;flexible when needed
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;microservices
&lt;/li&gt;
&lt;li&gt;internal APIs
&lt;/li&gt;
&lt;li&gt;integration services
&lt;/li&gt;
&lt;li&gt;background jobs
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  So I built &lt;code&gt;@dfsync/client&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A lightweight HTTP client focused on &lt;strong&gt;reliable service-to-service communication&lt;/strong&gt; in Node.js.&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%2Fidbi9ju55qpd1hkuhyl2.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%2Fidbi9ju55qpd1hkuhyl2.png" alt="HTTP client for service-to-service communication" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s part of an open-source toolkit:&lt;br&gt;
👉 &lt;a href="https://github.com/dfsyncjs/dfsync" rel="noopener noreferrer"&gt;https://github.com/dfsyncjs/dfsync&lt;/a&gt;&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;createClient&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;@dfsync/client&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="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.service.local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;attempts&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="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;baseDelayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&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;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&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;h2&gt;
  
  
  What’s already there
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;retry support&lt;/li&gt;
&lt;li&gt;auth handling&lt;/li&gt;
&lt;li&gt;request lifecycle hooks: &lt;code&gt;beforeRequest&lt;/code&gt;, &lt;code&gt;afterResponse&lt;/code&gt;, &lt;code&gt;onError&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;structured error handling&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I’m working on now (0.6.x)
&lt;/h2&gt;

&lt;p&gt;Right now I’m focusing on request lifecycle improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AbortSignal support&lt;/li&gt;
&lt;li&gt;request context (passing metadata through lifecycle)&lt;/li&gt;
&lt;li&gt;correlation ID propagation (&lt;code&gt;x-request-id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;improved hook context&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s next
&lt;/h2&gt;

&lt;p&gt;Planned direction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;observability (latency, retry metadata)&lt;/li&gt;
&lt;li&gt;safer integrations (validation, idempotency)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why I’m sharing this
&lt;/h2&gt;

&lt;p&gt;This is still early, and I’d really like feedback.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this solve a real problem for you?&lt;/li&gt;
&lt;li&gt;What would you expect from such a client?&lt;/li&gt;
&lt;li&gt;What’s missing?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/dfsyncjs/dfsync" rel="noopener noreferrer"&gt;https://github.com/dfsyncjs/dfsync&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/@dfsync/client" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@dfsync/client&lt;/a&gt;&lt;br&gt;
Website: &lt;a href="https://dfsyncjs.github.io" rel="noopener noreferrer"&gt;https://dfsyncjs.github.io&lt;/a&gt;&lt;br&gt;
LinkedIn: &lt;a href="https://www.linkedin.com/company/dfsync" rel="noopener noreferrer"&gt;https://www.linkedin.com/company/dfsync&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’ve ever rewritten the same HTTP client — I’d love to hear your experience.&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
