Build a routing service: production guide for modern geospatial systems

A routing service computes travel paths, distances, durations, and sometimes turn-by-turn instructions across a transportation network. A production implementation is more than an HTTP endpoint around a shortest-path algorithm: it requires a defensible road-data pipeline, transportation profiles, coordinate snapping, graph preparation, versioned releases, API limits, validation, monitoring, and a clear answer to what the returned route actually represents. This guide presents a provider-neutral architecture and uses OSRM as a concrete open-source example because its official documentation exposes the complete path from OpenStreetMap extracts to a running HTTP service. The same design concerns apply when using another routing engine or a hosted provider.

Routing is the computation layer that turns physical roads, paths, restrictions, and travel assumptions into an answer to a geographic question: how should something move from one place to another? The answer depends on the network, the mode of travel, the cost model, and the freshness of the source data. A reliable service therefore needs explicit contracts at every boundary: coordinates entering the system, network data being compiled, profiles determining allowed movement, route results returned to clients, and operational signals showing whether the result can be trusted.

How EDK fits

For: Engineers and geospatial developers building a self-hosted or provider-neutral routing API for web applications, logistics systems, fleet operations, mobility products, public-sector tools, or spatial analysis workflows.

A routing service is a focused example of computing about the physical world. It does not merely display roads; it models movement through a connected geographic network and calculates a relationship between real places. That makes routing complementary to a broader world model: a route can connect an address or building to a road, relate a vehicle or service request to a reachable facility, compare travel access across neighborhoods, or aggregate travel costs over roads, waterways, public assets, and other physical features. The important editorial distinction is that a routing engine supplies network-path computation, while a broader geographic system may also need to find the relevant places, understand their attributes and provenance, and combine routing with proximity, regions, infrastructure, environmental features, or public data. E2 is relevant here as a conceptual frame for treating routes as computations over a searchable representation of Earth, not as a claim that E2 itself provides or embeds any particular routing engine.

  • •Find candidate roads, facilities, addresses, service requests, charging stations, or public assets, then use routing as the network-aware computation for travel time, distance, reachability, or visit ordering.
  • •Compare physical access across regions by routing from population centers, depots, schools, clinics, or other places to a set of destinations and aggregating the results by boundary or operating area.
  • •Combine route geometry with geographic analysis: identify which roads pass near flooding issues, construction permits, parks, streams, or other features, while keeping straight-line proximity distinct from network travel distance.
Core computation
Find a path, distance, duration, or related network result between geographic waypoints.
Concrete reference implementation
OSRM with an OpenStreetMap extract, a routing profile, graph preparation, and an HTTP server.
Common adjacent computation
Many-to-many matrices, nearest-road lookup, map matching, trip ordering, and spatial filtering.
Critical distinction
Network distance and travel time are different from straight-line or geodesic distance.
Data responsibility
The operator must manage source freshness, provenance, licensing, attribution, capacity, and validation.

The EDK outcome

Before you start

Basic knowledge of HTTP APIs, JSON, command-line tools, containers, and geographic coordinates.

A Linux, macOS, or equivalent environment capable of running Docker.

A versioned road-network extract in a format supported by the selected routing engine.

A documented transportation mode and routing profile.

A target geography, expected request volume, latency target, refresh policy, and operational owner.

A test set of known routes and edge cases, including inaccessible points and mode-specific restrictions.

Learning objectives

Define a production routing service contract around modes, waypoints, units, geometry, errors, and limits.

Prepare a reproducible road-network graph from a versioned geographic extract.

Understand the OSRM extraction, partitioning, customization, and runtime stages.

Separate network distance and travel time from geometric proximity calculations.

Design refresh, validation, monitoring, rollback, attribution, and provenance workflows.

Verification

A known origin-destination request returns a successful response with distance and duration.

A coordinate-order test confirms that longitude and latitude are not accidentally reversed.

A no-route or invalid-coordinate test returns a stable application error rather than an unhandled engine failure.

At least one one-way, turn-restriction, bridge, ferry, or disconnected-network case is represented in the test suite.

The response geometry can be parsed by the intended client and uses the documented coordinate reference and order.

The running service exposes the expected graph release, engine version, and profile metadata through internal diagnostics.

A graph refresh can be rolled back without rebuilding the previous release.

