<?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: Miles Hilliard</title>
    <description>The latest articles on DEV Community by Miles Hilliard (@mhilliard27).</description>
    <link>https://dev.to/mhilliard27</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%2F3935398%2Fcb2d2a27-5502-49ce-8c8f-56f48457498a.jpg</url>
      <title>DEV Community: Miles Hilliard</title>
      <link>https://dev.to/mhilliard27</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mhilliard27"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Miles Hilliard</dc:creator>
      <pubDate>Tue, 26 May 2026 21:41:12 +0000</pubDate>
      <link>https://dev.to/mhilliard27/-4keo</link>
      <guid>https://dev.to/mhilliard27/-4keo</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mhilliard27/countapixyz-is-dead-heres-the-drop-in-replacement-i-found-and-actually-use-16b8" class="crayons-story__hidden-navigation-link"&gt;countapi.xyz is dead. Here's the drop-in replacement I found (and actually use)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/mhilliard27" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3935398%2Fcb2d2a27-5502-49ce-8c8f-56f48457498a.jpg" alt="mhilliard27 profile" class="crayons-avatar__image" width="800" height="1166"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mhilliard27" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Miles Hilliard
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Miles Hilliard
                
              
              &lt;div id="story-author-preview-content-3684995" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mhilliard27" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3935398%2Fcb2d2a27-5502-49ce-8c8f-56f48457498a.jpg" class="crayons-avatar__image" alt="" width="800" height="1166"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Miles Hilliard&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/mhilliard27/countapixyz-is-dead-heres-the-drop-in-replacement-i-found-and-actually-use-16b8" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mhilliard27/countapixyz-is-dead-heres-the-drop-in-replacement-i-found-and-actually-use-16b8" id="article-link-3684995"&gt;
          countapi.xyz is dead. Here's the drop-in replacement I found (and actually use)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/mhilliard27/countapixyz-is-dead-heres-the-drop-in-replacement-i-found-and-actually-use-16b8#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I made a simple WireGuard GUI for Linux (that works)</title>
      <dc:creator>Miles Hilliard</dc:creator>
      <pubDate>Tue, 26 May 2026 21:40:46 +0000</pubDate>
      <link>https://dev.to/mhilliard27/i-made-a-simple-wireguard-gui-for-linux-that-works-lhd</link>
      <guid>https://dev.to/mhilliard27/i-made-a-simple-wireguard-gui-for-linux-that-works-lhd</guid>
      <description>&lt;p&gt;If you’ve ever used WireGuard on Linux, you already know it’s great… until you actually have to use it daily.&lt;/p&gt;

&lt;p&gt;It’s fast, stable, super clean under the hood — but managing configs through the terminal gets old pretty quickly.&lt;/p&gt;

&lt;p&gt;So I ended up building something I wish already existed: a simple GUI for WireGuard on Linux.&lt;/p&gt;

&lt;p&gt;PyPI: wireguard-gui on PyPI&lt;br&gt;
GitHub: wireguard-gui on GitHub&lt;/p&gt;

&lt;h1&gt;
  
  
  What it is
&lt;/h1&gt;

&lt;p&gt;It’s a lightweight GUI wrapper around WireGuard tools like wg and wg-quick.&lt;/p&gt;

&lt;p&gt;It doesn’t try to replace WireGuard or re-invent networking. It just makes it easier to:&lt;/p&gt;

&lt;p&gt;see your configs&lt;br&gt;
connect/disconnect tunnels&lt;br&gt;
switch between profiles&lt;br&gt;
check status without opening a terminal&lt;/p&gt;

&lt;p&gt;Basically: click button → VPN works.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why I built it
&lt;/h1&gt;

&lt;p&gt;I was using WireGuard daily across different networks (home, dev stuff, random testing environments), and I kept running into the same annoying pattern:&lt;/p&gt;

&lt;p&gt;open terminal&lt;br&gt;
type command&lt;br&gt;
forget which config was active&lt;br&gt;
check status again&lt;br&gt;
repeat forever&lt;/p&gt;

&lt;p&gt;It wasn’t broken… just friction-heavy.&lt;/p&gt;

&lt;p&gt;At some point I just thought:&lt;br&gt;
“why isn’t there a simple GUI for this already?”&lt;/p&gt;

&lt;p&gt;So I made one.&lt;/p&gt;

&lt;h1&gt;
  
  
  What it can do
&lt;/h1&gt;

&lt;p&gt;Right now it supports:&lt;/p&gt;

