A NestJS-based geospatial service that allows you to create locations and find nearby locations within a specified radius using PostGIS.
- 📍 Create Locations: Store locations with names and coordinates
- 🔍 Proximity Search: Find all locations within a specified radius from a given point
- 🗺️ PostGIS Integration: Leverages PostgreSQL's PostGIS extension for efficient geospatial queries
- âś… Validation: Built-in coordinate validation and data transformation
- Node.js (v14 or higher)
- PostgreSQL with PostGIS extension enabled
- NestJS application setup
Enable PostGIS extension in your PostgreSQL database:
CREATE EXTENSION IF NOT EXISTS postgis;npm install @nestjs/common @nestjs/typeorm typeorm pg class-validator class-transformerImport the LocationModule in your app module:
import { LocationModule } from './location/location.module';
@Module({
imports: [
// ... other imports
LocationModule,
],
})
export class AppModule {}location/
├── location.controller.ts # HTTP endpoints
├── location.service.ts # Business logic
├── location.entity.ts # Database entity
├── location.dto.ts # Data transfer objects
└── location.module.ts # Module definition
Create a new location with name and coordinates.
Endpoint: POST /location
Request Body:
{
"name": "Central Park",
"latitude": 40.785091,
"longitude": -73.968285
}Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Central Park",
"coordinates": {
"type": "Point",
"coordinates": [40.785091, -73.968285]
}
}Validation Rules:
name: Must be a stringlatitude: Number between -90 and 90longitude: Number between -180 and 180
Example cURL:
curl -X POST http://localhost:3000/location \
-H "Content-Type: application/json" \
-d '{
"name": "Central Park",
"latitude": 40.785091,
"longitude": -73.968285
}'Find all locations within a specified radius from a given point.
Endpoint: GET /location/radius
Query Parameters:
lat(required): Latitude of the center pointlon(required): Longitude of the center pointrange(required): Search radius in kilometers
Example Request:
GET /location/radius?lat=40.785091&lon=-73.968285&range=5
Response:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Central Park",
"coordinates": {
"type": "Point",
"coordinates": [40.785091, -73.968285]
}
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Times Square",
"coordinates": {
"type": "Point",
"coordinates": [40.758896, -73.98513]
}
}
]Example cURL:
curl -X GET "http://localhost:3000/location/radius?lat=40.785091&lon=-73.968285&range=5"The service uses a single location table with the following structure:
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key, auto-generated |
| name | VARCHAR | Location name |
| coordinates | GEOGRAPHY(Point, 4326) | Geospatial point using WGS 84 coordinate system |
@Entity()
export class Location {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column({
type: 'geography',
srid: 4326,
spatialFeatureType: 'Point',
})
coordinates: Point;
}Coordinates are stored as PostGIS GEOGRAPHY points with SRID 4326 (WGS 84), which is the standard coordinate reference system used by GPS.
Note: In the Point structure, coordinates are stored as [latitude, longitude].
The service uses PostGIS's ST_DWithin function to efficiently find locations within a specified distance:
ST_DWithin(
location.coordinates,
ST_SetSRID(ST_MakePoint(:lon, :lat), 4326),
:range
)Features:
- Takes the search radius in meters (automatically converted from kilometers)
- Uses spatial indexing for fast queries
- Returns accurate results accounting for Earth's curvature
- Query format:
ST_MakePoint(longitude, latitude)- note the order!
The service includes validation for:
Returns 400 Bad Request with message:
{
"statusCode": 400,
"message": "Invalid coordinates"
}Returns 400 Bad Request with validation errors:
{
"statusCode": 400,
"message": [
"latitude must not be greater than 90",
"longitude must not be less than -180"
],
"error": "Bad Request"
}# Create location 1
curl -X POST http://localhost:3000/location \
-H "Content-Type: application/json" \
-d '{"name": "Statue of Liberty", "latitude": 40.689247, "longitude": -74.044502}'
# Create location 2
curl -X POST http://localhost:3000/location \
-H "Content-Type: application/json" \
-d '{"name": "Empire State Building", "latitude": 40.748817, "longitude": -73.985428}'
# Create location 3
curl -X POST http://localhost:3000/location \
-H "Content-Type: application/json" \
-d '{"name": "Brooklyn Bridge", "latitude": 40.706086, "longitude": -73.996864}'# Find all locations within 10km of Times Square
curl -X GET "http://localhost:3000/location/radius?lat=40.758896&lon=-73.985130&range=10"Ensure your TypeORM configuration includes:
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'your_username',
password: 'your_password',
database: 'your_database',
entities: [Location],
synchronize: true, // Set to false in production
});Consider using environment variables for database configuration:
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=your_username
DB_PASSWORD=your_password
DB_DATABASE=your_databaseFor optimal performance with large datasets, create a spatial index:
CREATE INDEX location_coordinates_idx
ON location
USING GIST (coordinates);TypeORM will automatically create this index when using the geography type.
- The
ST_DWithinfunction uses the spatial index automatically - For very large datasets, consider partitioning by geographic regions
- Use appropriate range values to limit result sets
npm run testnpm run test:e2eError: type "geography" does not exist
Solution: Install and enable PostGIS:
CREATE EXTENSION IF NOT EXISTS postgis;Important: Note the different coordinate orders:
- DTO Input:
latitude, longitude - Point Storage:
[latitude, longitude] - PostGIS Functions:
ST_MakePoint(longitude, latitude)
The range parameter is in kilometers, which is automatically converted to meters for PostGIS queries.
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
For issues and questions, please open an issue on the GitHub repository.