Troubleshooting

If every request fails, verify that the graph preparation completed, that the runtime algorithm matches the preparation workflow, and that the container can read all generated files.

If routes are unexpectedly reversed or far away, inspect coordinate order. OSRM's documented coordinate syntax is longitude,latitude, not latitude,longitude. (project-osrm.org)

If a point cannot be routed, test the nearest-road operation, move the point only for diagnostic purposes, and determine whether the issue is snapping distance, access restrictions, a disconnected component, or the selected profile.

If the service is slow after requesting steps or annotations, compare response sizes and query options. Use the smallest response that satisfies the client and impose waypoint and timeout limits.

If a new extract changes many routes, compare source extract date, profile revision, engine version, and preprocessing artifacts before assuming the runtime is defective.

If memory or disk usage becomes a problem during graph generation, reduce the build geography, provision more resources, or select a processing strategy appropriate to the target region. Do not infer a universal hardware requirement from a small-city example.

If traffic updates do not change results, confirm that the update files are being generated, applied by the selected workflow, loaded by the running service, and tested against a route whose affected edges are known.

Next steps

Add a geocoding boundary so address resolution and route computation remain independently testable.

Add a matrix endpoint for fleet assignment, service-area analysis, or facility comparison.

Store route requests and results with privacy-aware retention, source-release identifiers, and reproducibility metadata.

Combine network results with PostGIS proximity and containment queries for roads, buildings, public assets, environmental features, or operational records.

Evaluate whether a hosted routing provider, a self-hosted engine, or a hybrid design best fits your traffic, freshness, coverage, and compliance requirements.

Document OpenStreetMap attribution, data licensing, and the limits of any public tile or API services used alongside the routing stack.

What you need before writing code

Start by defining the routing problem rather than choosing an engine. Specify supported modes such as driving, cycling, walking, or delivery vehicles; whether the service must support one-to-one routes, many-to-many matrices, map matching, alternatives, or trip ordering; the maximum number of waypoints; expected request volume; acceptable latency; and how often the road network must be refreshed. OSRM documents separate services for routing, tables, map matching, trip planning, nearest-road lookup, and tiles, but another engine may expose a different feature set. Do not assume that a capability exists merely because a product is described as a routing engine. Verify it in the selected engine's current documentation. (project-osrm.org)

You also need a licensed and reproducible source of road-network data. For an OpenStreetMap-based deployment, obtain an extract appropriate to the target geography and retain its acquisition date, region, checksum, source attribution, and processing version. OpenStreetMap data is available under the Open Database License, and public use requires appropriate attribution; the OpenStreetMap Foundation also warns that its public tile services are best-effort and not intended as an unlimited production tile backend. A routing service should therefore use a suitable extract or provider arrangement rather than treating public map tiles as its data pipeline. (wiki.openstreetmap.org)

  • •Input data: a versioned road-network extract and documented source license.
  • •Processing: a routing profile describing allowed roads, speeds, turn behavior, and restrictions.
  • •Runtime: a routing daemon, health checks, logs, metrics, and a deployment strategy.
  • •Application contract: coordinate order, modes, waypoint limits, error codes, units, and route geometry format.
  • •Validation set: known origin-destination pairs, inaccessible points, one-way streets, ferries, bridges, turn restrictions, and mode-specific cases.

Step 1: prepare and version the road network

Download a regional extract from a source appropriate to your deployment and record its exact filename, timestamp, checksum, and licensing information. Keep the raw PBF separate from generated routing artifacts. This separation makes it possible to rebuild the graph after changing a profile or upgrading the engine without silently mixing source data and derived files.

OSRM's documented preparation pipeline begins with osrm-extract, which reads an OSM file and a Lua profile and produces intermediate files. For the Multi-Level Dijkstra workflow, the next stages are osrm-partition and osrm-customize, followed by osrm-routed with the MLD algorithm. OSRM also documents a Contraction Hierarchies path for deployments that do not need the same customization workflow. (github.com)

  • •Download or mount the PBF extract into a controlled build directory.
  • •Run extraction with a profile chosen for the transportation mode.
  • •Partition and customize for MLD, or contract for a CH deployment.
  • •Publish generated files as an immutable release artifact.
  • •Start the runtime against the exact release that passed validation.

Step 2: expose a small, explicit routing API

