DEV Community

ApogeoAPI
ApogeoAPI

Posted on • Originally published at apogeoapi.com

How to Detect User Country from IP in Node.js (2026)

Detecting a user's country from their IP address is the fastest way to localize your app — no consent forms, no user input. Here's how to do it reliably in Node.js.

Why Detect Country from IP?

  • Show prices in the user's local currency automatically
  • Pre-select country in forms and phone number inputs
  • Enforce geo-restrictions for compliance
  • Customize content by region without asking

Basic Express.js Example

import express from 'express';
const app = express();
app.get('/api/session', async (req, res) => {
const ip = req.ip ?? req.socket.remoteAddress ?? '8.8.8.8';
const geoRes = await fetch(
`https://api.apogeoapi.com/v1/geolocate/${ip}`,
{ headers: { 'X-API-Key': process.env.APOGEO_API_KEY! } }
);
const geo = await geoRes.json();
// { country_code: 'DE', country_name: 'Germany', city: 'Berlin',
//   timezone: 'Europe/Berlin', currency: 'EUR', latitude: 52.5, longitude: 13.4 }
res.json({ country: geo.country_code, currency: geo.currency, timezone: geo.timezone });
});
Enter fullscreen mode Exit fullscreen mode

Getting the Real IP Behind a Proxy

In production, your app sits behind a reverse proxy (nginx, Cloudflare, load balancer). The raw IP will be the proxy's — not the user's. Use the X-Forwarded-For header:

function getClientIp(req: express.Request): string {
const forwarded = req.headers['x-forwarded-for'];
if (typeof forwarded === 'string') {
return forwarded.split(',')[0].trim(); // First IP is the original client
}
return req.socket.remoteAddress ?? '8.8.8.8';
}
Enter fullscreen mode Exit fullscreen mode

If you're behind Cloudflare, use CF-Connecting-IP header instead — it's more reliable.

Next.js Middleware / Edge Function

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export async function middleware(req: NextRequest) {
const ip = req.headers.get('x-forwarded-for')?.split(',')[0] ?? '8.8.8.8';
const geo = await fetch(
`https://api.apogeoapi.com/v1/geolocate/${ip}`,
{ headers: { 'X-API-Key': process.env.APOGEO_API_KEY! } }
).then(r => r.json());
const res = NextResponse.next();
res.cookies.set('geo_country', geo.country_code, { maxAge: 86400 });
res.cookies.set('geo_currency', geo.currency, { maxAge: 86400 });
return res;
}
export const config = { matcher: ['/((?!_next|api).*)'] };
Enter fullscreen mode Exit fullscreen mode

What the Response Includes

  • country_code — ISO2 code (e.g. "DE")
  • country_name — Full name (e.g. "Germany")
  • region — State or region name
  • city — City name
  • latitude, longitude — Coordinates
  • timezone — IANA timezone string (e.g. "Europe/Berlin")
  • currency — ISO 4217 currency code (e.g. "EUR")

Caching the Result

IP geolocation data changes infrequently. Cache results in Redis with a 24-hour TTL to avoid hitting your API quota on every request:

const cacheKey = `geo:${ip}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const geo = await fetchGeo(ip);
await redis.setex(cacheKey, 86400, JSON.stringify(geo));
return geo;
Enter fullscreen mode Exit fullscreen mode

Originally published at https://apogeoapi.com/blog/detect-country-from-ip-nodejs. Try ApogeoAPI free at apogeoapi.com.

Top comments (0)