<?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: Shingo Kawamura</title>
    <description>The latest articles on DEV Community by Shingo Kawamura (@pannakoota).</description>
    <link>https://dev.to/pannakoota</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%2F132222%2Fd309af95-2ce1-4acb-8a99-922fb3c3f78a.png</url>
      <title>DEV Community: Shingo Kawamura</title>
      <link>https://dev.to/pannakoota</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pannakoota"/>
    <language>en</language>
    <item>
      <title>I built a transport-independent foundation for JSON API clients in Perl</title>
      <dc:creator>Shingo Kawamura</dc:creator>
      <pubDate>Sat, 19 Sep 2026 23:37:59 +0000</pubDate>
      <link>https://dev.to/pannakoota/i-built-a-transport-independent-foundation-for-json-api-clients-in-perl-1od6</link>
      <guid>https://dev.to/pannakoota/i-built-a-transport-independent-foundation-for-json-api-clients-in-perl-1od6</guid>
      <description>&lt;p&gt;Writing a simple API client is easy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&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;$http&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the client starts being used in production.&lt;/p&gt;

&lt;p&gt;Suddenly, it needs to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;query parameters&lt;/li&gt;
&lt;li&gt;JSON encoding and decoding&lt;/li&gt;
&lt;li&gt;structured errors&lt;/li&gt;
&lt;li&gt;retries and &lt;code&gt;Retry-After&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rate limits&lt;/li&gt;
&lt;li&gt;pagination&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;request IDs&lt;/li&gt;
&lt;li&gt;logging and metrics&lt;/li&gt;
&lt;li&gt;idempotency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, what started as a small API wrapper often becomes a collection of infrastructure code.&lt;/p&gt;

&lt;p&gt;I found myself looking at this problem and asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much of this is actually specific to the API being wrapped?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not very much.&lt;/p&gt;

&lt;p&gt;That led me to build &lt;strong&gt;HTTP::API::Core&lt;/strong&gt;, a small, dependency-light foundation for building JSON HTTP API clients in Perl.&lt;/p&gt;

&lt;p&gt;The key idea is simple: &lt;strong&gt;keep HTTP transport separate from API-client policy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use HTTP::Tiny, LWP, Mojo::UserAgent, Furl, or your preferred transport for HTTP. Let HTTP::API::Core handle the repetitive policy around it.&lt;/p&gt;

&lt;p&gt;The project is available on &lt;a href="https://github.com/kawamurashingo/HTTP-API-Core" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is deliberately not another HTTP client
&lt;/h2&gt;

&lt;p&gt;One of the main design decisions was not to replace the existing HTTP ecosystem.&lt;/p&gt;

&lt;p&gt;Perl already has good HTTP implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP::Tiny&lt;/li&gt;
&lt;li&gt;LWP&lt;/li&gt;
&lt;li&gt;Mojo::UserAgent&lt;/li&gt;
&lt;li&gt;Furl&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HTTP::API::Core sits above the transport layer.&lt;/p&gt;

&lt;p&gt;The HTTP implementation is responsible for making the request.&lt;/p&gt;

&lt;p&gt;HTTP::API::Core is responsible for the application-level policies surrounding that request.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your API client
      |
HTTP::API::Core
      |
HTTP::Tiny / LWP / Mojo / Furl / custom transport
      |
     HTTP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation means an API wrapper does not have to couple its retry, pagination, error handling, or authentication logic to a particular HTTP library.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small example
&lt;/h2&gt;

&lt;p&gt;A client can start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;HTTP::API::&lt;/span&gt;&lt;span class="nv"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HTTP::API::&lt;/span&gt;&lt;span class="nv"&gt;Core&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;headers&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;Authorization&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;$ENV&lt;/span&gt;&lt;span class="s2"&gt;{API_TOKEN}&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;attempts&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;max_delay&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;jitter&lt;/span&gt;     &lt;span class="o"&gt;=&amp;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="k"&gt;my&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;$api&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A service-specific client can then remain small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;/users/&lt;/span&gt;&lt;span class="si"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;")&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;json&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 resource method describes the API.&lt;/p&gt;

&lt;p&gt;The core handles the repetitive infrastructure around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries are more complicated than they look
&lt;/h2&gt;

&lt;p&gt;Retries were one of the areas where I wanted conservative defaults.&lt;/p&gt;

&lt;p&gt;HTTP::API::Core retries safe/idempotent methods by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET
HEAD
PUT
DELETE
OPTIONS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not automatically retry &lt;code&gt;POST&lt;/code&gt; or &lt;code&gt;PATCH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Retryable failures include transport failures and HTTP responses such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;408
425
429
5xx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also understands &lt;code&gt;Retry-After&lt;/code&gt; and rate-limit reset metadata.&lt;/p&gt;