Keep the public API narrower than the underlying engine. A useful first endpoint accepts an ordered list of waypoints, a mode, optional route preferences, and output options. Return a stable application-level response containing status, distance, duration, geometry, legs, and steps when requested. Preserve the engine response internally for diagnostics, but avoid coupling every client to engine-specific fields.

OSRM's HTTP API uses a path containing the service, API version, profile, and coordinates. Coordinates are supplied as longitude,latitude pairs separated by semicolons. The route service can request alternatives, steps, annotations, and different geometry formats. Its documented error codes include invalid requests and cases where an input coordinate cannot be snapped to a street segment. (project-osrm.org)

  • •Use POST for application-level requests with many waypoints, even if the backend engine uses GET.
  • •Return units explicitly, such as meters and seconds.
  • •Make geometry format an explicit option, such as GeoJSON or encoded polyline.
  • •Include a route-data release identifier in diagnostic metadata where appropriate.
  • •Do not promise live traffic unless the data pipeline actually supplies current traffic or speed updates.

Step 3: connect routing to spatial data without mixing distance concepts

Routing distance is the length of a path through a network. It is not the same as geodesic distance, projected planar distance, or a simple radius query. Use network distance when the question is travel or reachability; use spatial distance when the question is proximity. A facility can be geographically close but unreachable without a bridge, road connection, legal turn, or appropriate access mode.

PostGIS is useful around the routing service for storing origins, destinations, service areas, road-adjacent assets, and route results. Its ST_DWithin function is designed for indexed distance filtering, while ST_Distance computes distance. For geography values, PostGIS documents distance units in meters; for geometry values, units depend on the coordinate reference system. Keep those semantics explicit in SQL and application code. (postgis.net)

  • •Shortlist by spatial proximity when that is computationally cheaper.
  • •Rank or verify candidates with network distance or duration when movement matters.
  • •Store route geometry with its coordinate reference and engine release metadata.
  • •Use spatial indexes for geographic filtering and inspect query plans at realistic scale.
  • •Treat route geometry as derived output, not as an authoritative boundary for unrelated analysis.

Step 4: deploy, refresh, and operate the service

Run the routing daemon behind a reverse proxy or API gateway with request timeouts, authentication where needed, rate limits, and structured access logs. Keep graph generation outside the request-serving process. Build a new release in an isolated workspace, run validation tests, and switch traffic only after the new graph is ready. This avoids exposing partially generated artifacts and makes rollback possible.

Monitor more than HTTP availability. Track request rate, latency by endpoint and mode, timeout rate, invalid-input rate, no-route responses, snapping failures, response size, memory use, CPU use, and the age of the loaded network release. A service can be healthy at the process level while returning poor results because the graph is stale, the wrong profile is loaded, or a new extract changed connectivity.

  • •Build immutable graph releases and retain the previous known-good version.
  • •Run smoke tests after every data or profile change.
  • •Use blue-green, canary, or equivalent deployment for graph swaps.
  • •Record source extract date, profile version, engine version, and processing commands.
  • •Plan separately for static road-network updates and dynamic traffic or closure updates.

Workflow

01

1. Create a reproducible build directory

Keep source extracts, profiles, generated graph artifacts, validation results, and release metadata separate. Record the source URL, extract date, checksum, target region, engine image or binary version, and profile revision. Do not overwrite a known-good graph in place.

mkdir -p routing/{source,build,releases,tests}\ncd routing\nsha256sum source/region-latest.osm.pbf > source/region-latest.osm.pbf.sha256
02

2. Extract the road graph with a mode profile

OSRM's extraction stage reads the OSM file and Lua profile and generates the intermediate files used by later graph-preparation stages. The profile is a central part of routing behavior: changing it can change which ways are routable and how costs are assigned.

docker run --rm -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-extract -p /opt/car.lua /data/source/region-latest.osm.pbf
03

3. Prepare the graph for the chosen runtime algorithm

For OSRM's MLD workflow, partition the extracted graph and customize it before starting osrm-routed. If you choose a different algorithm or engine, follow that engine's official preparation sequence rather than copying these commands mechanically.

docker run --rm -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-partition /data/source/region-latest.osrm\ndocker run --rm -t -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-customize /data/source/region-latest.osrm
04

4. Start the routing service

