<?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: adado12</title>
    <description>The latest articles on DEV Community by adado12 (@adado_2e958757fa4dbf).</description>
    <link>https://dev.to/adado_2e958757fa4dbf</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%2F4140055%2F71725392-1ede-402f-8c5c-1ece2e9957b5.jpg</url>
      <title>DEV Community: adado12</title>
      <link>https://dev.to/adado_2e958757fa4dbf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adado_2e958757fa4dbf"/>
    <language>en</language>
    <item>
      <title>What If Your AI Agent Never Left the Browser?</title>
      <dc:creator>adado12</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:39:27 +0000</pubDate>
      <link>https://dev.to/adado_2e958757fa4dbf/6-free-public-apis-every-developer-should-know-38d3</link>
      <guid>https://dev.to/adado_2e958757fa4dbf/6-free-public-apis-every-developer-should-know-38d3</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every AI tool I use today has the same problem: it lives in a different tab.

I copy text from my editor. I switch to ChatGPT or Claude. I paste. I wait. I copy the answer. I switch back. I paste again. By the time I am done, I have lost the thread of what I was working on.

This is not a small annoyance. It is the single biggest tax on my productivity as a developer.

So here is a question that has been stuck in my head for months: what if the AI never left the browser in the first place?

## The Problem With the Current Model

Think about how you actually use an AI assistant today.

You are reading documentation. You want to understand a specific function. You highlight it, open a new tab, paste it into a chat, read the answer, go back, and try to find where you were.

You are filling out a long form. You want a summary of the terms. You copy the text, switch to an AI, ask for a summary, then manually type the important parts back into the form.

You are debugging an error. You open the console, copy the stack trace, switch tabs, paste, get an answer, come back, apply the fix.

Every one of these flows has the same hidden cost: context switching. Studies have shown that after a context switch, it takes an average of 23 minutes to return to the same level of focus. Even small switches add up.

The AI itself is not the bottleneck. The interface is.

## What a Browser-Native Agent Looks Like

Imagine this instead.

You are reading a page. You highlight a paragraph. A small assistant panel appears in the corner of the screen and explains it in plain language. No tab switch. No copy-paste. The explanation appears next to the text you were reading.

You open a form with 40 fields. The agent reads the context of the page and pre-fills the obvious ones. You review and approve. Done.

You are on a foreign-language page. The agent offers to translate. The translation appears inline.

You are searching for something in a 50,000-word document. You ask the agent a question. It answers using only the content of that page. No hallucinations from the wider internet. Just the text in front of you.

The agent is not a separate destination. It is a layer that sits on top of whatever you are already doing.

## The Technology Is Already Here

This is not science fiction. Every piece needed for this exists today.

Chrome ships with Gemini Nano built in. It runs locally, no internet required.

WebGPU gives the browser direct access to the GPU for running models at usable speeds.

WebLLM and Transformers.js let you run small open-source models entirely in JavaScript, inside the browser, without a server.

The Chrome Prompt API is being developed specifically to let web pages talk to a local AI model.

Small models, in the 3 to 7 billion parameter range, already run acceptably in the browser on modern hardware. They are not as capable as GPT-4 or Claude. But for summarization, translation, form filling, and question answering over a single page, they are more than enough.

The technical barriers are mostly solved. The real barriers are elsewhere.

## The Real Problem: Control

Here is where things get uncomfortable.

If one browser vendor owns the AI layer that sits inside every page you visit, they own how you experience the entire web.

They see every page you read. They see every form you fill. They see every search you run. They see every email you draft.

This is not a small amount of power. It is arguably more than any search engine has ever had, because a search engine sees what you look for. A browser agent sees what you do.

Google, Microsoft, and Apple are all racing to build this layer. Each of them will build it in a way that benefits their own ecosystem first.

The question is not whether this will happen. It is who will control it, and under what rules.

## The Permission Problem

There is a second issue that worries me even more: actions.

An agent that can read a page is helpful. An agent that can click buttons, type into fields, and submit forms is a completely different threat model.

Imagine an agent that fills out a purchase form on your behalf. What stops it from completing the purchase? What stops it from signing you up for something? What stops it from sending a message in your name?