&lt;p&gt;Delays use exponential backoff with optional jitter.&lt;/p&gt;

&lt;p&gt;If an API explicitly supports idempotent POST requests, that behavior can be enabled rather than silently assumed.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;$api&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;/jobs&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;methods&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;['&lt;/span&gt;&lt;span class="s1"&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;The goal is to make the safe behavior the boring default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits should be data, not special cases
&lt;/h2&gt;

&lt;p&gt;Different APIs expose rate limits differently.&lt;/p&gt;

&lt;p&gt;Instead of making application code repeatedly inspect raw headers, responses expose normalized rate-limit information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;rate_limit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;limit&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;remaining&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;wait_seconds&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;exhausted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP::API::Core understands the commonly used &lt;code&gt;X-RateLimit-*&lt;/code&gt; family as well as standard-style rate-limit fields and &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The retry system can use the same normalized information.&lt;/p&gt;

&lt;p&gt;This is one of the ideas behind the project: parse infrastructure information once and expose it consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  One pagination interface, different API styles
&lt;/h2&gt;

&lt;p&gt;Pagination is another thing that looks trivial until you support several APIs.&lt;/p&gt;

&lt;p&gt;Some APIs return a next URL.&lt;/p&gt;

&lt;p&gt;Others use page numbers.&lt;/p&gt;

&lt;p&gt;Others use opaque cursors.&lt;/p&gt;

&lt;p&gt;HTTP::API::Core provides one iterator interface for these styles.&lt;/p&gt;

&lt;p&gt;For cursor pagination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$pager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$api&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;mode&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data.users&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="k"&gt;next&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta.next_cursor&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For page-number pagination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$pager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$api&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;mode&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;items&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;page_size&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeated cursors or next URLs are detected so a broken API response does not accidentally create an infinite pagination loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication is implemented through hooks
&lt;/h2&gt;

&lt;p&gt;I did not want authentication to become a growing collection of special cases inside the HTTP client.&lt;/p&gt;

&lt;p&gt;Instead, HTTP::API::Core provides lifecycle hooks.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HTTP::API::&lt;/span&gt;&lt;span class="nv"&gt;Core&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;

    &lt;span class="s"&gt;hooks&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;before_request&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="nv"&gt;Authorization&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="s2"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="s"&gt;after_response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nv"&gt;log_status&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;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;

        &lt;span class="s"&gt;on_error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nv"&gt;record_failure&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;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;category&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same mechanism can be used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;metrics&lt;/li&gt;
&lt;li&gt;tracing&lt;/li&gt;
&lt;li&gt;request customization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small helpers for Bearer, Basic, and API-key authentication are included.&lt;/p&gt;

&lt;p&gt;OAuth token acquisition and refresh deliberately remain outside the core.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability without choosing an observability framework
&lt;/h2&gt;

&lt;p&gt;The library records useful request metadata without requiring a specific logging or tracing stack.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;request_id&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common request ID headers are normalized, including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Request-Id
Request-Id
X-Correlation-Id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hooks can then send this information wherever an application wants.&lt;/p&gt;

&lt;p&gt;The library provides the data without deciding how the application should observe it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured errors
&lt;/h2&gt;

&lt;p&gt;Another design goal was to avoid application code parsing human-readable error strings.&lt;/p&gt;

&lt;p&gt;Errors have machine-readable categories such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;encode
decode
transport
http
hook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Application code can inspect fields including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;$error&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;category&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;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;status&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;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;retryable&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;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP errors retain their response information, so callers can still inspect headers, text, JSON, or rate-limit state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transport independence
&lt;/h2&gt;

&lt;p&gt;The transport boundary is intentionally small.&lt;/p&gt;

&lt;p&gt;A transport receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;%options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and returns a response-like hash containing at least:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="s"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the same API client policy can sit on top of different HTTP implementations.&lt;/p&gt;

&lt;p&gt;It also makes testing API-specific code possible without requiring real network requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing against real API conventions
&lt;/h2&gt;

&lt;p&gt;The repository includes example clients demonstrating different API styles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub — page-number pagination and rate-limit metadata&lt;/li&gt;
&lt;li&gt;Slack — cursor pagination&lt;/li&gt;
&lt;li&gt;Cloudflare — page-number pagination using response metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not intended to replace their official SDKs.&lt;/p&gt;

&lt;p&gt;They are examples showing that the abstraction can map onto APIs with significantly different conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I intentionally left out
&lt;/h2&gt;

&lt;p&gt;A reusable core can easily become a framework that tries to solve everything.&lt;/p&gt;

&lt;p&gt;I am trying to avoid that.&lt;/p&gt;

&lt;p&gt;HTTP::API::Core intentionally does not handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service-specific SDK behavior&lt;/li&gt;
&lt;li&gt;complete OAuth flows&lt;/li&gt;
&lt;li&gt;OpenAPI generation&lt;/li&gt;
&lt;li&gt;GraphQL-specific behavior&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;HTTP server functionality&lt;/li&gt;
&lt;li&gt;async runtime concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is intended to remain small, predictable, dependency-light, and transport-independent.&lt;/p&gt;

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

&lt;p&gt;Although this implementation is for Perl, the architectural question is language-independent.&lt;/p&gt;

&lt;p&gt;Where should the boundary be between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the HTTP transport,&lt;/li&gt;
&lt;li&gt;reusable API-client policy, and&lt;/li&gt;
&lt;li&gt;service-specific domain methods?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Putting everything into the HTTP layer makes the transport too opinionated.&lt;/p&gt;

&lt;p&gt;Putting everything into every service SDK means repeatedly implementing retries, pagination, errors, authentication, and rate-limit handling.&lt;/p&gt;

&lt;p&gt;HTTP::API::Core is my attempt to put that boundary in the middle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HTTP::API::Core is available on GitHub:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/kawamurashingo/HTTP-API-Core" rel="noopener noreferrer"&gt;github.com/kawamurashingo/HTTP-API-Core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd be especially interested in feedback from people who maintain API clients or SDKs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What functionality do you consider generic API-client infrastructure, and what would you deliberately keep out of a core like this?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>perl</category>
      <category>api</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I Built a jq-Compatible Tool in Pure Perl (and Why It Still Matters)</title>
      <dc:creator>Shingo Kawamura</dc:creator>
      <pubDate>Fri, 02 Jan 2026 13:06:54 +0000</pubDate>
      <link>https://dev.to/pannakoota/why-i-built-a-jq-compatible-tool-in-pure-perl-and-why-it-still-matters-45a6</link>
      <guid>https://dev.to/pannakoota/why-i-built-a-jq-compatible-tool-in-pure-perl-and-why-it-still-matters-45a6</guid>
      <description>&lt;p&gt;Modern tooling often assumes a perfect world:&lt;br&gt;
latest Linux, internet access, package managers, and rebuildable binaries.&lt;/p&gt;

&lt;p&gt;Reality is different.&lt;/p&gt;

&lt;p&gt;In many production environments — especially enterprise, legacy, or air-gapped systems —&lt;br&gt;
those assumptions break down.&lt;/p&gt;

&lt;p&gt;That gap is why I built jq-lite.&lt;/p&gt;




&lt;p&gt;The Problem: jq Is Great — Until You Can’t Use It&lt;/p&gt;

&lt;p&gt;jq is an excellent JSON processor.&lt;br&gt;
But in real operations, I kept hitting walls:&lt;/p&gt;

&lt;p&gt;Legacy UNIX systems with outdated glibc&lt;/p&gt;

&lt;p&gt;Air-gapped environments with no package repositories&lt;/p&gt;

&lt;p&gt;Minimal containers without build tools&lt;/p&gt;

&lt;p&gt;Environments where “just install jq” was not an option&lt;/p&gt;

&lt;p&gt;In those cases, JSON processing didn’t disappear —&lt;br&gt;
the need became more critical.&lt;/p&gt;

&lt;p&gt;Shell scripts still had to be reliable.&lt;br&gt;
Automation still had to work.&lt;/p&gt;




&lt;p&gt;The Idea: A jq-Compatible Tool That Never Breaks Scripts&lt;/p&gt;

&lt;p&gt;I didn’t want another JSON tool.&lt;/p&gt;

&lt;p&gt;I wanted a tool that could be:&lt;/p&gt;

&lt;p&gt;Dropped into restricted systems&lt;/p&gt;

&lt;p&gt;Trusted in shell scripts&lt;/p&gt;

&lt;p&gt;Stable across years, not releases&lt;/p&gt;

&lt;p&gt;So I built jq-lite with three strict rules:&lt;/p&gt;




&lt;p&gt;Design Rule #1: Pure Perl, No Dependencies&lt;/p&gt;

&lt;p&gt;jq-lite is written in pure Perl.&lt;/p&gt;

&lt;p&gt;No native extensions&lt;/p&gt;

&lt;p&gt;No external libraries&lt;/p&gt;

&lt;p&gt;No compilation step&lt;/p&gt;

&lt;p&gt;If Perl exists, jq-lite runs.&lt;/p&gt;

&lt;p&gt;That means it works on:&lt;/p&gt;

&lt;p&gt;legacy Linux / UNIX&lt;/p&gt;

&lt;p&gt;minimal containers&lt;/p&gt;

&lt;p&gt;offline environments&lt;/p&gt;

&lt;p&gt;systems where only base OS tools are allowed&lt;/p&gt;

&lt;p&gt;Perl is still one of the most widely deployed runtimes in the world —&lt;br&gt;
especially in places modern tooling forgot.&lt;/p&gt;




&lt;p&gt;Design Rule #2: jq Compatibility Where It Matters&lt;/p&gt;

&lt;p&gt;jq-lite aims to be jq-compatible in daily CLI usage:&lt;/p&gt;

&lt;p&gt;echo '{}' | jq-lite --arg greeting hello '.hello = $greeting'&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "hello": "hello"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The goal isn’t to clone every feature,&lt;br&gt;
but to support the jq patterns people actually use in automation.&lt;/p&gt;




&lt;p&gt;Design Rule #3: A Stable CLI Contract (This Is the Important Part)&lt;/p&gt;

&lt;p&gt;Here’s the part that made jq-lite different.&lt;/p&gt;

&lt;p&gt;jq-lite defines a documented CLI contract:&lt;/p&gt;

&lt;p&gt;exit codes&lt;/p&gt;

&lt;p&gt;error categories&lt;/p&gt;

&lt;p&gt;stderr prefixes&lt;/p&gt;

&lt;p&gt;stdout behavior on failure&lt;/p&gt;

&lt;p&gt;And that contract is tested.&lt;/p&gt;

&lt;p&gt;Exit Code   Meaning&lt;/p&gt;

&lt;p&gt;0   Success&lt;br&gt;
1   -e used and result was false / null / empty&lt;br&gt;
2   Compile error&lt;br&gt;
3   Runtime error&lt;br&gt;
4   Input error&lt;br&gt;
5   Usage error&lt;/p&gt;

&lt;p&gt;This means shell scripts can safely rely on jq-lite:&lt;/p&gt;

&lt;p&gt;if jq-lite -e '.enabled' config.json; then&lt;br&gt;
  deploy&lt;br&gt;
fi&lt;/p&gt;

&lt;p&gt;No surprises.&lt;br&gt;
No silent behavior changes.&lt;/p&gt;

&lt;p&gt;In operations, this matters more than features.&lt;/p&gt;




&lt;p&gt;Why Not Rewrite It in Rust or Go?&lt;/p&gt;

&lt;p&gt;I get this question a lot.&lt;/p&gt;

&lt;p&gt;Because the problem wasn’t performance or language fashion.&lt;/p&gt;

&lt;p&gt;The problem was deployability and longevity.&lt;/p&gt;

&lt;p&gt;Perl already exists on:&lt;/p&gt;

&lt;p&gt;old servers&lt;/p&gt;

&lt;p&gt;enterprise systems&lt;/p&gt;

&lt;p&gt;restricted environments&lt;/p&gt;

&lt;p&gt;Adding jq-lite doesn’t require changing the environment —&lt;br&gt;
just using what’s already there.&lt;/p&gt;




&lt;p&gt;Who Is jq-lite For?&lt;/p&gt;

&lt;p&gt;jq-lite is not for everyone.&lt;/p&gt;

&lt;p&gt;It is for people who:&lt;/p&gt;

&lt;p&gt;maintain legacy systems&lt;/p&gt;

&lt;p&gt;write shell scripts that must survive years&lt;/p&gt;

&lt;p&gt;operate in restricted or offline environments&lt;/p&gt;

&lt;p&gt;care about CLI behavior as a contract, not a suggestion&lt;/p&gt;

&lt;p&gt;If you’ve ever thought&lt;br&gt;
“this script must not break in five years”&lt;br&gt;
— jq-lite is for you.&lt;/p&gt;




&lt;p&gt;Where to Find It&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/kawamurashingo/JQ-Lite" rel="noopener noreferrer"&gt;https://github.com/kawamurashingo/JQ-Lite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CLI Contract: &lt;a href="https://github.com/kawamurashingo/JQ-Lite/blob/main/docs/cli-contract.md" rel="noopener noreferrer"&gt;https://github.com/kawamurashingo/JQ-Lite/blob/main/docs/cli-contract.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CPAN: &lt;a href="https://metacpan.org/release/JQ-Lite" rel="noopener noreferrer"&gt;https://metacpan.org/release/JQ-Lite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alpine Linux package available&lt;/p&gt;




&lt;p&gt;Final Thought&lt;/p&gt;

&lt;p&gt;Modern tools optimize for speed of change.&lt;/p&gt;

&lt;p&gt;jq-lite optimizes for stability over time.&lt;/p&gt;

&lt;p&gt;Sometimes, that’s the more important optimization.&lt;/p&gt;




&lt;p&gt;Author&lt;br&gt;
川村慎吾（Shingo Kawamura）&lt;br&gt;
SRE / Infrastructure Engineer&lt;br&gt;
CPAN author of jq-lite&lt;/p&gt;

</description>
      <category>perl</category>
      <category>devops</category>
      <category>cli</category>
      <category>legacy</category>
    </item>
  </channel>
</rss>
