JavaScript Tutorial

JavaScript Geolocation API:
IP Lookup with Node.js

This tutorial shows how to use JavaScript to look up IP geolocation data — country, city, latitude, longitude, and timezone — using Node.js and the ip-api.io API. Since the API requires an API key, all lookups happen server-side. We cover Express.js middleware, Next.js API routes, and TypeScript types. Browser-side code should call your own backend, not ip-api.io directly.

What you'll build

  • No npm install — Node.js 18+ includes built-in fetch()
  • 4 lines to get country, city, latitude, longitude, and timezone
  • TypeScript interface included — full type safety, zero extra work
  • Express middleware and Next.js API route examples ready to copy

Prerequisites

  • Node.js 18 or higher (built-in fetch — no npm install required)
  • An ip-api.io API key — get one free
  • Your API key stored in an environment variable: IP_API_IO_KEY
ESM required for top-level await. The examples below use top-level await, which requires "type": "module" in your package.json, or save the file with a .mjs extension. If you prefer CommonJS, wrap the code in an async function and call it.
1

Quickstart: look up an IP address

Pass any IPv4 or IPv6 address to GET /api/v1/ip/{ip}. The response includes geolocation and security signals in a single call. Store your API key in an environment variable — never hardcode it.

const API_KEY = process.env.IP_API_IO_KEY;
const ip = "8.8.8.8";

const resp = await fetch(
  `https://ip-api.io/api/v1/ip/${ip}?api_key=${API_KEY}`,
  { signal: AbortSignal.timeout(5000) }
);
if (!resp.ok) throw new Error(`ip-api.io error: ${resp.status}`);

const data = await resp.json();
console.log(data.ip);                         // "8.8.8.8"
console.log(data.location.country);          // "United States"
console.log(data.location.city);             // "Mountain View"
console.log(data.location.latitude);         // 37.4056
console.log(data.location.longitude);        // -122.0775
console.log(data.location.timezone);         // "America/Los_Angeles"
console.log(data.location.local_time);       // "2024-05-20T15:16:52-07:00"
Auto-detect the caller's IP: omit the IP from the URL — GET /api/v1/ip/?api_key=YOUR_KEY — and the API resolves it from the incoming request. Useful when calling server-side with the client IP extracted from X-Forwarded-For.
Never call ip-api.io from browser JavaScript. Your API key would be visible in the browser's network tab — anyone who sees it can use your quota. All calls to ip-api.io must go through your server.
2

Detect VPN, proxy, Tor, and threats

Every response includes a suspicious_factors object with seven boolean flags. You get security intelligence in the same call, at no extra cost per lookup.

const data = await lookupIp("185.220.101.45"); // example Tor exit node

const sf = data.suspicious_factors;

if (sf.is_vpn || sf.is_proxy || sf.is_tor_node) {
  console.log("Anonymous or masked traffic — consider blocking or CAPTCHA");
}

if (sf.is_datacenter) {
  console.log("Cloud/datacenter IP — likely automated or bot traffic");
}

if (sf.is_threat) {
  console.log("Active threat signal — block this request");
}

// Combine signals for a risk decision
const isRisky = sf.is_vpn || sf.is_proxy || sf.is_tor_node || sf.is_threat;
console.log(`Risk flag: ${isRisky}`); // true
Tip: For a combined risk score with a VERY_LOW / LOW / MEDIUM / HIGH / VERY_HIGH risk level, use the Risk Score API at /api/v1/risk-score/{ip}.
3

TypeScript types

Copy this interface into your project to get full type safety on every field. No external type package needed.

interface SuspiciousFactors {
  is_vpn:        boolean;
  is_proxy:      boolean;
  is_tor_node:   boolean;
  is_datacenter: boolean;
  is_threat:     boolean;
  is_spam:       boolean;
  is_crawler:    boolean;
}

interface Location {
  country:            string | null;
  country_code:       string | null;
  city:               string | null;
  latitude:           number | null;
  longitude:          number | null;
  zip:                string | null;
  timezone:           string | null;
  local_time:         string | null;
  local_time_unix:    number | null;
  is_daylight_savings: boolean | null;
}

interface IpApiResponse {
  ip:                 string;
  suspicious_factors: SuspiciousFactors;
  location:           Location;
}

// Typed lookup function
async function lookupIp(ip: string): Promise<IpApiResponse> {
  const apiKey = process.env.IP_API_IO_KEY!;
  const resp = await fetch(
    `https://ip-api.io/api/v1/ip/${ip}?api_key=${apiKey}`,
    { signal: AbortSignal.timeout(5000) }
  );
  if (!resp.ok) throw new Error(`ip-api.io error: ${resp.status}`);
  return resp.json() as Promise<IpApiResponse>;
}
4

Express.js middleware

