Build a Store Locator API with Python Flask
Create a RESTful API that finds nearby store locations using ZIP code radius search. Perfect for powering mobile apps, frontend applications, or microservices.
The Problem
Your mobile app or frontend needs an API to find nearby stores, but calculating distances and managing location data is complex. You need a reliable backend service that handles radius searches efficiently.
The Solution
Build a Flask API that uses the ZIP Code API's radius endpoint to find locations within a specified distance. The API can be deployed as a standalone service or integrated into your existing Python backend.
1Get Your API Key
Sign up on RapidAPI and get your API key for ZIP Code API:
Get Free API Key →2Install Dependencies
pip install flask requests python-dotenv flask-cors3Setup Project Structure
store-locator-api/
├── app.py # Main Flask application
├── config.py # Configuration
├── stores.py # Store data (can use DB later)
├── .env # Environment variables
└── requirements.txt # Dependencies4Define Store Data
stores.py
# Sample store data (replace with database queries in production)
STORES = [
{
"id": 1,
"name": "Downtown Store",
"address": "123 Main St",
"city": "Beverly Hills",
"state": "CA",
"zipcode": "90210",
"phone": "+1-555-0100",
"hours": "Mon-Sat 9AM-9PM, Sun 10AM-6PM"
},
{
"id": 2,
"name": "Westside Location",
"address": "456 Oak Ave",
"city": "Beverly Hills",
"state": "CA",
"zipcode": "90211",
"phone": "+1-555-0101",
"hours": "Mon-Sat 9AM-9PM, Sun 10AM-6PM"
},
{
"id": 3,
"name": "Airport Plaza",
"address": "789 LAX Blvd",
"city": "Los Angeles",
"state": "CA",
"zipcode": "90045",
"phone": "+1-555-0102",
"hours": "Mon-Sun 8AM-10PM"
},
{
"id": 4,
"name": "Beach Store",
"address": "321 Beach Dr",
"city": "Santa Monica",
"state": "CA",
"zipcode": "90401",
"phone": "+1-555-0103",
"hours": "Mon-Sun 9AM-9PM"
},
{
"id": 5,
"name": "Valley Center",
"address": "555 Valley Rd",
"city": "Sherman Oaks",
"state": "CA",
"zipcode": "91403",
"phone": "+1-555-0104",
"hours": "Mon-Sat 10AM-8PM, Sun 11AM-6PM"
}
]
def get_stores():
"""Get all stores (can be replaced with database query)"""
return STORES
def get_store_by_id(store_id):
"""Get a single store by ID"""
return next((store for store in STORES if store["id"] == store_id), None)
def get_stores_by_zipcodes(zipcodes):
"""Filter stores by list of ZIP codes"""
return [store for store in STORES if store["zipcode"] in zipcodes]5Configure Environment Variables
.env
RAPIDAPI_KEY=your_api_key_here
FLASK_ENV=development
FLASK_DEBUG=True6Create the Flask API
app.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
import os
from dotenv import load_dotenv
from stores import get_stores, get_store_by_id, get_stores_by_zipcodes
# Load environment variables
load_dotenv()
app = Flask(__name__)
CORS(app) # Enable CORS for frontend access
RAPIDAPI_KEY = os.getenv('RAPIDAPI_KEY')
RAPIDAPI_HOST = 'zip-code-api8.p.rapidapi.com'
@app.route('/api/health', methods=['GET'])
def health_check():
"""Health check endpoint"""
return jsonify({
'status': 'healthy',
'message': 'Store Locator API is running'
})
@app.route('/api/stores', methods=['GET'])
def get_all_stores():
"""Get all stores"""
try:
stores = get_stores()
return jsonify({
'success': True,
'count': len(stores),
'stores': stores
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
@app.route('/api/stores/<int:store_id>', methods=['GET'])
def get_single_store(store_id):
"""Get a single store by ID"""
store = get_store_by_id(store_id)
if store:
return jsonify({
'success': True,
'store': store
})
else:
return jsonify({
'success': False,
'error': 'Store not found'
}), 404
@app.route('/api/stores/nearby', methods=['GET'])
def find_nearby_stores():
"""Find stores near a ZIP code within a radius"""
# Get query parameters
zipcode = request.args.get('zipcode')
radius = request.args.get('radius', '25') # Default 25 miles
# Validate inputs
if not zipcode:
return jsonify({
'success': False,
'error': 'ZIP code is required'
}), 400
if not zipcode.isdigit() or len(zipcode) != 5:
return jsonify({
'success': False,
'error': 'Invalid ZIP code format'
}), 400
try:
# Call ZIP Code API radius search
url = f"https://{RAPIDAPI_HOST}/radius"
headers = {
"X-RapidAPI-Key": RAPIDAPI_KEY,
"X-RapidAPI-Host": RAPIDAPI_HOST
}
params = {
"zipcode": zipcode,
"radius": radius
}
response = requests.get(url, headers=headers, params=params, timeout=10)
response.raise_for_status()
radius_data = response.json()
# Extract ZIP codes from radius search results
nearby_zipcodes = set()
if radius_data.get('results'):
for result in radius_data['results']:
nearby_zipcodes.add(result['zipcode'])
# Filter stores by nearby ZIP codes
nearby_stores = get_stores_by_zipcodes(nearby_zipcodes)
# Enhance stores with distance information
stores_with_distance = []
for store in nearby_stores:
# Find matching radius result for distance
distance_info = next(
(r for r in radius_data.get('results', [])
if r['zipcode'] == store['zipcode']),
None
)
store_copy = store.copy()
store_copy['distance_miles'] = distance_info['distance'] if distance_info else None
stores_with_distance.append(store_copy)
# Sort by distance (closest first)
stores_with_distance.sort(
key=lambda s: s['distance_miles'] if s['distance_miles'] is not None else float('inf')
)
return jsonify({
'success': True,
'search': {
'zipcode': zipcode,
'radius': radius,
'center': radius_data.get('center')
},
'count': len(stores_with_distance),
'stores': stores_with_distance
})
except requests.exceptions.RequestException as e:
return jsonify({
'success': False,
'error': 'Failed to search radius',
'details': str(e)
}), 500
except Exception as e:
return jsonify({
'success': False,
'error': 'Internal server error',
'details': str(e)
}), 500
@app.route('/api/stores/search', methods=['GET'])
def search_stores():
"""Search stores by city, state, or ZIP code"""
city = request.args.get('city', '').lower()
state = request.args.get('state', '').upper()
zipcode = request.args.get('zipcode')
stores = get_stores()
filtered = stores
if city:
filtered = [s for s in filtered if city in s['city'].lower()]
if state:
filtered = [s for s in filtered if s['state'] == state]
if zipcode:
filtered = [s for s in filtered if s['zipcode'] == zipcode]
return jsonify({
'success': True,
'count': len(filtered),
'stores': filtered
})
@app.errorhandler(404)
def not_found(error):
return jsonify({
'success': False,
'error': 'Endpoint not found'
}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({
'success': False,
'error': 'Internal server error'
}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)7Run the API Server
python app.pyThe API will start on http://localhost:5000
Test Your API
Find Nearby Stores
curl "http://localhost:5000/api/stores/nearby?zipcode=90210&radius=25"Response:
{
"success": true,
"search": {
"zipcode": "90210",
"radius": "25",
"center": {
"city": "Beverly Hills",
"state": "CA",
"latitude": 34.0901,
"longitude": -118.4065
}
},
"count": 3,
"stores": [
{
"id": 1,
"name": "Downtown Store",
"address": "123 Main St",
"city": "Beverly Hills",
"state": "CA",
"zipcode": "90210",
"distance_miles": 0.0
},
{
"id": 2,
"name": "Westside Location",
"zipcode": "90211",
"distance_miles": 1.2
}
]
}Get All Stores
curl "http://localhost:5000/api/stores"Search by City
curl "http://localhost:5000/api/stores/search?city=Beverly%20Hills&state=CA"Production Enhancements
Add Database Support
Replace the in-memory store list with a database (PostgreSQL, MongoDB):
# Using SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
class Store(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
zipcode = db.Column(db.String(5), nullable=False)
# ... other fields
def get_stores_by_zipcodes(zipcodes):
return Store.query.filter(Store.zipcode.in_(zipcodes)).all()Add Response Caching
Cache radius search results to reduce API calls:
from flask_caching import Cache
cache = Cache(app, config={
'CACHE_TYPE': 'redis',
'CACHE_REDIS_URL': 'redis://localhost:6379/0'
})
@app.route('/api/stores/nearby', methods=['GET'])
@cache.cached(timeout=3600, query_string=True) # Cache for 1 hour
def find_nearby_stores():
# ... existing codeAdd Rate Limiting
Protect your API from abuse:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["100 per hour"]
)
@app.route('/api/stores/nearby', methods=['GET'])
@limiter.limit("20 per minute") # Max 20 requests per minute
def find_nearby_stores():
# ... existing codeDeploy to Production
Deploy using Gunicorn for production:
# Install Gunicorn
pip install gunicorn
# Run with Gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app
# Or use Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]Next Steps
- 1.Get your free API key from RapidAPI
- 2.Add your API key to .env file as RAPIDAPI_KEY
- 3.Copy the code and run the Flask server
- 4.Replace sample store data with your real store database
- 5.Test all endpoints with cURL or Postman
- 6.Deploy to production with Gunicorn and a reverse proxy (Nginx)
Related Recipes
Ready to Build Your API?
Get your free API key and start building powerful location-based services.
Get Free API Key →