A Go-based REST API for geocoding location lookup using US ZIP codes and Ohio address data, built with Echo framework and PostgreSQL with PostGIS.
- π Fast geocoding lookup by ZIP code
- π Search ZIP codes by city name and state
- π Ohio address data from official state sources
- πΊοΈ Shapefile to GeoJSON conversion using GDAL
- ποΈ PostgreSQL database with PostGIS for geospatial queries
- π Built with Echo web framework
- π³ Docker support with Docker Compose
- π Comprehensive location data including population, density, coordinates, and county information
- Go 1.21 or later
- Docker and Docker Compose
- PostgreSQL with PostGIS extension
- GDAL - Required for converting shapefiles to GeoJSON
- macOS:
brew install gdal - Ubuntu:
sudo apt-get install gdal-bin - Windows: Download from gdal.org
- See GDAL Setup Guide for detailed instructions
- macOS:
# Run the setup script
./scripts/setup_dev.shThis will:
- Check all prerequisites
- Install Go dependencies
- Start Docker services
- Optionally test GDAL installation
-
Install GDAL (optional but recommended)
# macOS brew install gdal # Ubuntu/Debian sudo apt-get install gdal-bin
-
Start Docker services
docker compose up -d db pgadmin
-
Install Go dependencies
go mod download
-
Run the application
go run main.go # or with hot reloading air
- Local: http://localhost:8080/docs
- Advanced: http://localhost:8080/docs/advanced.html
- Fallback: http://localhost:8080/docs/fallback.html
- Primary YAML: http://localhost:8080/api-docs.yaml
- Alternative URLs:
- JSON Info: http://localhost:8080/api-docs.json (redirects to YAML)
- Discovery: http://localhost:8080/api-docs-test
- π Interactive API explorer - Test endpoints directly in browser
- π Comprehensive examples - Real ZIP code data samples
- π¨ Beautiful Scalar UI - Modern, responsive design
- π Searchable documentation - Quick navigation with hotkeys
- πΎ Multiple code samples - JavaScript, cURL, and more
- π Multi-environment support - Switch between dev/prod servers
# Start API with docs
make dev
# Visit documentation
open http://localhost:8080/docs
# Or serve docs only
make docsGET /api/v1/geocode/{zipcode}
Returns detailed information for a specific ZIP code.
Example:
curl http://localhost:8080/api/v1/geocode/10001Response:
{
"success": true,
"data": {
"zip_code": "10001",
"city_name": "New York",
"state_code": "NY",
"state_name": "New York",
"zcta": true,
"population": 21102.0,
"density": 35400.5,
"primary_county_code": "36061",
"primary_county_name": "New York",
"county_weights": {"36061": "100"},
"county_names": ["New York"],
"county_codes": ["36061"],
"imprecise": false,
"military": false,
"timezone": "America/New_York",
"latitude": 40.75066,
"longitude": -73.99670
},
"count": 1
}GET /api/v1/search?city={city_name}&state={state_code}&limit={limit}
Search for ZIP codes by city name, optionally filtered by state.
Parameters:
city(required): City name to search forstate(optional): Two-letter state code (e.g., "NY", "CA")limit(optional): Maximum number of results (default: 50, max: 100)
Example:
curl "http://localhost:8080/api/v1/search?city=Springfield&state=IL&limit=10"GET /api/v1/distance/{from}/{to}
Calculate the precise distance between two ZIP codes using the Haversine formula.
Example:
curl http://localhost:8080/api/v1/distance/10001/90210Response:
{
"success": true,
"data": {
"from_zip_code": "10001",
"to_zip_code": "90210",
"distance_miles": 2445.5,
"distance_km": 3936.2
},
"count": 1
}GET /api/v1/nearby/{zipcode}?radius={miles}&limit={limit}
Find all ZIP codes within a specified radius of a center ZIP code.
Parameters:
radius(optional): Search radius in miles (default: 1, max: 100)limit(optional): Maximum results (default: 50, max: 200)
Example:
curl "http://localhost:8080/api/v1/nearby/10001?radius=5&limit=10"GET /api/v1/proximity/{center}/{target}?radius={miles}
Check if a target ZIP code is within a specified radius of a center ZIP code.
Example:
curl "http://localhost:8080/api/v1/proximity/10001/10002?radius=1"Response:
{
"success": true,
"data": {
"center_zip_code": "10001",
"target_zip_code": "10002",
"radius_miles": 1,
"is_within_radius": true,
"actual_distance_miles": 0.5,
"actual_distance_km": 0.8
},
"count": 1
}GET /api/v1/health
Returns the API health status.
POST /api/v1/admin/load-data?file={csv_file_path}
Loads ZIP code data from CSV file into the database.
-
Clone or set up the project:
git clone <your-repo-url> cd geocoding-api
-
Ensure the CSV file is present: Make sure
georef-united-states-of-america-zc-point.csvis in the project root. -
Start the services:
docker-compose up -d
-
Load the ZIP code data:
curl -X POST http://localhost:8080/api/v1/admin/load-data
-
Test the API:
curl http://localhost:8080/api/v1/geocode/10001
- Go 1.21 or later
- PostgreSQL 12 or later
-
Install dependencies:
go mod download
-
Set up PostgreSQL database:
CREATE DATABASE geocoding_db;
-
Configure environment variables:
cp .env.example .env # Edit .env with your database configuration -
Run the application:
go run main.go
-
Load the ZIP code data:
curl -X POST http://localhost:8080/api/v1/admin/load-data
| Variable | Description | Default |
|---|---|---|
DB_HOST |
PostgreSQL host | localhost |
DB_PORT |
PostgreSQL port | 5432 |
DB_USER |
PostgreSQL username | postgres |
DB_PASSWORD |
PostgreSQL password | postgres |
DB_NAME |
PostgreSQL database name | geocoding_db |
DB_SSLMODE |
PostgreSQL SSL mode | disable |
PORT |
API server port | 8080 |
The ZIP code data includes the following fields:
- ZIP Code: Primary identifier
- City and State: Official USPS names and codes
- Population and Density: Demographics data
- Coordinates: Latitude and longitude
- County Information: Primary county and weights for multi-county ZIP codes
- Timezone: Time zone identifier
- Flags: ZCTA, imprecise, military status
CREATE TABLE zip_codes (
zip_code VARCHAR(10) PRIMARY KEY,
city_name VARCHAR(255) NOT NULL,
state_code VARCHAR(2) NOT NULL,
state_name VARCHAR(255) NOT NULL,
zcta BOOLEAN NOT NULL DEFAULT FALSE,
zcta_parent VARCHAR(10),
population DECIMAL(12,2),
density DECIMAL(10,2),
primary_county_code VARCHAR(10) NOT NULL,
primary_county_name VARCHAR(255) NOT NULL,
county_weights JSONB,
county_names TEXT,
county_codes TEXT,
imprecise BOOLEAN NOT NULL DEFAULT FALSE,
military BOOLEAN NOT NULL DEFAULT FALSE,
timezone VARCHAR(100) NOT NULL,
latitude DECIMAL(10,7) NOT NULL,
longitude DECIMAL(10,7) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);This project uses a custom migration system with version tracking. Migrations run automatically when the application starts.
The application automatically:
- Creates a
schema_migrationstable to track applied migrations - Runs pending migrations in order
- Loads ZIP code data from CSV if the database is empty
- Downloads and converts Ohio address data (requires GDAL)
Migration files are located in:
database/migrations.go- Migration definitionsservices/zipcode_service.go- Data loading logic
The application automatically downloads address data from the Ohio LBRS site for all 88 Ohio counties.
Data Flow:
- π₯ Download - ZIP files from Ohio LBRS (e.g.,
ADA_ADDS.zip) - π¦ Extract - Shapefiles from ZIP archives
- π Convert - Shapefiles to GeoJSON using GDAL/ogr2ogr
- πΎ Load - GeoJSON features into PostgreSQL with PostGIS
Requirements:
- GDAL must be installed for shapefile conversion
- See GDAL Setup Guide for installation instructions
- Without GDAL, placeholder files are created (no address data)
Testing:
# Test GDAL installation and conversion
./scripts/test_gdal.sh
# This will download and convert a sample countyManual trigger:
# Force re-download and conversion
# Delete cached files and restart the application
rm -rf oh/* cache/*
go run main.goFor more advanced migration needs, you can use golang-migrate:
# Install migrate tool
make install-tools
# Create a new migration
make migrate-create
# Apply migrations
make migrate-up
# Rollback migrations
make migrate-down
# Check migration status
make migrate-versionMigration files: migrations/*.sql
The application automatically loads ZIP code data on first run:
- Automatic: Data loads when database is empty
- Manual:
curl -X POST http://localhost:8080/api/v1/admin/load-data - Makefile:
make load-data
The production Docker image automatically optimizes its size by:
- GeoJSON File Cleanup: After loading county boundary data into PostgreSQL, the system automatically removes source GeoJSON files (~2.6GB) to minimize container size
- Environment-Based Cleanup: Files are only removed in production (
ENV=production) or when explicitly enabled (CLEANUP_GEOJSON=true) - Post-Migration Cleanup: Cleanup occurs after successful data migration, ensuring data integrity
For optimal production deployment, set these environment variables:
ENV=production # Enables production optimizations
CLEANUP_GEOJSON=true # Forces GeoJSON cleanup regardless of ENVIn development, GeoJSON files are preserved by default. To test cleanup locally:
CLEANUP_GEOJSON=true docker-compose upThe database includes several indexes for optimal query performance:
- Primary key index on
zip_code - Index on
state_codefor state-based filtering - Index on
city_namefor city searches - Composite index on
latitude, longitudefor geographical queries - Spatial index on county boundary geometry (PostGIS)
The API returns standardized error responses:
{
"success": false,
"error": "Error description"
}Common HTTP status codes:
200: Success400: Bad Request (invalid parameters)404: Not Found (ZIP code not found)500: Internal Server Error
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
This API uses ZIP code data from the GeoNames geographical database. The data includes comprehensive information about US ZIP codes including geographical coordinates, administrative boundaries, and demographic information.