"Restaurants within 2 km", "which delivery zone covers this address", "does this route cross a flood area" — location questions are common in delivery, travel, real-estate and logistics products. MongoDB answers them natively: store shapes as GeoJSON, index them with 2dsphere, and query by distance, containment or intersection. After this lesson you will be able to model points and polygons correctly, run $near, $geoWithin and $geoIntersects queries, and compute distances with the $geoNear aggregation stage.
GeoJSON objects have a type and coordinates. MongoDB supports Point, LineString, Polygon, the Multi* variants and GeometryCollection. The one rule everyone gets wrong at least once: coordinates are [longitude, latitude], in that order, with longitude in -180..180 and latitude in -90..90.
{ type: "Point", coordinates: [73.8567, 18.5204] }
{ type: "Polygon", coordinates: [[
[73.85, 18.51], [73.87, 18.51], [73.87, 18.53], [73.85, 18.53], [73.85, 18.51] // closed ring
]] }A polygon ring must be closed (first and last positions equal). Create the index on the field that holds the GeoJSON:
db.places.createIndex({ location: "2dsphere" })
db.zones.createIndex({ area: "2dsphere" })
db.places.createIndex({ category: 1, location: "2dsphere" }) // compound is allowed2dsphere calculates on a sphere, so distances are in meters and results are accurate across the globe. The older 2d index is for flat, Euclidean coordinate systems such as game maps.
$near returns documents ordered from nearest to farthest, optionally bounded by $maxDistance and $minDistance (meters):
db.places.find({
category: "cafe",
location: {
$near: {
$geometry: { type: "Point", coordinates: [73.856, 18.52] },
$minDistance: 50,
$maxDistance: 2000
}
}
}).limit(10)$near requires a geospatial index, permits only one $near per query, cannot be placed inside $or, and is not supported on sharded collections — use $geoNear there. $nearSphere behaves the same and exists for legacy coordinate pairs.
$geoWithin matches documents whose geometry lies entirely inside a shape. Results are not sorted, an index is optional (but recommended), and the operator combines freely with other conditions:
// Inside a polygon (e.g. a delivery zone drawn on a map)
db.places.find({ location: { $geoWithin: { $geometry: zone.area } } })
// Inside a circle: center [lng, lat] and radius in radians (km / 6378.1)
db.places.find({ location: { $geoWithin: { $centerSphere: [[73.856, 18.52], 5 / 6378.1] } } })$geoIntersects matches documents whose geometry shares any point with the given one. It answers "which zones contain this address" when the zones are the stored polygons:
db.zones.find({ area: { $geoIntersects: { $geometry: { type: "Point", coordinates: [73.856, 18.52] } } } })
db.roads.find({ path: { $geoIntersects: { $geometry: floodPolygon } } })$geoNear must be the first stage. It sorts by distance like $near and additionally writes the distance into each document:
db.places.aggregate([
{ $geoNear: {
near: { type: "Point", coordinates: [73.856, 18.52] },
distanceField: "distanceMeters",
maxDistance: 3000,
query: { category: "cafe", open: true },
spherical: true
} },
{ $project: { name: 1, distanceMeters: { $round: ["$distanceMeters", 0] } } },
{ $limit: 5 }
])Use key: "location" when the collection has more than one geospatial index. $geoNear works on sharded collections and is the natural choice for "nearest N with distance shown" features.
| Operator | Sorted by distance | Returns distance | Needs index | Typical question |
|---|---|---|---|---|
| $near | yes | no | yes | nearest places |
| $geoWithin | no | no | no | everything inside this area |
| $geoIntersects | no | no | no | which shapes touch this one |
| $geoNear (stage) | yes | yes | yes | nearest places with distance |
$near returns nothing useful.$maxDistance and $geoNear use meters for GeoJSON; $centerSphere uses radians.{ lat, lng } objects that no geospatial operator understands.What is the correct coordinate order for a GeoJSON point in MongoDB?
[longitude, latitude] coordinates and index them with 2dsphere.$near returns results nearest-first with distances bounded in meters; it needs an index and one per query.$geoWithin tests containment in a polygon or circle; $geoIntersects tests overlap; both work without sorting.$geoNear is the aggregation equivalent that also outputs the distance and supports sharded collections.$centerSphere.Next lesson: The Node.js Driver in Depth — connection pooling, typed collections, cursors, error handling and production configuration.