Every action needs explicit, granular permission. Not a blanket "allow this site to use AI." Real permissions, per action, with clear consent, every time.

The current permission systems in browsers were not designed for this. They were built for things like camera access and location. They are not sufficient for an agent that can take arbitrary actions on your behalf.

This is the hard problem. Not running the model. Not making it fast. Making it safe.

## What I Would Like to See

I do not want a browser AI that only works in Chrome, only works if you are logged in, and only works if you accept a 12-page terms of service.

I want:

Open-source agents. The model, the runtime, and the permission system should all be auditable.

Local-first execution. The agent should work without an internet connection. Cloud models should be an opt-in upgrade, not the default.

Per-action permissions. Every action the agent takes should require explicit approval, at least by default. Users should be able to build trust over time, not hand over the keys on day one.

No telemetry by default. If the agent sees everything I do, I should at minimum be able to verify that it is not sending any of it anywhere.

The technology to build this exists today. What is missing is the will from the companies who stand to gain the most from the opposite approach.

## Where This Is Going

Over the next two years, I expect three things to happen.

First, browser-native AI will become standard. Every major browser will ship with some form of local model. Some will be better than others, but all will exist.

Second, the permission systems will be a mess. Early versions will be too permissive. There will be incidents. Regulators will get involved. The final design will be shaped by mistakes, not by good planning.

Third, open-source alternatives will emerge. They will be slower and less polished, but they will exist, and they will be the only option for people who care about privacy and control.

The question for developers is simple: when this layer becomes standard, what do you want it to look like? Do you want to build on top of a proprietary agent that you cannot inspect? Or do you want to build on something open that you can shape?

I know which one I would pick.

What is your take? Is a browser-native agent something you would actually use, or does the privacy tradeoff kill it before it starts?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>api</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>6 Free Public APIs Every Developer Should Know</title>
      <dc:creator>adado12</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:31:54 +0000</pubDate>
      <link>https://dev.to/adado_2e958757fa4dbf/6-free-public-apis-every-developer-should-know-3b4l</link>
      <guid>https://dev.to/adado_2e958757fa4dbf/6-free-public-apis-every-developer-should-know-3b4l</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Every&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;side&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;project,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;myself&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reaching&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;handful&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;free&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;APIs.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;They&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;API&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;generous&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;limits,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tasks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;would&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;take&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;days&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scratch.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;regularly.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;All&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;them&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;free,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;HTTPS,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;work&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;directly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;browser.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;IPify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Client's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;IP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Address&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;problem:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;visitor's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;IP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JavaScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;alone.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;browser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;doesn't&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;expose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;IPify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;solves&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;endpoint:&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://api.ipify.org?format=json" rel="noopener noreferrer"&gt;https://api.ipify.org?format=json&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
