Python20 min setupIntermediate

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-cors

3Setup 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    # Dependencies

4Define 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=True

6Create 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.py

The 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 code

Add 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 code

Deploy 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)

Ready to Build Your API?

Get your free API key and start building powerful location-based services.

Get Free API Key →