{"id":17552574,"url":"https://github.com/iloveitaly/zip-code-database","last_synced_at":"2026-02-18T23:01:43.249Z","repository":{"id":190482579,"uuid":"682356016","full_name":"iloveitaly/zip-code-database","owner":"iloveitaly","description":"Database of zip codes and their lat/lng, sourced from the census database","archived":false,"fork":false,"pushed_at":"2025-11-22T17:36:55.000Z","size":7252,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-22T18:21:26.451Z","etag":null,"topics":["census-data","database","zipcode-data"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iloveitaly.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["iloveitaly"]}},"created_at":"2023-08-24T02:04:00.000Z","updated_at":"2025-11-22T17:36:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"120be637-e0b7-42e3-8e96-a91c1889c6f8","html_url":"https://github.com/iloveitaly/zip-code-database","commit_stats":null,"previous_names":["iloveitaly/zip-code-database"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/iloveitaly/zip-code-database","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iloveitaly%2Fzip-code-database","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iloveitaly%2Fzip-code-database/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iloveitaly%2Fzip-code-database/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iloveitaly%2Fzip-code-database/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iloveitaly","download_url":"https://codeload.github.com/iloveitaly/zip-code-database/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iloveitaly%2Fzip-code-database/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29597853,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T22:25:43.180Z","status":"ssl_error","status_checked_at":"2026-02-18T22:25:42.766Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["census-data","database","zipcode-data"],"created_at":"2024-10-21T05:43:14.322Z","updated_at":"2026-02-18T23:01:43.235Z","avatar_url":"https://github.com/iloveitaly.png","language":"Python","funding_links":["https://github.com/sponsors/iloveitaly"],"categories":[],"sub_categories":[],"readme":"# Zip Code Database Based on Census Data\n\nI wanted a database of all zip codes in the country, with their lat \u0026 long. And I wanted it to be easy to import and easy to update.\n\n[I found this gist](https://gist.github.com/abatko/ee7b24db82a6f50cfce02afafa1dfd1e), which was great and did most of the hard work, but I wanted to:\n\n1. Put the scripts in a single location\n2. Have the database in dolthub for easy querying, updating, and sharing\n3. Be able to easily update the zip code data\n\nLater on, I wanted the population in the database as well, so I added that in.\n\n## API\n\nThe cool thing about dolthub is you can use it as an API. Get a random zip code:\n\n```shell\nhttp \"https://www.dolthub.com/api/v1alpha1/iloveitaly/zip_codes_with_lat_and_lng/main?q=SELECT+*%0AFROM+%60zip_codes%60%0AORDER+BY+RAND%28%29%0ALIMIT+1%3B\"\n```\n\nEven better, get a random zip code by population:\n\n```shell\nhttps://www.dolthub.com/api/v1alpha1/iloveitaly/zip_codes_with_lat_and_lng/main?q=SELECT+*%0AFROM+%28%0A++++SELECT+*%0A++++FROM+%60zip_codes%60%0A++++ORDER+BY+%60population%60+DESC%0A++++LIMIT+100%0A%29+AS+top_100%0AORDER+BY+RAND%28%29%0ALIMIT+1%3B\n```\n\nHere's an example with python:\n\n```python\ndef get_random_lat_lng() -\u003e Tuple[float, float]:\n    \"\"\"Fetch a random lat/lng from the dolthub API.\"\"\"\n    url = \"https://www.dolthub.com/api/v1alpha1/iloveitaly/zip_codes_with_lat_and_lng/main\"\n    # pick the top 100 most populous zip codes\n    params = {\n        \"q\": \"\"\"\nSELECT *\nFROM (\n    SELECT *\n    FROM `zip_codes`\n    ORDER BY `population` DESC\n    LIMIT 100\n) AS top_100\nORDER BY RAND()\nLIMIT 1;\n              \"\"\"\n    }\n\n    try:\n        log.info(\"Fetching random lat/lng from dolthub API...\")\n        response = httpx.get(url, params=params)\n        response.raise_for_status()\n        data = response.json()\n\n        if data.get(\"rows\") and len(data[\"rows\"]) \u003e 0:\n            row = data[\"rows\"][0]\n            lat = float(row.get(\"lat\"))\n            lng = float(row.get(\"lng\"))\n            zip_code = row.get(\"zip\")\n            log.info(f\"Got random location: ZIP {zip_code}, lat={lat}, lng={lng}\")\n            return lat, lng\n        else:\n            raise ValueError(\"No rows found in response\")\n\n    except Exception as e:\n        log.error(f\"Error fetching random lat/lng: {e}\")\n        # Fallback to NYC coordinates\n        return 40.7128, -74.0060\n```\n\n## How this works\n\n1. Download the latest zip code database (\"gazetteer\") from the Census Bureau. This contains lat/lng and zip code data.\n2. Transform this data and import it into a Dolthub database.\n3. Separately, manually download population data from the Census Bureau.\n4. Transform this data, import into dolt, and then update the existing zip code data with the population data.\n\nThe entrypoint to this process is `bin/download-gazetteer`.\n\n## Formats\n\n* JSON\n* CSV\n* CSV with PK ID\n* SQL (mysql dialect)\n* **Enhanced formats** (after running `download-zcta-place`):\n  - CSV with city/state columns (may have limited data) (`zip_codes_with_city_state_pk.csv`)\n  - SQL with city/state columns (may have limited data) (`zip_codes.sql`)\n  - JSON with city/state columns (may have limited data) (`zip_codes.json`)\n  - **Note**: City/state data may be incomplete due to Census file format limitations\n\n## Latest Zipcode Data\n\nWhere did this data come from?\n\n* \u003chttps://www.census.gov/geographies/reference-files/time-series/geo/gazetteer-files.html\u003e\n* \"ZIP Code Tabulation Areas\"\n\nThis is documented in the `bin/download-gazetteer` script.\n\n## City/State Data\n\nThis project attempts to provide city and state information for each ZIP code.\nThe data is sourced from Census Bureau's ZCTA-to-Place relationship files and Place Gazetteers.\n**Limitations**: Due to complexities and inconsistencies in Census data formats, the city/state mapping might be incomplete for some ZIP codes.\nYou can find this enhanced data in the various output formats, including the SQLite database and the `zip_codes_with_city_state_pk.csv` file.\n\n## DoltHub\n\nThe data is available on Dolthub here:\n\n\u003chttps://www.dolthub.com/repositories/iloveitaly/zip_codes_with_lat_and_lng\u003e\n\n## API Server\n\nThis project includes a lightweight Python FastAPI server for querying the data locally or in a container.\n\n### Endpoints\n\n- `GET /random`: Returns a random zip code.\n- `GET /nearest?lat=...\u0026lng=...`: Returns the nearest zip code to the provided coordinates.\n- `GET /{zip_code}`: Returns details for a specific zip code.\n\n### Docker\n\nYou can build a Docker image containing the server and the latest database:\n\n```bash\njust docker\n```\n\nAlternatively, you can pull the pre-built image from GitHub Container Registry (GHCR):\n\n```bash\ndocker pull ghcr.io/iloveitaly/zip-code-database:latest\n```\n\nRun the container:\n\n```bash\ndocker run -p 8000:8000 ghcr.io/iloveitaly/zip-code-database:latest\n```\n\nThe API will be available at `http://localhost:8000`.\n\n**API Overview (Examples):**\n\n- **Get a random zip code:**\n  ```bash\n  curl http://localhost:8000/random\n  ```\n\n- **Get nearest zip code by coordinates (lat,lng):**\n  ```bash\n  curl http://localhost:8000/39.7301,-104.9078\n  # Or using query parameters:\n  # curl \"http://localhost:8000/nearest?lat=39.7301\u0026lng=-104.9078\"\n  ```\n\n- **Get details for a specific zip code:**\n  ```bash\n  curl http://localhost:8000/19335\n  ```\n\n### Docker Compose Example\n\nFor easily deploying and managing the API server, a `docker-compose.yml` file is provided:\n\n```yaml\nservices:\n  zip_code_api:\n    image: ghcr.io/iloveitaly/zip-code-database:latest\n    container_name: zip_code_api\n    restart: unless-stopped\n    ports:\n      - \"8000:8000\"\n    labels:\n      - \"com.centurylinklabs.watchtower.enable=true\"\n    networks:\n      - default\n\nnetworks:\n  default:\n    driver: bridge\n```\n\nTo run the API server using Docker Compose, navigate to the project root directory and execute:\n\n```bash\ndocker compose up -d\n```\n\nThe API will then be accessible at `http://localhost:8000`.\n\n## Models\n\n### SQLModel / ActiveModel\n\n```python\nfrom activemodel import BaseModel\nfrom sqlmodel import Field\n\n\nclass ZipCode(BaseModel, table=True):\n    \"\"\"Represents a US zip code with location and population.\"\"\"\n\n    id: int = Field(primary_key=True)\n    zip: str = Field(unique=True, index=True)\n    lat: float\n    lng: float\n    population: int | None = None\n```\n\nHere's a alembic migration to import:\n\n```python\n\"\"\"add_zip_code_to_database\n\nRevision ID: 9f2ad5bb714e\nRevises: 48c4c8ca899b\nCreate Date: 2025-08-06 13:06:52.530393\n\n\"\"\"\nimport csv\nfrom typing import Sequence, Union\n\nfrom alembic import op\nimport sqlalchemy as sa\nimport sqlmodel\nimport activemodel\nfrom sqlmodel import Session\nfrom activemodel.session_manager import global_session\n\nimport app\nfrom app import log\nfrom app.models.zip_code import ZipCode\n\n\n# revision identifiers, used by Alembic.\nrevision: str = '9f2ad5bb714e'\ndown_revision: Union[str, None] = '48c4c8ca899b'\nbranch_labels: Union[str, Sequence[str], None] = None\ndepends_on: Union[str, Sequence[str], None] = None\n\n\ndef load_zip_codes_from_csv(session: Session):\n    csv_path = app.root / \"data/zip_code_with_population.csv\"\n    rows = csv_path.read_text().splitlines()\n    reader = csv.DictReader(rows)\n\n    log.info(\"loading zip codes from csv\", path=str(csv_path))\n\n    zip_codes = []\n\n    for row in reader:\n        # Skip rows with missing required fields\n        if not row[\"id\"] or not row[\"zip\"] or not row[\"lat\"] or not row[\"lng\"]:\n            continue\n\n        # Some population fields may be empty\n        population = int(row[\"population\"]) if row[\"population\"] else None\n\n        zip_code = ZipCode(\n            id=int(row[\"id\"]),\n            zip=row[\"zip\"],\n            lat=float(row[\"lat\"]),\n            lng=float(row[\"lng\"]),\n            population=population,\n        )\n        zip_codes.append(zip_code)\n\n    session.add_all(zip_codes)\n    session.commit()\n\n\ndef remove_all_zip_codes():\n    ZipCode.where().delete()\n\n\ndef upgrade() -\u003e None:\n    session = Session(bind=op.get_bind())\n\n    load_zip_codes_from_csv(session)\n\n    # flush before running any other operations, otherwise not all changes will persist to the transaction\n    session.flush()\n\n\ndef downgrade() -\u003e None:\n    session = Session(bind=op.get_bind())\n\n    with global_session(session):\n        remove_all_zip_codes()\n\n    # flush before running any other operations, otherwise not all changes will persist to the transaction\n    session.flush()\n\n```\n\n## Updating\n\n1. Update download URL in `bin/download-gazetteer\n2. Run `bin/download-gazetteer`\n3. Profit\n\n## Links\n\n* https://tools.usps.com/zip-code-lookup.htm?citybyzipcode\n* https://radar.cloudflare.com/ip","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filoveitaly%2Fzip-code-database","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Filoveitaly%2Fzip-code-database","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filoveitaly%2Fzip-code-database/lists"}