Attach IP intelligence to every request as req.ipIntel. The middleware never crashes — if ip-api.io is unavailable, it sets req.ipIntel = null and calls next(). Treat ip-api.io as a non-critical enrichment, not a hard dependency.

import express from "express";

const API_KEY = process.env.IP_API_IO_KEY;

// Extract the real client IP from X-Forwarded-For (set by reverse proxies).
// Only trust this header when behind a reverse proxy you control —
// an attacker can spoof it on a directly-exposed server.
function getClientIp(req) {
  const xff = req.headers["x-forwarded-for"];
  return xff ? xff.split(",")[0].trim() : req.socket.remoteAddress;
}

async function ipIntelMiddleware(req, res, next) {
  try {
    const ip = getClientIp(req);
    const resp = await fetch(
      `https://ip-api.io/api/v1/ip/${ip}?api_key=${API_KEY}`,
      { signal: AbortSignal.timeout(5000) }
    );
    req.ipIntel = resp.ok ? await resp.json() : null;
  } catch {
    req.ipIntel = null;
  }
  next();
}

const app = express();
app.use(ipIntelMiddleware);

app.get("/check", (req, res) => {
  const data = req.ipIntel;
  if (!data) return res.status(503).json({ error: "IP lookup unavailable" });

  res.json({
    ip:         data.ip,
    country:    data.location.country,
    is_vpn:     data.suspicious_factors.is_vpn,
    is_proxy:   data.suspicious_factors.is_proxy,
    is_tor:     data.suspicious_factors.is_tor_node,
    is_threat:  data.suspicious_factors.is_threat,
  });
});

app.listen(3000);
app.set('trust proxy', 1): Alternatively, add app.set('trust proxy', 1) to your Express app and use req.ip directly — Express will populate it from X-Forwarded-For automatically.
5

Next.js API route

Create a server-side API route so your frontend can call /api/ip-lookup on your own domain. The API key stays in environment variables and is never sent to the browser.

// app/api/ip-lookup/route.ts  (Next.js App Router)
import { NextRequest, NextResponse } from "next/server";

const API_KEY = process.env.IP_API_IO_KEY!;

export async function GET(request: NextRequest): Promise<NextResponse> {
  const xff = request.headers.get("x-forwarded-for");
  const clientIp = xff ? xff.split(",")[0].trim() : "127.0.0.1";

  try {
    const resp = await fetch(
      `https://ip-api.io/api/v1/ip/${clientIp}?api_key=${API_KEY}`,
      { signal: AbortSignal.timeout(5000) }
    );
    if (!resp.ok) {
      return NextResponse.json({ error: "IP lookup failed" }, { status: 502 });
    }
    const data = await resp.json();
    return NextResponse.json(data);
  } catch {
    return NextResponse.json({ error: "IP lookup unavailable" }, { status: 503 });
  }
}
Frontend usage: Your React components call fetch('/api/ip-lookup') — they never touch ip-api.io directly and never see the API key. Add export const runtime = 'edge' to run this route on Vercel Edge for lower latency.

API Response Reference

Every IP lookup returns a JSON object with three top-level keys. All location fields are nullable — they may be null for private ranges or unrecognized addresses.

GET /api/v1/ip/78.55.53.58?api_key=YOUR_KEY
{
  "ip": "78.55.53.58",
  "suspicious_factors": {
    "is_proxy":      false,
    "is_tor_node":   false,
    "is_spam":       false,
    "is_crawler":    false,
    "is_datacenter": false,
    "is_vpn":        false,
    "is_threat":     false
  },
  "location": {
    "country":           "Germany",
    "country_code":      "DE",
    "city":              "Berlin",
    "latitude":          52.5694,
    "longitude":         13.3753,
    "zip":               "13409",
    "timezone":          "Europe/Berlin",
    "local_time":        "2024-05-20T22:16:52+02:00",
    "local_time_unix":   1716236212,
    "is_daylight_savings": true
  }
}

suspicious_factors — security signals (all boolean)

Field Description
is_vpn VPN service, corporate gateway, or self-hosted VPN detected
is_proxy HTTP, HTTPS, or SOCKS proxy (~99.5% accuracy)
is_tor_node Tor exit node, relay, or bridge (updated in real time from Tor consensus)
is_datacenter Cloud provider (AWS, GCP, Azure), VPS, or hosting facility
is_threat Active security threat — malware C&C, botnet, or DDoS source
is_spam Associated with spam, phishing, or malware email campaigns
is_crawler Known web crawler, scraper, or bot (search engines, price monitors)

location — geographic and timezone data

