Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Geoip2 Geolite2 Composer Laravel Package

bobey/geoip2-geolite2-composer

Composer package that bundles the free GeoLite2 databases so you can use the MaxMind GeoIP2 PHP API without manual downloads or license keys. Installs the .mmdb files automatically for IP-based country and city lookups.

Deep Wiki
Context7

Getting Started

Add the package and its reader, then confirm what actually landed in vendor/:

composer require bobey/geoip2-geolite2-composer
composer require geoip2/geoip2   # pulls maxmind-db/reader

Look here first: vendor/bobey/geoip2-geolite2-composer/. Open its composer.json and README.md and check whether it (a) ships .mmdb files directly under db/ or data/, or (b) relies on an install-time download script. That single check determines the rest of your integration — and whether the package still works at all in 2025.

Point a config entry at the file rather than hard-coding the vendor path everywhere:

// config/services.php
'geoip' => [
    'database' => env('GEOIP_DATABASE', base_path('vendor/bobey/geoip2-geolite2-composer/db/GeoLite2-City.mmdb')),
],

First use case — country/locale defaulting for anonymous traffic:

use GeoIp2\Database\Reader;

$reader = new Reader(config('services.geoip.database'));
$record = $reader->city($request->ip());

$record->country->isoCode;   // 'DE'
$record->city->name;         // 'Berlin'
$record->location->latitude;

That is roughly the whole API surface you need day-to-day. Everything else is lifecycle and failure handling.

Implementation Patterns

Register the Reader once, as a singleton. maxmind-db/reader memory-maps the file, so a single long-lived instance is the efficient shape and works well with Octane and queue workers:

$this->app->singleton(Reader::class, fn () => new Reader(config('services.geoip.database')));

$this->app->bind(GeoLocator::class, fn ($app) => new MaxMindGeoLocator($app->make(Reader::class)));

Wrap it behind your own interface. You will almost certainly swap providers (see Gotchas), and an interface makes that a container binding change instead of a grep:

interface GeoLocator { public function locate(?string $ip): ?GeoResult; }

GeoResult should be a small immutable DTO (countryCode, subdivision, city, lat, lng, accuracyRadius), not the vendor Record object. Leaking GeoIp2\Model\City into your controllers is what makes migration painful.

Cache per-IP results. Lookups are sub-millisecond, but your traffic is heavily concentrated on a handful of office, VPN and datacenter IPs, and caching lets you survive a missing/corrupt database gracefully:

return Cache::remember("geo:{$ip}", now()->addDay(), fn () => $this->lookup($ip));

Middleware for a request-scoped country, resolved lazily so pages that don't need geo never touch the file:

$request->attributes->set('geo', fn () => app(GeoLocator::class)->locate($request->ip()));

Async enrichment for stored records. For analytics, orders or signup records, dispatch a job after the write rather than blocking the request. Geocoding is fast but the file can be tens of megabytes and cold-page-faulting it on a request thread is avoidable latency.

Testing. Never depend on the real .mmdb in unit tests. Bind a fake GeoLocator:

$this->app->instance(GeoLocator::class, new FakeGeoLocator('DE'));

For integration coverage, add a tiny committed test database (MaxMind publishes small test fixtures) and point services.geoip.database at it in phpunit.xml.

Where it earns its keep: locale/currency defaults, VAT and tax jurisdiction hints, shipping ETA, content/region gating, and cheap fraud signals (country mismatch vs. billing country). All of these are "best guess" inputs, not authoritative facts.

Gotchas and Tips

The data is frozen at December 2018. The last release predates MaxMind's December 30, 2019 licensing change, when the unauthenticated geolite.maxmind.com/download/... URLs were retired and an account + license key became mandatory. If this package downloads on install, that step is almost certainly returning 403s today. If it ships the database in the package, you are geolocating against seven-year-old allocations — ASN blocks, city assignments and country boundaries have all moved since. Test with a handful of known IPs before trusting anything.

Never store the database in vendor/. composer install, composer update, and Docker layer rebuilds wipe it. If you keep this package at all, copy/symlink the .mmdb into storage/app/geoip/ and treat that as the real location. Anything mutable in vendor/ is a bug waiting for a deploy.

Watch the size. GeoLite2-City is tens of megabytes. It inflates your vendor directory, slows composer install in CI, and can blow past container image budgets. Put it in a separate deploy step or a volume if you containerize.

Handle AddressNotFoundException unconditionally. Private ranges (10/8, 172.16/12, 192.168/16), loopback, and some IPv6 space always throw. Filter first, then treat the reader as a fallible collaborator:

if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    return null;
}
try { return $reader->city($ip); } catch (AddressNotFoundException) { return null; }

Geolocation failure must never 500 a page.

isoCode and nested fields can be null even on a successful lookup — satellite, research, and some IPv6 allocations have no country. Null-check rather than assuming ->country->isoCode exists.

Restart workers after swapping the database. The Reader holds an open mmap. Atomic update pattern: download to a temp file, rename() over the live path (rename is atomic on the same filesystem), then php artisan horizon:terminate / queue:restart so workers remap. On Octane, reload workers.

Beware --no-scripts and --no-plugins. If this package hooks into Composer, CI installs using those flags will skip the setup silently and you will debug a missing-file error at runtime instead of at build time. Add a deploy-time assertion that the file exists and is readable.

Accuracy is a guideline, not a fact. City-level GeoLite2 is frequently off by tens or hundreds of kilometres, VPN egress dominates for privacy-conscious users, and mobile carrier NAT routinely collapses thousands of users onto one location. Do not gate pricing, legal jurisdiction, or access control on it without a user-confirmed fallback.

Treat the IP as personal data. Under GDPR/UK GDPR you are processing personal data the moment you store it. Prefer persisting the derived country code over the raw IP, set a retention window, and document the lawful basis.

Where to go instead. For day-to-day production use, drop the wrapper and run the official pipeline: composer require geoip2/geoip2, install MaxMind's geoipupdate CLI with your license key, point it at storage/app/geoip/, and schedule it weekly. You get current data, a supported upgrade path, and the Reader API you already learned here. The interface you wrote above means this is a one-line container binding.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle