google-maps-rest
v1.0.1
Published
REST client for the nine new Google Maps Platform APIs: Places, Routes, Geocoding v4, Weather, Address Validation, Air Quality, Pollen, Solar, Area Insights. Native fetch, typed field masks, zero dependencies, ESM and CommonJS.
Maintainers
Readme
google-maps-rest
Google's new Maps APIs each ship their own npm package, and every one is generated on google-gax. gRPC is the default transport in Node, and the install carries @grpc/grpc-js, protobufjs and google-auth-library: 36 MB for one API.
Every one of these APIs is REST/JSON over HTTPS, and every RPC declares its own REST route in Google's public service definitions. This package calls those routes directly. 108 kB unpacked for all nine APIs, no runtime dependencies.
npm install google-maps-restNode 18 or later, or any runtime with a global fetch.
| | install size | runtime deps |
|---|---|---|
| @googlemaps/places | 36 MB, 66 packages | google-gax |
| google-maps-rest | 108 kB unpacked, 27 kB packed | none |
The @googlemaps/places figure is local node_modules after npm install --omit=dev; the google-maps-rest figures are npm pack output. Both measured 2026-09-18. Neither is a container image delta.
Usage
import { createClient } from 'google-maps-rest';
import { searchText, getPlace, billingTierFor } from 'google-maps-rest/places';
const client = createClient({ apiKey: process.env.GOOGLE_MAPS_API_KEY! });
const { places = [] } = await searchText(client, {
textQuery: 'coffee in Paris',
fieldMask: ['id', 'displayName', 'location'],
});
for (const match of places) {
console.log(match.displayName?.text, match.location);
}
const [first] = places;
if (first?.id) {
const place = await getPlace(client, {
placeId: first.id,
fieldMask: ['displayName', 'formattedAddress', 'rating'],
});
console.log(place.rating, billingTierFor('getPlace', ['displayName', 'rating']));
}Each API is a subpath import, so you only bundle what you call. The package ships ESM and CommonJS builds with their own type declarations, and typesVersions covers moduleResolution: "node".
import { computeRoutes } from 'google-maps-rest/routes';
import { geocodeAddress } from 'google-maps-rest/geocode';
import { currentConditions } from 'google-maps-rest/weather';Client options
const client = createClient({
apiKey: process.env.GOOGLE_MAPS_API_KEY!,
timeoutMs: 5_000, // default 10_000, applied per call
fetch: myFetch, // any fetch-compatible function; default is globalThis.fetch
resolveOrigin: (service) => `https://${service}.googleapis.com`, // proxies and tests
});Every call takes an optional { signal } as its last argument. The caller's signal is joined with the timeout, so either one aborts the request.
const controller = new AbortController();
const places = searchText(client, { textQuery: 'pizza', fieldMask: ['id'] }, { signal: controller.signal });Pagination
Calls that page (searchText, the weather forecasts and history, Air Quality forecast and history, Pollen forecast) take pageSize and pageToken and return nextPageToken. There is no iterator: pass the token back until it is absent.
let pageToken: string | undefined;
do {
const page = await searchText(client, { textQuery: 'bakery', fieldMask: ['id'], pageSize: 20, pageToken });
pageToken = page.nextPageToken;
} while (pageToken);64-bit integers
ProtoJSON encodes int64 as a decimal string because it overflows a JavaScript number. Fields such as Area Insights count, Routes fuelConsumptionMicroliters and every Money.units are typed Int64String and arrive as strings. Convert with BigInt(value) when you need arithmetic.
Field masks
Places and Routes require X-Goog-FieldMask, and the mask decides the billed SKU. Pass bare field names. The client roots them where the response nests results:
| call | you pass | sent as |
|---|---|---|
| searchText, searchNearby | ['id'] | places.id |
| getPlace | ['id'] | id |
| computeRoutes | ['duration'] | routes.duration |
| computeRouteMatrix | ['duration'] | duration |
billingTierFor(method, mask) returns the tier that mask bills at, since the highest tier present applies to the whole call. The method is required because the same field is priced differently per call: photos is IDs Only on getPlace and Pro on searchText, and Nearby Search has no tier below Pro at all.
billingTierFor('getPlace', ['id', 'photos']);
// { tier: 'ESSENTIALS_IDS_ONLY', unclassified: [] }
billingTierFor('searchText', ['id', 'photos']);
// { tier: 'PRO', unclassified: [] }A field Google has added since these tables were written comes back in unclassified rather than being priced as the cheapest tier, so tier is a lower bound whenever that array is not empty.
Coverage
| API | host | calls |
|---|---|---|
| Places (New) | places.googleapis.com | autocomplete, getPlace, searchText, searchNearby |
| Routes | routes.googleapis.com | computeRoutes, computeRouteMatrix |
| Geocoding v4 | geocode.googleapis.com | geocodeAddress, geocodeLocation, geocodePlace |
| Weather | weather.googleapis.com | currentConditions, forecastDays, forecastHours, historyHours |
| Address Validation | addressvalidation.googleapis.com | validateAddress, provideValidationFeedback |
| Air Quality | airquality.googleapis.com | currentConditions, forecast, history |
| Pollen | pollen.googleapis.com | forecast |
| Solar | solar.googleapis.com | findClosestBuildingInsights, getDataLayers |
| Area Insights | areainsights.googleapis.com | computeInsights |
This package does not cover the legacy APIs. Elevation, Time Zone, Geolocation and the legacy Places, Directions and Distance Matrix endpoints use ?key= query auth against maps.googleapis.com. Use @googlemaps/google-maps-services-js for those.
Auth
The API key goes in an X-Goog-Api-Key header on every call, never in the query string. Some Google docs show ?key= instead, but the two are the query and header forms of the same system parameter, available across Google REST APIs.
Errors
Failed calls throw MapsError or one of MapsAuthError, MapsQuotaError, MapsInvalidRequestError. Each carries httpStatus, a status narrowed to the canonical gRPC codes, the raw googleStatus Google sent, the original message and any details.
error.potentiallyRetryable is true for RESOURCE_EXHAUSTED, UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED and ABORTED. It means another attempt is worth making, not that one will succeed: RESOURCE_EXHAUSTED covers both short throttling and a hard daily quota, and retrying the second only burns the rest of your budget.
import { MapsQuotaError } from 'google-maps-rest';
try {
await searchText(client, { textQuery: 'pizza', fieldMask: ['id'] });
} catch (error) {
if (error instanceof MapsQuotaError) {
// error.potentiallyRetryable === true
}
}Contract tests
The wrapper tests for the seven pinned services send their requests through a stub fetch that checks the final URL, query string and JSON body against the Google Discovery document pinned under test/discovery/. Unknown body fields, undefined enum values, undeclared query parameters and wrong scalar types fail the test. The check covers requests only: responses, oneof exclusivity, required body fields (Discovery marks them in prose, not in the schema) and semantic limits are outside it.
Seven documents are pinned. Routes and Geocoding v4 refuse anonymous Discovery requests, so their tests run without the check until someone fetches those two documents once with a key:
GOOGLE_MAPS_API_KEY=... npm run discovery:fetch -- routes geocodeThe key travels in a header and is not recorded. A weekly workflow refetches the pinned documents and opens an issue when a method, parameter, request or response field, or enum changes, and closes it once the pinned documents match again.
Versioning
Semantic versioning. A major release is any change a consumer can observe: an exported type or function signature, an error class, a subpath entry, the wire shape a call produces, or the Node floor (18). A minor release adds calls, request fields or exports. A patch changes nothing observable.
A field Google adds to a response is not a breaking change: the JSON passes through untouched, and a minor release declares it. Enum-like strings are typed Open<T>, so a new enum value arrives without a release. A request field Google adds appears in a minor release once the pinned Discovery document carries it.
License
MIT