Field Type Description
country String Full country name in English (ISO 3166-1, e.g. "Germany")
country_code String ISO 3166-1 alpha-2 code (e.g. "DE", "US", "GB")
city String City or municipality name (85–95% accuracy)
latitude Float Decimal degrees, WGS84 — ~50 km median accuracy radius
longitude Float Decimal degrees, WGS84 — ~50 km median accuracy radius
zip String Postal or ZIP code in country-specific format
timezone String IANA timezone identifier (e.g. "America/Los_Angeles")
local_time String Current local time in ISO 8601 with UTC offset
local_time_unix Integer Unix timestamp (seconds since epoch) in local timezone
is_daylight_savings Boolean True if the location is currently observing DST; null if not applicable

Top-level fields

Field Type Description
ip String Analyzed IP address in normalized format (IPv4 dotted decimal or compressed IPv6)
suspicious_factors Object Seven boolean security signals — always present, never null
location Object Geographic and timezone data — fields may be null for private/unrecognized IPs

Frequently asked questions

Common questions about using the JavaScript and Node.js geolocation API.

Can I call ip-api.io from browser JavaScript?

No. Calling ip-api.io directly from the browser would expose your API key in the browser's network tab — anyone who sees it can use your quota. All calls must go through your server.

The correct pattern: your frontend calls /api/ip-lookup (or any path you choose) on your own domain. Your server calls ip-api.io with the API key from an environment variable and returns the result. See the Next.js example and Express middleware above.

Do I need to install an npm package?

No. Node.js 18 and higher ships with a built-in fetch() function — the same API as the browser. No npm install required.

If you are on Node.js 16, install node-fetch as a drop-in replacement: npm install node-fetch. For HTTP clients like axios, the same endpoint and query-param pattern works — just replace fetch with axios.get(url).

How do I get the real client IP address in Express.js?

Behind a reverse proxy (nginx, Cloudflare, AWS ALB), the real client IP is in the X-Forwarded-For header, not req.socket.remoteAddress. Extract it manually:

const xff = req.headers["x-forwarded-for"];
const clientIp = xff ? xff.split(",")[0].trim() : req.socket.remoteAddress;

Or add app.set('trust proxy', 1) to your Express app — Express will populate req.ip from X-Forwarded-For automatically. Only enable this when you are actually behind a trusted reverse proxy.

Does the API support TypeScript?

Yes. The API returns standard JSON, and you can define TypeScript interfaces for full type safety. Copy the IpApiResponse interface from Step 3 into your project — it covers suspicious_factors, location, and all nested fields with correct nullability.

How do I handle API errors and timeouts?

Use AbortSignal.timeout(5000) to set a 5-second timeout. Always check resp.ok before calling .json(). Wrap the whole call in try/catch to handle network failures:

try {
  const resp = await fetch(url, { signal: AbortSignal.timeout(5000) });
  if (!resp.ok) { /* handle HTTP error */ }
  const data = await resp.json();
} catch (err) {
  // TimeoutError, TypeError (network fail), or any fetch error
  // Proceed without IP intelligence — treat it as non-critical
}

In production, treat ip-api.io as a non-critical enrichment. If the request fails, log the error and let the user through — never block a request because of a failed IP lookup.

Can I use this with Fastify, Hono, or Koa?

Yes. The Express middleware pattern translates directly to other frameworks:

  • Fastify: use fastify.addHook('onRequest', ...)
  • Hono: use app.use('*', async (c, next) => { ... })
  • Koa: use app.use(async (ctx, next) => { ... })

In each case, call ip-api.io at the start of the middleware, attach the result to the context or request object, and call next(). Always catch errors and continue without crashing.

How do I use IP geolocation in a Next.js app?

Create a server-side API route. In the App Router, create app/api/ip-lookup/route.ts and use the Request object to read the x-forwarded-for header. See the full example in Step 5.

Your React components call fetch('/api/ip-lookup') — they never touch ip-api.io directly. The API key lives in .env.local as IP_API_IO_KEY and is never sent to the browser.

Pricing

Start using IP-API.io to make your website safer and more user-friendly. Keep out unwanted bots, show visitors content that's relevant to where they are, and spot risky IP addresses quickly. It's perfect for making online shopping more personal and keeping your site secure. Get started today with one of the plans!

Small

€10 /mo
100,000 geo ip requests
10,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption

Medium

€20 /mo
300,000 geo ip requests
25,000 advanced email validation requests
Location data
Email validation
Risk score calculation
Currency data
Time zone data
Threat data
Unlimited support
HTTPS encryption
Note: Your API key will be sent to your email after the subscription is confirmed.
From our blog

How to Detect User Location Using the ip-api.io API

A step-by-step guide to retrieving IP geolocation data and integrating location detection into your JavaScript application.

Need support?

Explore how IP-API.io can enhance your security, provide robust bot protection, and improve IP geolocation accuracy for your applications.

Contact Support

Need more queries?

Customize your experience with tailored plans that fit your IP security and geolocation needs.

Email Us