<?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: Koray KÖYLÜ</title>
    <description>The latest articles on DEV Community by Koray KÖYLÜ (@koraykoylu).</description>
    <link>https://dev.to/koraykoylu</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%2F3956907%2Fd9c2e429-29e5-437e-9d74-326c541b887f.jpg</url>
      <title>DEV Community: Koray KÖYLÜ</title>
      <link>https://dev.to/koraykoylu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/koraykoylu"/>
    <language>en</language>
    <item>
      <title>We built a free ipify alternative - here's what we learned</title>
      <dc:creator>Koray KÖYLÜ</dc:creator>
      <pubDate>Fri, 12 Jun 2026 20:23:26 +0000</pubDate>
      <link>https://dev.to/koraykoylu/we-built-a-free-ipify-alternative-heres-what-we-learned-39eb</link>
      <guid>https://dev.to/koraykoylu/we-built-a-free-ipify-alternative-heres-what-we-learned-39eb</guid>
      <description>&lt;p&gt;If you've ever needed your public IP in a script, you've probably typed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl api.ipify.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ipify is one of those quiet pieces of internet infrastructure. Launched in 2014, it answers one question — &lt;em&gt;"what is my public IP?"&lt;/em&gt; — so simply that it ended up embedded in millions of scripts and apps, serving billions of requests a month. Respect.&lt;/p&gt;

