npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-geocoding

v0.1.1

Published

Zero-config Expo-first geocoding SDK with Nominatim, caching, hooks, and autocomplete.

Readme

expo-geocoding

Expo-first geocoding for React Native — a typed layer over forward and reverse geocoding with no required native dependencies in v1. The default backend is OpenStreetMap Nominatim; you can plug in your own provider (Mapbox, Google Places, self-hosted Nominatim, etc.).

License: MIT npm npm downloads

Why this library

Raw Nominatim usage in production often means: no caching, no rate-aware queue, messy JSON, inconsistent fields. expo-geocoding provides:

  • A single Place model after normalizing OSM payloads
  • In-memory cache (default TTL 24h) and optional PersistentCache (e.g. MMKV / AsyncStorage with your adapter)
  • Request queue + throttle (~1 req/s by default, aligned with Nominatim etiquette) and retry with backoff on HTTP 429
  • Imperative functions + React hooks with debouncing and AbortSignal cancellation
  • PlaceAutocomplete — ready-made UI with match highlighting

Works in Expo Go and normal dev/production builds: TypeScript / JavaScript only for this package version.

Installation

npm install expo-geocoding
# or
yarn add expo-geocoding

Peer dependencies: expo, react, react-native.

When developing this repo locally:

npm install
npm run build

Required: User-Agent and Nominatim policy

Public Nominatim usage is governed by the OSM Foundation usage policy. You must send an identifiable User-Agent (app name, version, contact). Configure once at startup in production:

import { configureDefaultClient } from 'expo-geocoding';

configureDefaultClient({
  userAgent: 'MyAwesomeApp/1.0 ([email protected])',
});

If omitted, the library falls back to a generic default (fine for quick tests only).

Quick start

import { configureDefaultClient, search, reverse } from 'expo-geocoding';

configureDefaultClient({
  userAgent: 'MyApp/1.0 ([email protected])',
});

const places = await search('Kyiv');
const place = await reverse({ lat: 50.45, lon: 30.52 });

Place data model

All public APIs use this normalized shape:

| Field | Type | Description | |-------|------|-------------| | id | string | Stable identifier | | name | string | Short label for UI | | fullName | string | Full display string | | lat / lon | number | WGS84 coordinates | | city, country, street, postcode | string? | Parsed when available | | type | 'city' \| 'street' \| 'address' \| 'poi'? | Heuristic from OSM class/type |

Imperative API

search(query, options?)

Forward geocoding (text → list of places). No built-in debounce here (use useSearch for debounced UI).

import { search } from 'expo-geocoding';

const results = await search('Berlin', {
  limit: 10,
  language: 'en',
  countryCodes: ['de'],
  signal: abortController.signal,
  skipCache: false,
});

reverse({ lat, lon }, options?)

Reverse geocoding (coordinates → one Place).

import { reverse } from 'expo-geocoding';

const place = await reverse({ lat: 52.52, lon: 13.405 }, { language: 'de' });

autocomplete(query, options?)

Fast suggestions: internal debounce (300ms by default), ranking that favors cities/POIs, a separate cache key from search, and cancellation via AbortSignal.

import { autocomplete } from 'expo-geocoding';

const suggestions = await autocomplete('Kos', { language: 'sk' });

Client and global configuration

GeocodingClient

Use a dedicated instance when you need isolated cache/queue (tests, multiple configs):

import { GeocodingClient, createNominatimProvider } from 'expo-geocoding';

const client = new GeocodingClient({
  userAgent: 'MyApp/1.0 ([email protected])',
  minRequestIntervalMs: 1000,
  defaultTtlMs: 24 * 60 * 60 * 1000,
  autocompleteDebounceMs: 300,
  maxRetries: 3,
  baseUrl: 'https://nominatim.openstreetmap.org', // optional: self-hosted
  // provider: myCustomProvider,
  // persistentCache: myMmkvAdapter,
});

const list = await client.search('Lviv');

configureDefaultClient / getDefaultClient

Top-level search, reverse, and autocomplete use a singleton client by default:

import { configureDefaultClient, getDefaultClient } from 'expo-geocoding';

configureDefaultClient({ userAgent: '...' });
const client = getDefaultClient();

React hooks

All hooks accept an optional client?: GeocodingClient; otherwise the default singleton is used.

useSearch(query, options?)

  • debounceMs: default 300
  • enabled: default true
  • Returns { data, loading, error } with data as Place[] | null
import { useState } from 'react';
import { Text, FlatList } from 'react-native';
import { useSearch } from 'expo-geocoding';

function SearchDemo() {
  const [q, setQ] = useState('');
  const { data, loading, error } = useSearch(q, { debounceMs: 300, limit: 8 });

  if (error) return <Text>{error.message}</Text>;
  if (loading) return <Text>Loading…</Text>;
  return (
    <FlatList
      data={data ?? []}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <Text>{item.fullName}</Text>}
    />
  );
}

useReverse(coords, options?)

  • coords: { lat, lon } | null — when null, no request runs
  • Returns { data, loading, error } with data as Place | null
import { useReverse } from 'expo-geocoding';

const { data, loading } = useReverse({ lat: 50.45, lon: 30.52 });

useAutocomplete(query, options?)

Relies on the client’s autocomplete() debounce and cache. Returns { suggestions, loading, error }.

import { useAutocomplete } from 'expo-geocoding';

const { suggestions, loading } = useAutocomplete(query, { language: 'en' });

UI: PlaceAutocomplete

Text field, suggestion list, loading indicator, match highlighting, keyboard dismiss on select.

import { PlaceAutocomplete, type Place } from 'expo-geocoding';

export function Screen() {
  return (
    <PlaceAutocomplete
      placeholder="Search location"
      onSelect={(place: Place) => console.log(place.lat, place.lon)}
      maxSuggestions={8}
    />
  );
}

Optional props: client, language, countryCodes, style, inputStyle, listStyle, maxSuggestions.

PlaceAutocomplete is exported from the root entry (expo-geocoding) so consumers always resolve the same built bundle; there is no separate expo-geocoding/ui publish path.

Custom provider

Implement GeocodingProvider: search / reverse must return RawPlace[] / RawPlace. Pass it into GeocodingClient via provider. The client normalizes to Place using normalize().

createNominatimProvider({ userAgent, baseUrl? }) is exported if you need multiple Nominatim endpoints without duplicating client setup.

Persistent cache

Implement PersistentCache (get / set / remove as async). Values are stored as JSON strings.

const client = new GeocodingClient({
  userAgent: '...',
  persistentCache: {
    get: async (key) => storage.getString(key) ?? null,
    set: async (key, value, _ttlMs) => {
      /* persist with TTL if your backend supports it */
    },
    remove: async (key) => {
      storage.delete(key);
    },
  },
});

Example app

The example/ folder is a minimal Expo app using PlaceAutocomplete and configureDefaultClient.

cd example
npm install
npm run start
# then press i / a in the CLI, or in another terminal:
npm run ios

After native dependency changes on iOS: cd example/ios && pod install.

Build and publish

npm run build   # TypeScript → build/
npm run clean

Before npm publish, ensure build/ is up to date (the prepare script in package.json usually handles this).

Limitations

  • Public Nominatim has strict limits; for high traffic use a dedicated instance or another provider via GeocodingClient + provider.
  • This library does not ship Google/Mapbox API keys — only abstractions and a Nominatim default.
  • Follow the Nominatim policy and the terms of any geocoding API you use.

License

MIT