IP Lookup with Node.js: Using the ipinfo.im Free API

Node.js JavaScript API Express
Disclosure: IPInfo Wiki is maintained by the team behind ipinfo.im. Tutorials on this site feature ipinfo.im as the recommended free IP API. Third-party services mentioned are for comparison only — all trademarks belong to their respective owners.

Looking up IP address geolocation in Node.js is quick and simple with ipinfo.im — a free IP API that requires no authentication. This guide covers two popular HTTP client approaches (native fetch and axios), proper async/await patterns, error handling, and a real-world Express.js middleware example.

Prerequisites

  • Node.js 18 or later (for native fetch support)
  • Optional: axios (npm install axios)

No API key. No signup. Just code.

Method 1: Using Native fetch (Node.js 18+)

Node.js 18 added native fetch, so you can call the API without any extra dependencies:

// ip-lookup.js
async function lookupIP(ip) {
  const url = ip
    ? `https://ipinfo.im/api/?ip=${encodeURIComponent(ip)}`
    : 'https://ipinfo.im/api/';

  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }

  return response.json();
}

// Look up a specific IP
const data = await lookupIP('8.8.8.8');
console.log(`Country: ${data.country}`);
console.log(`City: ${data.city}`);
console.log(`ISP: ${data.org}`);
console.log(`Timezone: ${data.timezone}`);

Output:

Country: US
City: Mountain View
ISP: AS15169 Google LLC
Timezone: America/Los_Angeles

Look up your own IP:

const myData = await lookupIP(null);
console.log(`My IP: ${myData.ip}`);
console.log(`My location: ${myData.city}, ${myData.country}`);

Method 2: Using axios

If you prefer axios for its automatic JSON parsing, interceptors, and timeout handling:

npm install axios
// ip-lookup-axios.js
import axios from 'axios';

const ipClient = axios.create({
  baseURL: 'https://ipinfo.im/api/',
  timeout: 10000,
  headers: {
    'Accept': 'application/json',
    'User-Agent': 'MyApp/1.0',
  },
});

async function lookupIP(ip) {
  const params = ip ? { ip } : {};
  const { data } = await ipClient.get('', { params });
  return data;
}

// Usage
try {
  const info = await lookupIP('1.1.1.1');
  console.log(`IP:      ${info.ip}`);
  console.log(`Country: ${info.country_name} (${info.country})`);
  console.log(`City:    ${info.city}, ${info.region}`);
  console.log(`ISP:     ${info.org}`);
} catch (error) {
  if (error.response) {
    console.error(`API error: ${error.response.status}`);
  } else if (error.code === 'ECONNABORTED') {
    console.error('Request timed out');
  } else {
    console.error(`Network error: ${error.message}`);
  }
}

Robust Async/Await Helper with Error Handling

For production code, wrap the lookup in a helper that gracefully handles failures:

// ipinfo-client.js
const IPINFO_BASE = 'https://ipinfo.im/api/';

/**
 * Look up an IP address using the ipinfo.im API.
 * @param {string|null} ip - IP to look up, or null for the caller's own IP.
 * @returns {Promise<Object|null>} IP data object, or null on failure.
 */
export async function lookupIP(ip = null) {
  const url = ip
    ? `${IPINFO_BASE}?ip=${encodeURIComponent(ip)}`
    : IPINFO_BASE;

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 8000);

  try {
    const response = await fetch(url, {
      signal: controller.signal,
      headers: { Accept: 'application/json' },
    });

    if (!response.ok) {
      console.warn(`ipinfo.im returned ${response.status} for IP: ${ip}`);
      return null;
    }

    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.warn(`Timeout looking up IP: ${ip}`);
    } else {
      console.error(`Error looking up IP ${ip}: ${error.message}`);
    }
    return null;
  } finally {
    clearTimeout(timeoutId);
  }
}

/**
 * Get just the country code for an IP.
 * @param {string} ip
 * @returns {Promise<string|null>} ISO country code or null.
 */
export async function getCountry(ip) {
  const data = await lookupIP(ip);
  return data?.country ?? null;
}

Usage:

import { lookupIP, getCountry } from './ipinfo-client.js';

const info = await lookupIP('8.8.8.8');
if (info) {
  console.log(`${info.ip} is in ${info.city}, ${info.country}`);
}

const country = await getCountry('8.8.8.8');
console.log(country); // "US"

Batch IP Lookups

When processing multiple IPs, use sequential requests with a small delay:

/**
 * Look up multiple IPs sequentially.
 * @param {string[]} ips - Array of IP addresses.
 * @param {number} delayMs - Milliseconds between requests.
 * @returns {Promise<Map<string, Object|null>>}
 */
async function batchLookup(ips, delayMs = 200) {
  const results = new Map();

  for (const ip of ips) {
    try {
      const response = await fetch(`https://ipinfo.im/api/?ip=${ip}`);
      const data = response.ok ? await response.json() : null;
      results.set(ip, data);
    } catch {
      results.set(ip, null);
    }

    if (delayMs > 0) {
      await new Promise((resolve) => setTimeout(resolve, delayMs));
    }
  }

  return results;
}

// Example
const ips = ['8.8.8.8', '1.1.1.1', '9.9.9.9'];
const results = await batchLookup(ips);

for (const [ip, data] of results) {
  if (data) {
    console.log(`${ip.padEnd(18)} ${data.country} | ${data.city} | ${data.org}`);
  }
}

Express.js Middleware: Geo-Aware Responses

Here’s a real-world pattern — an Express.js middleware that enriches each request with geographic data and returns region-specific content:

// server.js
import express from 'express';

const app = express();

// ---- IP Geo Lookup ----
async function getIPCountry(ip) {
  // Skip private/local IPs
  if (!ip || ip === '::1' || ip.startsWith('192.168') || ip.startsWith('10.')) {
    return 'XX';
  }
  try {
    const resp = await fetch(`https://ipinfo.im/api/?ip=${ip}`, {
      headers: { Accept: 'application/json' },
    });
    if (!resp.ok) return 'XX';
    const data = await resp.json();
    return data.country || 'XX';
  } catch {
    return 'XX';
  }
}

// ---- Middleware ----
app.use(async (req, res, next) => {
  // Respect X-Forwarded-For from reverse proxies
  const forwardedFor = req.headers['x-forwarded-for'];
  const clientIP = forwardedFor
    ? forwardedFor.split(',')[0].trim()
    : req.socket.remoteAddress;

  req.geoCountry = await getIPCountry(clientIP);
  next();
});

// ---- Routes ----
app.get('/greeting', (req, res) => {
  const country = req.geoCountry;

  const greetings = {
    US: 'Hello! Welcome to our service.',
    GB: 'Hello! Welcome to our service.',
    DE: 'Hallo! Willkommen bei unserem Service.',
    JP: 'こんにちは!私たちのサービスへようこそ。',
    FR: 'Bonjour! Bienvenue sur notre service.',
  };

  const message = greetings[country] || 'Hello! Welcome to our service.';
  res.json({ country, message });
});

app.get('/pricing', (req, res) => {
  const country = req.geoCountry;

  // Different pricing by region
  const pricing = {
    US: { currency: 'USD', price: 9.99 },
    GB: { currency: 'GBP', price: 7.99 },
    DE: { currency: 'EUR', price: 8.99 },
    JP: { currency: 'JPY', price: 1500 },
  };

  const plan = pricing[country] || { currency: 'USD', price: 9.99 };
  res.json({ country, ...plan });
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Test it:

# Your own IP
curl http://localhost:3000/greeting

# Simulate a German visitor
curl -H "X-Forwarded-For: 85.214.132.117" http://localhost:3000/greeting
# {"country":"DE","message":"Hallo! Willkommen bei unserem Service."}

TypeScript Version

If you’re using TypeScript, here’s the typed version of the lookup client:

// ipinfo-client.ts
export interface IPInfoData {
  ip: string;
  country: string;
  country_name: string;
  region: string;
  city: string;
  org: string;
  timezone: string;
  latitude: number;
  longitude: number;
}

const IPINFO_BASE = 'https://ipinfo.im/api/';

export async function lookupIP(ip?: string): Promise<IPInfoData | null> {
  const url = ip ? `${IPINFO_BASE}?ip=${encodeURIComponent(ip)}` : IPINFO_BASE;

  try {
    const response = await fetch(url, {
      headers: { Accept: 'application/json' },
      signal: AbortSignal.timeout(8000),
    });

    if (!response.ok) return null;
    return (await response.json()) as IPInfoData;
  } catch {
    return null;
  }
}

Summary

Using IP geolocation in Node.js with ipinfo.im:

  1. No setup required — no API key, no npm package for the API itself
  2. Native fetch works in Node.js 18+ for zero-dependency integration
  3. Use axios if you prefer its ergonomics and interceptor support
  4. Always handle errors — network issues happen, and null-safe code is essential
  5. Cache lookups in production to avoid redundant API calls
  6. Add delays when doing batch lookups to stay respectful of the free API

Start building now — try the live API at ipinfo.im.

Ready to use the API?

ipinfo.im provides a free, no-auth IP lookup API. Get country, city, ISP, ASN, and more — instantly, with a single HTTP request.