Geocoding endpoints — capability-gated autocomplete & reverse
Granit.Geocoding is a server-side
contract. Two of its capabilities, though, are things a browser needs to call
directly: an address autocomplete that fires as the user types, and a reverse
lookup when someone drops a pin on a map. Granit.Geocoding.Endpoints exposes
exactly those two — and nothing else — as HTTP endpoints.
There is deliberately no forward-geocoding endpoint. Resolving a full stored
address to a coordinate is back-office enrichment (see
Granit.AddressEnrichment), not
something a client should drive. Autocomplete and reverse are the only two that
belong on the wire, because only they answer an interactive, per-user question.
What gets mapped
Section titled “What gets mapped”The host maps the group with one call, which returns a RouteGroupBuilder for
further chaining:
app.MapGranitGeocoding();| Method & route | Query | Mapped when | Response |
|---|---|---|---|
GET /geocoding/autocomplete | q (required), limit (default 5) | an autocomplete-capable provider is registered | 200 GeocodingAutocompleteResponse |
GET /geocoding/reverse | lat, lon (required) | a reverse-capable provider is registered | 200 GeocodingReverseResponse, else 400 / 404 problem |
The route prefix (geocoding), OpenAPI tag, and authorization policy are
configurable via GeocodingEndpointsOptions (section Geocoding:Endpoints).
Capability gating
Section titled “Capability gating”Each endpoint is mapped only when the registered provider can serve it — the
group reads the GeocodingCapabilities
aggregate at map time:
if (capabilities.Autocomplete) group.MapGet("/autocomplete", HandleAutocompleteAsync) /* … */;
if (capabilities.Reverse) group.MapGet("/reverse", HandleReverseAsync) /* … */;So a host running Nominatim only (forward + reverse, no autocomplete) exposes
/reverse but not /autocomplete — the endpoint simply doesn’t exist, rather
than returning a runtime “not supported” error. Add Photon and the /autocomplete
route appears. The OpenAPI document reflects exactly what the deployment can do.
Response shapes
Section titled “Response shapes”public sealed record GeocodingAutocompleteResponse(IReadOnlyList<GeocodingSuggestionResponse> Suggestions);
public sealed record GeocodingSuggestionResponse( string Label, string? Street, string? PostalCode, string Locality, string Country, double? Latitude, double? Longitude);
public sealed record GeocodingReverseResponse( string? Street, string? PostalCode, string Locality, string Country, string Precision);The DTOs are flattened for the wire — a suggestion carries both a human-readable
Label for the typeahead list and the structured components a form fills in when
the user picks it. Precision is the match granularity (Rooftop, Street, or
Locality).
Security
Section titled “Security”Authenticated by default
Section titled “Authenticated by default”Geocoding proxies an external, rate-limited, billable provider, so the group
is never anonymous. By default MapGranitGeocoding() requires any
authenticated user; set GeocodingEndpointsOptions.AuthorizationPolicy to demand
a specific policy instead.
The host must rate-limit — denial-of-wallet
Section titled “The host must rate-limit — denial-of-wallet”The package does not apply a rate-limit policy itself, and this is the one thing a host must add. Autocomplete fires per keystroke, and the upstream provider quota is a shared resource: one principal hammering the endpoint can exhaust the quota — or run up the bill — for every tenant. That’s a denial-of-wallet, not just a denial-of-service.
Pull in the module and map the group:
[DependsOn(typeof(GranitGeocodingEndpointsModule))]public sealed class StorefrontModule : GranitModule { }// in the host's endpoint wiringapp.MapGranitGeocoding() .RequireGranitRateLimiting("geocoding");GranitGeocodingEndpointsModule depends on GranitGeocodingModule, so the
geocoding services and the GeocodingCapabilities aggregate are already
registered — register a provider (Nominatim
or Photon) and the matching
endpoints light up.
See also
Section titled “See also”- Geocoding — the contracts, providers, and the capability matrix these endpoints gate on.
- Rate limiting —
RequireGranitRateLimitingand per-principal partitioning. - Address platform overview — where the endpoints sit in the wider platform.