&lt;p&gt;But a while ago I noticed something while browsing its &lt;a href="https://github.com/rdegges/ipify-api" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;: the last code change was &lt;strong&gt;January 2018&lt;/strong&gt;. Community pull requests — CORS headers, Docker support, even one-line README fixes — have been sitting unanswered for years. The hosted service still runs (it's commercially operated by WhoisXML API), but the open-source project that made developers trust it is effectively frozen. And geolocation was never part of the free deal — it's a separate paid product.&lt;/p&gt;

&lt;p&gt;That gap looked like an invitation. We run &lt;a href="https://whatsmy.fyi" rel="noopener noreferrer"&gt;whatsmy.fyi&lt;/a&gt;, an IP and network info tool, and we decided to build the thing we wished existed. Here's what we shipped and — more usefully — what we learned along the way.&lt;/p&gt;

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

&lt;p&gt;A keyless endpoint that's deliberately compatible with ipify's response format:&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="c"&gt;# Plain text&lt;/span&gt;
curl https://whatsmy.fyi/ip
&lt;span class="c"&gt;# → 203.0.113.42&lt;/span&gt;

&lt;span class="c"&gt;# JSON — identical shape to api.ipify.org&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://whatsmy.fyi/ip?format=json"&lt;/span&gt;
&lt;span class="c"&gt;# → {"ip":"203.0.113.42"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swapping it into existing code is a one-URL change. No key, no signup, CORS enabled, IPv4 + IPv6, nothing logged.&lt;/p&gt;

&lt;p&gt;When you need more than the bare IP, a free API key (10,000 requests/day) returns city, country, coordinates, timezone, ASN, and ISP — the data ipify sells separately. There's also a &lt;a href="https://github.com/koraykoylu/whatsmyfyi-sdk" rel="noopener noreferrer"&gt;TypeScript SDK&lt;/a&gt; with a keyless mode:&lt;br&gt;
&lt;/p&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;WhatsMyFyi&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;@whatsmyfyi/sdk&lt;/span&gt;&lt;span class="dl"&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;WhatsMyFyi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;           &lt;span class="c1"&gt;// no key needed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ip&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="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;// "203.0.113.42"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lesson 1: friction decides everything in this category
&lt;/h2&gt;

&lt;p&gt;Our API originally required a key for everything. Reasonable, right? Usage tracking, rate limits, dashboards.&lt;/p&gt;

&lt;p&gt;Wrong — at least for this use case. The entire reason &lt;code&gt;curl api.ipify.org&lt;/code&gt; won the internet is that it works &lt;em&gt;before you've decided to care&lt;/em&gt;. A developer comparing IP APIs closes the tab the moment they see "sign up first." We realized the keyless endpoint wasn't a nice-to-have; it was the price of admission. The authenticated API now earns its keep as the &lt;em&gt;upgrade path&lt;/em&gt;, not the front door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: the edge changed the economics
&lt;/h2&gt;

&lt;p&gt;ipify's era required choices: serving geolocation means licensing a GeoIP database, updating it, and eating lookup latency. Charging for geo made sense in 2014.&lt;/p&gt;

&lt;p&gt;Running on Cloudflare Workers changed that math for us. Every request already arrives with geolocation, ASN, and timezone attached (the &lt;code&gt;request.cf&lt;/code&gt; object) — data from the network itself, with no external database call. Serving "rich" IP data now costs us roughly the same as serving the bare IP. That's not cleverness on our part; it's a structural shift that makes the old free-IP/paid-geo split feel dated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: honesty is a feature you can ship
&lt;/h2&gt;

&lt;p&gt;We published a &lt;a href="https://whatsmy.fyi/compare" rel="noopener noreferrer"&gt;comparison page&lt;/a&gt; covering ipify, icanhazip, ip-api.com, ipapi.co, and ipinfo — and we verified every claim by calling each API ourselves (ip-api.com really does reject HTTPS on the free tier; ipapi.co really did 429 our first request). The page also lists the places where competitors beat us, and recommends icanhazip for shell scripts and ipinfo for enterprise needs, because that's the truth.&lt;/p&gt;

&lt;p&gt;This felt risky to write. But developers can smell a rigged comparison table from a mile away; an honest one is rare enough to be memorable — and it gives every claim on the page a "verify it yourself" quality that no marketing copy can match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 4: maintenance is the moat
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable part: nothing we built is technically hard. A keyless IP endpoint is a weekend project. What ipify's story actually demonstrates is that the hard part is &lt;em&gt;still being there&lt;/em&gt; — answering issues, merging PRs, shipping releases — years after the launch excitement fades.&lt;/p&gt;

&lt;p&gt;So we wrote the only durable differentiator we could into our &lt;a href="https://github.com/koraykoylu/whatsmyfyi-sdk/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt;: every issue and PR gets a human response within a few days. If we ever stop honoring that, someone should build the whatsmy.fyi alternative — and they'd be right to.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Keyless endpoint: &lt;code&gt;curl https://whatsmy.fyi/ip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://whatsmy.fyi/docs" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; · &lt;a href="https://whatsmy.fyi/openapi.json" rel="noopener noreferrer"&gt;OpenAPI spec&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/koraykoylu/whatsmyfyi-sdk" rel="noopener noreferrer"&gt;SDK on GitHub&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/@whatsmyfyi/sdk" rel="noopener noreferrer"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://whatsmy.fyi/compare" rel="noopener noreferrer"&gt;The full comparison&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions, criticism, and "actually you got X wrong" comments are genuinely welcome — that's how the comparison page stays honest.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>api</category>
      <category>opensource</category>
    </item>
    <item>
      <title>IP geolocation with zero external APIs, the Cloudflare Workers cf object</title>
      <dc:creator>Koray KÖYLÜ</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:49:39 +0000</pubDate>
      <link>https://dev.to/koraykoylu/ip-geolocation-with-zero-external-apis-the-cloudflare-workers-cf-object-16e</link>
      <guid>https://dev.to/koraykoylu/ip-geolocation-with-zero-external-apis-the-cloudflare-workers-cf-object-16e</guid>
      <description>&lt;p&gt;When I built &lt;a href="https://whatsmy.fyi" rel="noopener noreferrer"&gt;whatsmy.fyi&lt;/a&gt;, I assumed I'd need a geolocation provider: MaxMind, ipinfo, ip-api, pick your poison. They all mean the same thing: an external dependency, an API key, a quota, added latency, and someone else's server seeing your users' IPs.&lt;/p&gt;

&lt;p&gt;Then I found out Cloudflare Workers makes the whole category unnecessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;cf&lt;/code&gt; object
&lt;/h2&gt;

&lt;p&gt;Every request that hits a Cloudflare Worker carries a &lt;code&gt;request.cf&lt;/code&gt; object, populated at the edge before your code even runs. No lookup, no latency, no key. Here's what's inside:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;asn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;34984&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;// ISP's autonomous system number&lt;/span&gt;
  &lt;span class="nx"&gt;asOrganization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Superonline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ISP name&lt;/span&gt;
  &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Istanbul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Istanbul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;continent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isEUCountry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// "1" if EU, undefined otherwise&lt;/span&gt;
  &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;41.01380&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// string, not number!&lt;/span&gt;
  &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;28.94970&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;postalCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;34000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Europe/Istanbul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;colo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;// which CF datacenter handled this&lt;/span&gt;
  &lt;span class="nx"&gt;clientTcpRtt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// user's RTT to the edge, in ms&lt;/span&gt;
  &lt;span class="nx"&gt;httpProtocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP/3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;tlsVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TLSv1.3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;tlsCipher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AEAD-AES128-GCM-SHA256&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;That last group surprised me most: you get the user's &lt;strong&gt;HTTP protocol, TLS version, and actual TCP round-trip time&lt;/strong&gt; for free. Try getting that from a geo API.&lt;/p&gt;

&lt;h2&gt;
  
  
  A complete IP endpoint in ~30 lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;cf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf&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;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="s2"&gt;CF-Connecting-IP&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;Response&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="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&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="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&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="na"&gt;isp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asOrganization&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="na"&gt;asn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asn&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="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&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="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&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="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&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="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;httpProtocol&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="na"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tlsVersion&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="na"&gt;rttMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientTcpRtt&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="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;That's the entire backend. No database, no GeoIP file to update monthly, no vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotchas (learned the hard way)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Coordinates are strings.&lt;/strong&gt; &lt;code&gt;latitude: "41.01380"&lt;/code&gt; — parse them or your JSON consumers will suffer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Every field can be missing.&lt;/strong&gt; Tor exits, some corporate proxies, and brand-new IP ranges come through with sparse data. Null-check everything:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;isEUCountry&lt;/code&gt; is the string &lt;code&gt;"1"&lt;/code&gt; or undefined.&lt;/strong&gt; Not a boolean. You've been warned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Local dev gives you an empty object.&lt;/strong&gt; &lt;code&gt;wrangler dev&lt;/code&gt; (and Next.js dev servers behind adapters) won't populate most fields. Guard for it, and test geo logic in preview deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. City-level accuracy is "usually right, sometimes hilarious."&lt;/strong&gt; Same as every IP geolocation source, this data comes from the same underlying registries. Country is reliable; city is best-effort. Don't build anything that &lt;em&gt;requires&lt;/em&gt; city precision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The privacy angle
&lt;/h2&gt;

&lt;p&gt;This architecture has a property I didn't fully appreciate until I wrote the privacy policy: &lt;strong&gt;the user's IP never leaves the request path.&lt;/strong&gt; There's no third-party geo provider receiving your traffic logs as a side effect. Combined with not writing logs yourself, you can honestly say: nothing is stored, nothing is shared. That's the whole privacy section.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://whatsmy.fyi" rel="noopener noreferrer"&gt;whatsmy.fyi&lt;/a&gt; this is the entire data pipeline: the site, the &lt;a href="https://whatsmy.fyi/docs" rel="noopener noreferrer"&gt;free API&lt;/a&gt;, all of it runs on &lt;code&gt;request.cf&lt;/code&gt; and nothing else. Sub-50ms TTFB, zero external calls, zero logs.&lt;/p&gt;

&lt;p&gt;If you're on Workers and calling a geo API today, check &lt;code&gt;request.cf&lt;/code&gt; first. The data was already there before you asked.&lt;/p&gt;

</description>
      <category>cloudflarechallenge</category>
      <category>serverless</category>
      <category>webdev</category>
      <category>privacy</category>
    </item>
    <item>
      <title>I built a "what is my IP" site because I was tired of the ugly ones</title>
      <dc:creator>Koray KÖYLÜ</dc:creator>
      <pubDate>Thu, 28 May 2026 15:39:14 +0000</pubDate>
      <link>https://dev.to/koraykoylu/i-built-a-what-is-my-ip-site-because-i-was-tired-of-the-ugly-ones-263j</link>
      <guid>https://dev.to/koraykoylu/i-built-a-what-is-my-ip-site-because-i-was-tired-of-the-ugly-ones-263j</guid>
      <description>&lt;p&gt;I use "what is my IP" sites maybe once a month. Every time I end up on something covered in ads, calling three different tracking APIs, and showing me results I don't fully understand. So I spent a weekend building whatsmy.fyi.&lt;/p&gt;

&lt;p&gt;The thing I didn't expect: you don't need an IP geolocation  API at all if you're on Cloudflare Workers. Every request  comes with a &lt;code&gt;cf&lt;/code&gt; object that already has your city, country,  ISP, TLS version, HTTP protocol, and RTT. Free. Zero latency.&lt;/p&gt;

&lt;p&gt;The part I enjoyed most was the WebRTC leak test. It checks  whether your browser is exposing your real IP through  RTCPeerConnection even when you're on a VPN. I ran it on  my own setup. It was leaking.&lt;/p&gt;

&lt;p&gt;Zero logs. Zero storage. Just your data, shown to you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://whatsmy.fyi" rel="noopener noreferrer"&gt;https://whatsmy.fyi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>cloudflare</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