&lt;p&gt;listing WireGuard configs on the system&lt;br&gt;
connecting / disconnecting tunnels&lt;br&gt;
showing current active state&lt;br&gt;
switching between multiple profiles&lt;br&gt;
working directly with system WireGuard configs (no weird duplication)&lt;/p&gt;

&lt;p&gt;It’s intentionally simple. I didn’t want it turning into a full network manager.&lt;/p&gt;

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

&lt;p&gt;Under the hood, it just wraps existing WireGuard tools.&lt;/p&gt;

&lt;p&gt;No custom VPN stack.&lt;br&gt;
No kernel tricks.&lt;br&gt;
No replacement daemon.&lt;/p&gt;

&lt;p&gt;If Linux can already do it, I just call it from Python and present it in a UI.&lt;/p&gt;

&lt;p&gt;That keeps it lightweight and (hopefully) less fragile than bigger “network manager replacement” type tools.&lt;/p&gt;

&lt;h1&gt;
  
  
  Install
&lt;/h1&gt;

&lt;p&gt;pip install wireguard-gui&lt;/p&gt;

&lt;p&gt;Then just run it from your terminal or application launcher depending on your setup.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I learned building it
&lt;/h1&gt;

&lt;p&gt;A few things surprised me:&lt;/p&gt;

&lt;p&gt;WireGuard itself is way easier to work with than most GUI tools make it look&lt;br&gt;
permissions are the real pain point, not networking&lt;br&gt;
most “VPN GUI problems” are just bad state handling&lt;br&gt;
Linux desktop environments behave very differently (this was fun… and painful)&lt;/p&gt;

&lt;p&gt;Also, Snap packaging makes everything slightly more chaotic than it should be (but it works).&lt;/p&gt;

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

&lt;p&gt;This is still early, but I want to improve it with:&lt;/p&gt;

&lt;p&gt;system tray support (probably the biggest quality-of-life upgrade)&lt;br&gt;
auto-reconnect on network change&lt;br&gt;
better status visibility (handshake timing, latency, etc)&lt;br&gt;
smoother onboarding for new configs&lt;br&gt;
maybe Flatpak support&lt;/p&gt;

&lt;p&gt;Not trying to bloat it — just remove friction.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why release it publicly
&lt;/h1&gt;

&lt;p&gt;Honestly, I think small tools like this are worth sharing even if they’re not “perfect”.&lt;/p&gt;

&lt;p&gt;A lot of Linux tooling is either:&lt;/p&gt;

&lt;p&gt;super powerful but CLI-only&lt;br&gt;
or overly complex GUI suites&lt;/p&gt;

&lt;p&gt;This sits in the middle: simple, focused, and just enough UI to make life easier.&lt;/p&gt;

&lt;p&gt;Even if only a handful of people use it, that’s still useful.&lt;/p&gt;

&lt;p&gt;If you try it&lt;/p&gt;

&lt;p&gt;Feedback is honestly the most useful thing right now.&lt;/p&gt;

&lt;p&gt;Especially stuff like:&lt;/p&gt;

&lt;p&gt;what distro you’re on&lt;br&gt;
what broke (if anything)&lt;br&gt;
weird permission issues&lt;br&gt;
UI annoyances&lt;/p&gt;

&lt;p&gt;Even small notes help a lot.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>networking</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>countapi.xyz is dead. Here's the drop-in replacement I found (and actually use)</title>
      <dc:creator>Miles Hilliard</dc:creator>
      <pubDate>Sat, 16 May 2026 19:37:27 +0000</pubDate>
      <link>https://dev.to/mhilliard27/countapixyz-is-dead-heres-the-drop-in-replacement-i-found-and-actually-use-16b8</link>
      <guid>https://dev.to/mhilliard27/countapixyz-is-dead-heres-the-drop-in-replacement-i-found-and-actually-use-16b8</guid>
      <description>&lt;p&gt;If you've been using countapi.xyz for page view tracking, button click counters, or really anything that just needed a simple number that goes up... you've probably noticed it's gone. No announcement, no migration guide, just a dead URL.&lt;/p&gt;

&lt;p&gt;And honestly, it was one of those APIs that was so useful precisely because it asked nothing of you. No account. No API key. No SDK to install. Just fetch a URL and get a number back. It was perfect.&lt;/p&gt;

&lt;p&gt;So when it died, a lot of people were left hunting for alternatives. Most of what I found either required signing up, had weird rate limits buried in the fine print, or was just overkill for what is fundamentally a very simple problem.&lt;/p&gt;