It returns:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
json&lt;br&gt;
{ "ip": "203.0.113.47" }&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
If you need IPv6, use `api64.ipify.org` instead. It returns IPv6 when the client supports it, and falls back to IPv4 otherwise.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
fetch('&lt;a href="https://api.ipify.org?format=json'" rel="noopener noreferrer"&gt;https://api.ipify.org?format=json'&lt;/a&gt;)&lt;br&gt;
  .then(res =&amp;gt; res.json())&lt;br&gt;
  .then(data =&amp;gt; console.log('Your IP:', data.ip));&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
What I use it for: analytics dashboards, security logging, and any time I need to identify a connection's network origin.

## 2. ipapi.co - IP Geolocation

Once you have the IP, ipapi.co tells you roughly where it is:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
&lt;a href="https://ipapi.co/json/" rel="noopener noreferrer"&gt;https://ipapi.co/json/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
The response includes city, region, country, timezone, coordinates, and the ISP:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
json&lt;br&gt;
{&lt;br&gt;
  "ip": "203.0.113.47",&lt;br&gt;
  "city": "London",&lt;br&gt;
  "region": "England",&lt;br&gt;
  "country_name": "United Kingdom",&lt;br&gt;
  "latitude": 51.5085,&lt;br&gt;
  "longitude": -0.1257,&lt;br&gt;
  "org": "Example ISP"&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
The free tier allows 1,000 requests per day without an API key.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
fetch('&lt;a href="https://ipapi.co/json/'" rel="noopener noreferrer"&gt;https://ipapi.co/json/'&lt;/a&gt;)&lt;br&gt;
  .then(res =&amp;gt; res.json())&lt;br&gt;
  .then(data =&amp;gt; {&lt;br&gt;
    console.log(&lt;code&gt;You appear to be in ${data.city}, ${data.country_name}&lt;/code&gt;);&lt;br&gt;
  });&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
One important note: IP geolocation is approximate. It might point to a data center 200 km away. Never present these coordinates as the user's exact location.

## 3. Have I Been Pwned - Password Breach Check

This one is brilliant. HIBP lets you check if a password has appeared in known data breaches, using a technique called k-anonymity.

Here is how it works. You hash the password with SHA-1 in the browser. Then you send only the first 5 characters of the hash to the API. The API returns all hashes that share that prefix, and you check locally if the full hash is in the list.

The password never leaves the browser. The server never sees the full hash.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
async function checkPassword(password) {&lt;br&gt;
  const buffer = new TextEncoder().encode(password);&lt;br&gt;
  const hashBuffer = await crypto.subtle.digest('SHA-1', buffer);&lt;br&gt;
  const hash = Array.from(new Uint8Array(hashBuffer))&lt;br&gt;
    .map(b =&amp;gt; b.toString(16).padStart(2, '0'))&lt;br&gt;
    .join('')&lt;br&gt;
    .toUpperCase();&lt;/p&gt;

&lt;p&gt;const prefix = hash.substring(0, 5);&lt;br&gt;
  const suffix = hash.substring(5);&lt;/p&gt;

&lt;p&gt;const res = await fetch(&lt;code&gt;https://api.pwnedpasswords.com/range/${prefix}&lt;/code&gt;);&lt;br&gt;
  const text = await res.text();&lt;/p&gt;

&lt;p&gt;const isBreached = text.split('\n').some(line =&amp;gt; {&lt;br&gt;
    const [hashSuffix, count] = line.split(':');&lt;br&gt;
    return hashSuffix === suffix;&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;return isBreached;&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
No API key required. No rate limit issues. And it is genuinely useful for your users.

## 4. Cloudflare Speed Test - Measure Connection Speed

Most speed test APIs require an API key or have strict rate limits. Cloudflare's endpoints do not.

Download test:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
&lt;a href="https://speed.cloudflare.com/__down?bytes=25000000" rel="noopener noreferrer"&gt;https://speed.cloudflare.com/__down?bytes=25000000&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Upload test (POST):

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
&lt;a href="https://speed.cloudflare.com/__up" rel="noopener noreferrer"&gt;https://speed.cloudflare.com/__up&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
You measure the time it takes to download or upload a known amount of data, then calculate Mbps:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
async function testDownload() {&lt;br&gt;
  const bytes = 25 * 1024 * 1024;&lt;br&gt;
  const start = performance.now();&lt;br&gt;
  const res = await fetch(&lt;code&gt;https://speed.cloudflare.com/__down?bytes=${bytes}&lt;/code&gt;);&lt;br&gt;
  const blob = await res.blob();&lt;br&gt;
  const seconds = (performance.now() - start) / 1000;&lt;br&gt;
  return (blob.size * 8) / seconds / 1_000_000;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;testDownload().then(mbps =&amp;gt; {&lt;br&gt;
  console.log(&lt;code&gt;Download speed: ${mbps.toFixed(2)} Mbps&lt;/code&gt;);&lt;br&gt;
});&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
For latency, send a few small requests and take the median, not the average. One slow request should not skew the whole result.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
async function testLatency() {&lt;br&gt;
  const samples = [];&lt;br&gt;
  for (let i = 0; i &amp;lt; 5; i++) {&lt;br&gt;
    const start = performance.now();&lt;br&gt;
    await fetch('&lt;a href="https://speed.cloudflare.com/__down?bytes=0'" rel="noopener noreferrer"&gt;https://speed.cloudflare.com/__down?bytes=0'&lt;/a&gt;);&lt;br&gt;
    samples.push(performance.now() - start);&lt;br&gt;
  }&lt;br&gt;
  samples.sort((a, b) =&amp;gt; a - b);&lt;br&gt;
  return samples[Math.floor(samples.length / 2)];&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## 5. DNS over HTTPS - Resolve Domain Names

Google runs a public DNS resolver that speaks JSON over HTTPS:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
&lt;a href="https://dns.google/resolve?name=example.com&amp;amp;type=A" rel="noopener noreferrer"&gt;https://dns.google/resolve?name=example.com&amp;amp;type=A&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
It returns the same answer you would get from a DNS server, in a clean JSON format:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
json&lt;br&gt;
{&lt;br&gt;
  "Status": 0,&lt;br&gt;
  "Answer": [&lt;br&gt;
    { "name": "example.com", "type": 1, "TTL": 300, "data": "93.184.216.34" }&lt;br&gt;
  ]&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
You can query A, AAAA, MX, TXT, NS, CNAME, and more. No key, no limits that matter, and it works from the browser.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
async function lookupDNS(domain, type = 'A') {&lt;br&gt;
  const url = &lt;code&gt;https://dns.google/resolve?name=${domain}&amp;amp;type=${type}&lt;/code&gt;;&lt;br&gt;
  const res = await fetch(url);&lt;br&gt;
  const data = await res.json();&lt;br&gt;
  return data.Answer || [];&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;lookupDNS('example.com', 'A').then(records =&amp;gt; {&lt;br&gt;
  records.forEach(r =&amp;gt; console.log(r.data));&lt;br&gt;
});&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
I use this for domain lookup tools, SSL certificate checks, and verifying DNS propagation after a migration.

## 6. OpenStreetMap Tiles - Free Maps

If you have ever built a map and discovered that Google Maps charges per request, this one is for you.

OpenStreetMap provides free map tiles:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
&lt;a href="https://tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png" rel="noopener noreferrer"&gt;https://tile.openstreetmap.org/{z}/{x}/{y}.png&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Use them with Leaflet.js:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
const map = L.map('map').setView([51.505, -0.09], 13);&lt;br&gt;
L.tileLayer('&lt;a href="https://tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png" rel="noopener noreferrer"&gt;https://tile.openstreetmap.org/{z}/{x}/{y}.png&lt;/a&gt;', {&lt;br&gt;
  attribution: '© OpenStreetMap contributors'&lt;br&gt;
}).addTo(map);&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
There is a usage policy you should respect: no more than a few requests per second, and always credit OpenStreetMap. If you need higher volume, consider a commercial tile provider like MapTiler or Stadia Maps.

### One Alternative: CARTO Basemaps

If you want a cleaner look, CARTO offers free basemaps:

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
They are faster than OpenStreetMap's default tiles in many regions and look more modern.

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

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {&lt;br&gt;
  maxZoom: 20,&lt;br&gt;
  subdomains: 'abcd',&lt;br&gt;
  attribution: '© OpenStreetMap contributors © CARTO'&lt;br&gt;
}).addTo(map);&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## A Few Things to Keep in Mind

Always use HTTPS. Every API above supports it. Browsers will block mixed-content requests if you mix HTTP and HTTPS.

Always have a fallback. Public APIs go down. Wrap every fetch call in a try-catch and show a friendly message if something fails.

Never expose secrets in client-side code. None of the APIs above require a key, which is why they are safe to call from the browser. If an API requires a secret key, proxy it through your own backend.

Respect rate limits. Even free APIs have limits. Cache results where possible, and do not hammer the endpoints.

That is my list. What free APIs do you reach for most often? I am always looking for new ones to add to my toolkit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>api</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Free IP Lookup Tool with Vanilla JS - Here's What I Learned</title>
      <dc:creator>adado12</dc:creator>
      <pubDate>Thu, 24 Sep 2026 21:02:30 +0000</pubDate>
      <link>https://dev.to/adado_2e958757fa4dbf/i-built-a-free-ip-lookup-tool-with-vanilla-js-heres-what-i-learned-1pkp</link>
      <guid>https://dev.to/adado_2e958757fa4dbf/i-built-a-free-ip-lookup-tool-with-vanilla-js-heres-what-i-learned-1pkp</guid>
      <description></description>
    </item>
    <item>
      <title>I Built a Free IP Lookup Tool with Vanilla JS — Here's What I Learned</title>
      <dc:creator>adado12</dc:creator>
      <pubDate>Thu, 24 Sep 2026 20:39:38 +0000</pubDate>
      <link>https://dev.to/adado_2e958757fa4dbf/i-built-a-free-ip-lookup-tool-with-vanilla-js-heres-what-i-learned-3h5j</link>
      <guid>https://dev.to/adado_2e958757fa4dbf/i-built-a-free-ip-lookup-tool-with-vanilla-js-heres-what-i-learned-3h5j</guid>
      <description>&lt;p&gt;A few months ago, I got tired of visiting IP lookup sites that were bloated with ads, required sign-ups, or gave vague answers about what an IP address actually reveals.&lt;/p&gt;

&lt;p&gt;So I built my own: IPCheck.app - a single-page tool that shows your public IPv4/IPv6, ISP, approximate location, and includes a few extra network utilities.&lt;/p&gt;

&lt;p&gt;This post isn't a sales pitch. It's an honest breakdown of what I built, the technical decisions I made, and the parts that were harder than I expected.&lt;/p&gt;

&lt;p&gt;The Stack (Spoiler: No Framework)&lt;/p&gt;

&lt;p&gt;I made a deliberate choice to avoid React, Vue, or any build tools. Why?&lt;/p&gt;

&lt;p&gt;First, hosting simplicity. The site is a single index.html file. No build step, no npm install, no CI/CD.&lt;/p&gt;

&lt;p&gt;Second, load speed. No framework overhead means the page renders almost instantly, even on slow connections.&lt;/p&gt;

&lt;p&gt;Third, longevity. Vanilla JS from 2024 will still work in 2034. A React app from 2019 might not.&lt;/p&gt;

&lt;p&gt;External dependencies are minimal and loaded from CDNs. Leaflet.js for the interactive map. Font Awesome for icons. Inter font from Google Fonts.&lt;/p&gt;

&lt;p&gt;That's it. Everything else is hand-written.&lt;/p&gt;

&lt;p&gt;The IP Detection Logic&lt;/p&gt;

&lt;p&gt;This was trickier than I expected. Here's the core problem: you cannot get your own public IP address from JavaScript alone. The browser doesn't expose it. You have to ask an external server.&lt;/p&gt;

&lt;p&gt;I ended up using three endpoints to handle edge cases. ipapi.co for primary geolocation. ipwho.is as a fallback. And two ipify endpoints for separate IPv4 and IPv6 detection.&lt;/p&gt;

&lt;p&gt;The IPv6 Problem&lt;/p&gt;

&lt;p&gt;Here's something most tutorials don't tell you: api.ipify.org and api64.ipify.org behave differently.&lt;/p&gt;

&lt;p&gt;api.ipify.org resolves to an IPv4-only endpoint. It will never return IPv6.&lt;/p&gt;

&lt;p&gt;api64.ipify.org returns IPv6 if your network supports it, otherwise falls back to IPv4.&lt;/p&gt;

&lt;p&gt;So to detect both addresses on a dual-stack connection, you need both endpoints. I learned this the hard way when my IPv6 address field kept showing the same IPv4 from the main API.&lt;/p&gt;

&lt;p&gt;The Map: Approximate, Not Exact&lt;/p&gt;

&lt;p&gt;The map uses Leaflet with CARTO tiles. It plots a marker at the coordinates returned by the IP API.&lt;/p&gt;

&lt;p&gt;But here's the thing I had to be careful about: IP geolocation is not GPS. The coordinates might point to a data center 200 km away, or to your ISP's regional office. Mobile and VPN connections can be off by hundreds of kilometers.&lt;/p&gt;

&lt;p&gt;So I made two deliberate choices.&lt;/p&gt;

&lt;p&gt;First, I added a circle with a 25 km radius around the marker as a visual cue that the location is approximate.&lt;/p&gt;

&lt;p&gt;Second, I wrote the disclaimer directly into the popup, so no way to miss it.&lt;/p&gt;

&lt;p&gt;It's not just honest, it's better UX. Users who understand the limitation don't get frustrated when the map isn't precise.&lt;/p&gt;

&lt;p&gt;The Speed Test (Cloudflare Endpoints)&lt;/p&gt;

&lt;p&gt;This one surprised me. Cloudflare exposes public test endpoints that anyone can hit. I use these to measure download, upload, and latency, with no API key required.&lt;/p&gt;

&lt;p&gt;For latency, I send 5 small requests and take the median, not the average, because one slow request shouldn't ruin the whole result.&lt;/p&gt;

&lt;p&gt;The Password Breach Checker (k-Anonymity)&lt;/p&gt;

&lt;p&gt;This is the tool I'm most proud of, because it's a beautiful example of privacy-preserving design.&lt;/p&gt;

&lt;p&gt;Here's how it works. The user types a password. I hash it with SHA-1 in the browser using the Web Crypto API. I take the first 5 characters of the hash and send only those to the Have I Been Pwned API. The API returns hundreds of hash suffixes that share the same prefix. Then I check locally if the user's full hash is in that list.&lt;/p&gt;

&lt;p&gt;The password never leaves the browser. The server never sees the full hash.&lt;/p&gt;

&lt;p&gt;If you're building anything that handles sensitive data, k-anonymity is a pattern worth learning.&lt;/p&gt;

&lt;p&gt;Things That Were Harder Than Expected&lt;/p&gt;

&lt;p&gt;First, Blogger's XML parser is not HTML.&lt;/p&gt;

&lt;p&gt;I initially deployed to Blogger, thinking it would be easy. It wasn't. Blogger parses templates as strict XML, which means every meta tag must be self-closed. Every ampersand must be escaped as and-amp-semicolon, even in URLs. Named entities like copy-semicolon are not defined in XML, you must use numeric codes. And JavaScript must be wrapped in CDATA.&lt;/p&gt;

&lt;p&gt;I spent three days debugging issues that a static HTML host would never have.&lt;/p&gt;

&lt;p&gt;The lesson: if you're deploying a custom-built page, don't use a blogging platform. Use Netlify, Vercel, Cloudflare Pages, or just plain shared hosting. The technical debt isn't worth the free hosting.&lt;/p&gt;

&lt;p&gt;Second, IP geolocation accuracy is unpredictable.&lt;/p&gt;

&lt;p&gt;I tested the site from 4 different networks: home fiber, mobile 4G, corporate WiFi, and VPN. The results ranged from spot-on city to wrong country. Mobile networks were the worst. One 4G test showed my location 800 km away.&lt;/p&gt;

&lt;p&gt;There's no fix for this. You have to design for inaccuracy, which is why I added the disclaimer, the circle on the map, and the approximate label everywhere.&lt;/p&gt;

&lt;p&gt;Third, speed test results vary wildly.&lt;/p&gt;

&lt;p&gt;The same 25 MB download can return 47 Mbps or 190 Mbps depending on time of day, Cloudflare edge server proximity, WiFi signal strength, and background processes.&lt;/p&gt;

&lt;p&gt;I now label the results as approximate and warn users that the numbers aren't comparable to dedicated speed test services like Speedtest.net.&lt;/p&gt;

&lt;p&gt;What I'd Do Differently&lt;/p&gt;

&lt;p&gt;If I started over, I would skip Blogger entirely. A static HTML file on Netlify would have saved me a week.&lt;/p&gt;

&lt;p&gt;I would add the geolocation disclaimer from day one. I added it after user confusion, and it should have been there from the start.&lt;/p&gt;

&lt;p&gt;And I would test on mobile earlier. I built for desktop first, then had to rework the layout.&lt;/p&gt;

&lt;p&gt;Try It, Tell Me What Breaks&lt;/p&gt;

&lt;p&gt;The site is live at ipcheck.app. All four tools are free, no sign-up, no tracking beyond what's technically required.&lt;/p&gt;

&lt;p&gt;If you find a bug, or if a tool doesn't work on your network, I'd love to hear about it. The best feedback I've gotten so far came from a comment saying your IPv6 detection is wrong on a dual-stack connection, which led me to rewrite the detection logic.&lt;/p&gt;

&lt;p&gt;What's your experience with IP geolocation APIs? Have you found one that's more accurate than the others? I'm always looking for better data sources.&lt;/p&gt;

&lt;p&gt;Building in public. If you're working on something similar, drop a link in the comments, I'd love to try it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