Run the service against the generated graph base path. In production, bind it to an internal network, place an API gateway or reverse proxy in front of it, and expose health and readiness checks separately from user-facing route requests.

docker run --rm -p 5000:5000 -v "${PWD}:/data" ghcr.io/project-osrm/osrm-backend osrm-routed --algorithm mld /data/source/region-latest.osrm
05

5. Test a known route and inspect the response

Use a small, deterministic request first. Confirm that the coordinate order is longitude,latitude, that the response status is successful, and that returned distance and duration have the units your application expects. Request steps or GeoJSON only when needed because detailed responses increase payload size.

curl "http://127.0.0.1:5000/route/v1/driving/-122.4194,37.7749;-121.8863,37.3382?overview=false&steps=true"
06

6. Add application-level validation and limits

Validate coordinates, mode, waypoint count, options, timeout, and payload size before forwarding a request. Normalize engine errors into a stable API contract while preserving the original error code in structured logs. Add request identifiers so a failed client response can be traced through the gateway and routing process.

curl -X POST "https://routing.example.test/v1/routes" -H "content-type: application/json" -d '{"mode":"driving","coordinates":[[-122.4194,37.7749],[-121.8863,37.3382]],"geometry":"geojson","steps":false}'
07

7. Validate the graph against geographic edge cases

Run automated tests for one-way streets, turn restrictions, bridges, ferries, disconnected roads, mode exclusions, points away from roads, and routes crossing the extract boundary. Compare output after every source, profile, or engine change. Store the expected result tolerance rather than requiring identical geometry when a legitimate engine update changes the path slightly.

curl "http://127.0.0.1:5000/nearest/v1/driving/-122.4194,37.7749"\ncurl "http://127.0.0.1:5000/table/v1/driving/-122.4194,37.7749;-121.8863,37.3382"
08

8. Release, monitor, and refresh

Publish the graph as an immutable release, record its provenance, run smoke tests, and switch traffic only after readiness checks pass. Monitor latency, errors, no-route responses, snapping failures, resource usage, and loaded-release age. Maintain a rollback path to the previous graph. Refresh static road data and dynamic traffic or closure data as separate pipelines.

curl -fsS "http://127.0.0.1:5000/route/v1/driving/-122.4194,37.7749;-121.8863,37.3382?overview=false" >/dev/null && echo "routing smoke test passed"

Questions teams ask

Should I build a routing engine from scratch?

Usually not. The difficult work includes graph modeling, preprocessing, turn restrictions, profiles, optimization, map matching, and operational maintenance. A more practical approach is to use an established engine such as OSRM or another documented routing system, then invest engineering effort in data provenance, API design, domain-specific constraints, testing, and integration. Build from scratch only when you have a strong research or product reason and the expertise to maintain a graph engine.

What is the difference between routing distance and distance in PostGIS?

Routing distance follows the modeled transportation network and can account for one-way roads, turns, access restrictions, and mode-specific connectivity. PostGIS distance functions measure geometric distance between spatial objects. ST_DWithin is appropriate for indexed proximity filtering, while ST_Distance calculates geometric distance; neither substitutes for a route when the question is how far someone must travel. (postgis.net)

Can an OpenStreetMap-based routing service provide live traffic?

Not from the base road extract alone. A road network and routing profile provide a model of connectivity and default costs. Live traffic requires a separate current data source and an engine workflow that can apply dynamic speed or penalty updates. OSRM documents customization inputs for segment speeds and turn penalties in its MLD tooling, but your deployment must supply and validate those updates. (github.com)

Why does a valid coordinate sometimes return no route?

The point may be too far from a routable segment, located on an inaccessible feature, placed on the wrong side of a barrier, or incompatible with the selected profile. It may also be a coordinate-order error. Validate longitude and latitude ranges, inspect the snapped location with a nearest-road operation where available, test the same point against the intended mode, and log the engine's structured error or response code.

Should I route every candidate or filter candidates first?

For large candidate sets, shortlist with indexed spatial predicates such as a radius or region filter, then route the smaller set when travel cost matters. This two-stage workflow is especially useful for facilities, charging stations, service locations, or public assets. Keep the semantics clear: the first stage is geometric proximity; the second is network reachability or travel cost.

Keep exploring

Sources

More in Guides