&lt;p&gt;Then I found &lt;a href="https://countapi.mileshilliard.com" rel="noopener noreferrer"&gt;countapi.mileshilliard.com&lt;/a&gt; and it's basically exactly what we lost.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;It's a free, open source counting API built as a direct spiritual successor to countapi.xyz. No signup. No API keys. No headers. You just hit an endpoint and it gives you a number.&lt;/p&gt;

&lt;p&gt;The whole thing runs on a Python serverless backend with Redis, and it's hosted on OCI with near 99% uptime (there's a public UptimeRobot dashboard you can check yourself).&lt;/p&gt;

&lt;p&gt;The concept is dead simple: every counter is identified by a unique &lt;strong&gt;key&lt;/strong&gt;. That's it. You pick a key name, and the API tracks a number against it. Since there are no namespaces like the original had, you just want to make your key specific enough that nobody else is going to accidentally share it, something like &lt;code&gt;johns-blog-homepage-visits&lt;/code&gt; rather than just &lt;code&gt;visits&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The endpoints
&lt;/h2&gt;

&lt;p&gt;There are really only three you'll ever use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hit (increment by +1)&lt;/strong&gt;&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;GET /api/v1/hit/your_key
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Key updated successfully"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&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;&lt;strong&gt;Get (read without incrementing)&lt;/strong&gt;&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;GET /api/v1/get/your_key
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Key requested successfully"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&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;&lt;strong&gt;Set (jump to a specific value)&lt;/strong&gt;&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;GET /api/v1/set/your_key?value=100
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Key set successfully"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"old_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100"&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;That's the whole API. There's also &lt;code&gt;/api/v1/status&lt;/code&gt; and &lt;code&gt;/health&lt;/code&gt; if you want to ping it for monitoring purposes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using it in practice
&lt;/h2&gt;

&lt;p&gt;Adding a page view counter to any site is maybe 5 lines of JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This page has been visited &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"visits"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; times.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&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://countapi.mileshilliard.com/api/v1/hit/my-site-homepage&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&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;visits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're on a platform that doesn't allow JavaScript at all (like certain Notion pages or some README renderers), there's even a tracking pixel approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://countapi.mileshilliard.com/api/v1/hit/my-site-visits"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:none;"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No JS required. The hit happens server-side when the image loads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migrating from countapi.xyz
&lt;/h2&gt;

&lt;p&gt;If you were using countapi.xyz, your old calls probably looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&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.countapi.xyz/hit/mysite.com/visits&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 new equivalent is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&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://countapi.mileshilliard.com/api/v1/hit/mysite-visits&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 main difference is there are no namespaces anymore, so you fold your old namespace and key together into one unique key. Everything else works the same way.&lt;/p&gt;




&lt;h2&gt;
  
  
  A few things worth knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;All keys are public.&lt;/strong&gt; Anyone who knows your key name can read or hit your counter. This is by design and is the tradeoff for having zero auth overhead. Just don't use it to store anything sensitive (not that a number really could be, but still).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keys don't expire.&lt;/strong&gt; If you want to "reset" a counter, use the &lt;code&gt;/set&lt;/code&gt; endpoint or just start using a new key name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is rate limiting&lt;/strong&gt;, though the docs describe it as "extremely generous." Someone apparently stress tested it without warning, which is how rate limiting got added in the first place. The docs have a great line about this: "Shoot me an email before you try to... if you wouldn't mind."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's open source.&lt;/strong&gt; The whole thing is on GitHub at &lt;a href="https://github.com/syntaxerror019/countapi" rel="noopener noreferrer"&gt;syntaxerror019/countapi&lt;/a&gt; if you want to self-host or contribute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I like it
&lt;/h2&gt;

&lt;p&gt;There are absolutely fancier analytics tools out there. But sometimes you really do just want a number that goes up. This API gets that. It doesn't ask you to create an account to track 12 page views on your personal blog. It doesn't require you to read 40 pages of docs. It just works.&lt;/p&gt;

&lt;p&gt;The fact that it's a community project filling a gap that a commercial service left behind makes it even better. If you end up using it, maybe give the repo a star or drop the maintainer a note. Small projects like this run on that kind of encouragement.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Site: &lt;a href="https://countapi.mileshilliard.com" rel="noopener noreferrer"&gt;countapi.mileshilliard.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/syntaxerror019/countapi" rel="noopener noreferrer"&gt;github.com/syntaxerror019/countapi&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Uptime dashboard: linked from the site nav&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
