A small JSON API for practicing HTTP requests, URL parameters, filtering, sorting, pagination, and API-consumer exercises. The source of truth lives in GitHub and the public API will run on Cloudflare Workers.
- Public endpoint: https://api.cosmic-lab.workers.dev
- GitHub repository: https://github.com/matt22/cosmic-lab-api
- Repository visibility: public
- Production branch:
main
Repository and deployment plumbing are connected, and the initial practice datasets are complete. Versioned API endpoints are implemented for airports, cities, books, movies, incidents, and oil fields.
- The repository contains five validated, flat practice datasets in
data/: movies, cities, US airports, books, and fictional service incidents. offshore_oil_fields.jsoncontains 50 oil-field records (25 land-based and 25 ocean-based), ranked by reported daily production for API practice, split evenly between land-based and ocean-based settings.- Cloudflare Worker
apiis connected tomatt22/cosmic-lab-api. - Cloudflare KV namespace
cosmic-lab-api-query-cacheis configured as theQUERY_CACHEWorker binding inwrangler.jsonc. - Cached query result sets use dataset-specific TTL variables. All five dataset
TTLs currently default to three hours (
10800seconds). - Cloudflare watches the
mainbranch and deploys this Python Worker withuv run pywrangler deploy. GET /api/v1/airportssupports state-code filtering and fixed three-record page-based pagination.GET /api/v1/citiessupports country-code filtering and fixed ten-record page-based pagination.GET /api/v1/bookssupports case-insensitive substring searches with thetitleparameter and fixed ten-record page-based pagination.GET /api/v1/moviessupports case-insensitive substring searches with thetitleparameter and fixed ten-record page-based pagination.GET /api/v1/incidentssupports case-insensitive substring searches with theservice_nameparameter and fixed ten-record page-based pagination.- The Python Worker is deployed at https://api.cosmic-lab.workers.dev and the versioned airports endpoint has been verified in production.
- The Python Worker entry point, package manifest, and initial tests are tracked on the production branch.
Do not assume the code currently running at the endpoint exists in this Git repository. Before relying on Git-based deployment, add and test the minimal Worker project files locally.
All datasets are checked-in JSON arrays. Records are intentionally flat: fields
contain strings, numbers, or documented null values rather than nested objects
or arrays. Dataset-specific provenance, selection rules, and rebuilding notes
live in data/README.md.
| File | Records | Fields |
|---|---|---|
movies.json |
1,000 | id, title, year, runtimeMinutes, mpaaRating, scoreRating, directorLastName |
cities.json |
1,000 | id, cityName, countryCode, countryName, continent, latitude, longitude |
airports.json |
100 | id, airportName, iataCode, icaoCode, city, stateCode, stateName, countryCode, countryName, latitude, longitude |
books.json |
1,000 | id, title, author, isbn13, publicationDate, pages |
incidents.json |
100 | id, serviceName, severity, status, startTime, endTime |
offshore_oil_fields.json |
50 | id, fieldName, country, operator, latitude, longitude, wellDepthM, dailyProductionBbl, discoveryYear, basin |
The movie, city, airport, and book datasets contain sourced real-world data. Incidents are fictional and deterministic. Their null timestamps carry meaning:
- A planned incident has a null
startTimeand a futureendTimetarget. - An active incident has a populated
startTimeand a nullendTime. - A resolved incident has both timestamps, with the end after the start.
- Both incident timestamps are never null.
Validation and deterministic build scripts are kept in scripts/. Generated
JSON is committed so the Worker can eventually bundle and serve it without a
database or a build-time network dependency.
The likely first version will keep small, read-only JSON datasets in the repository and bundle them with a Cloudflare Worker:
cosmic-lab-api/
├── src/
│ └── index.js # Request routing and query processing
├── data/
│ ├── movies.json
│ ├── cities.json
│ ├── airports.json
│ ├── books.json
│ ├── incidents.json
│ └── ... # Additional practice datasets
├── test/ # Endpoint and filtering tests
├── package.json
└── wrangler.jsonc # Cloudflare Worker configuration
Example endpoints may eventually look like:
GET /api/movies
GET /api/movies?year=2020&scoreRating_gte=7
GET /api/cities?countryCode=JP&sort=cityName
GET /api/v1/cities?country_code=JP
GET /api/v1/airports?state_code=CA
GET /api/books?publicationDate_gte=2000-01-01&pages_lte=400
GET /api/incidents?endTime=null
GET /api/movies?sort=scoreRating&order=desc&limit=10
The stored data models are now defined. The airports endpoint establishes the first response envelope and pagination behavior; broader query operators and practice exercises remain undecided.
The initial endpoint requires a two-letter state_code. It accepts an optional
positive page, which defaults to 1. The airports page size is fixed at
three; other datasets can define their own pagination rules.
GET /api/v1/airports?state_code=CA
GET /api/v1/airports?state_code=CA&page=2
Airport records contain every source field except latitude and longitude.
Those two values are returned as a comma-delimited coordinates string:
{
"coordinates": "33.9425,-118.408"
}The cities endpoint requires a two-letter country_code. It accepts an
optional positive page, which defaults to 1; its page size is fixed at
10.
GET /api/v1/cities?country_code=JP
GET /api/v1/cities?country_code=JP&page=2
City records use snake_case response keys. Like airport records, latitude
and longitude are replaced by one comma-delimited coordinates string:
{
"coordinates": "35.6762,139.6503"
}Airport responses place pagination metadata before the result array:
{
"pagination": {
"page": 1,
"page_size": 3,
"count": 3,
"total": 12,
"total_pages": 4
},
"data": []
}The books endpoint requires a case-insensitive substring query and accepts an
optional positive page, which defaults to 1:
GET /api/v1/books?title=atomic
The movies endpoint requires a case-insensitive substring query and accepts an
optional positive page, which defaults to 1:
GET /api/v1/movies?title=Jurassic%20P
The incidents endpoint requires a case-insensitive substring query and accepts
an optional positive page, which defaults to 1:
GET /api/v1/incidents?service_name=gateway
GET /api/v1/offshore-oil-fields?country_code=BR
View a live incidents response.
The query values are treated as literal text. Search results use case-folded substring checks, so SQL injection syntax cannot be executed by these endpoints. Unsupported query parameters are rejected.
Run the unit tests with:
python3 -m unittest discover -s tests -vRun the Cloudflare Python Worker locally with:
uv run pywrangler dev- Inspect the response currently served by the public endpoint so its useful behavior is not accidentally lost.
- Decide whether to preserve that generated implementation or replace it.
- Change the Cloudflare build command to
uv run pywrangler deployand ensure the build environment providesuv. - Verify that a GitHub-based deployment preserves the public endpoint after the Cloudflare build command is updated.
- Define filtering, sorting, null handling, pagination, response envelopes, errors, and expected exercise results for the remaining datasets.
git pull
# make and test changes
git add <files>
git commit -m "Describe the change"
git pushPushing to main starts a Cloudflare build, so implementation changes should
be locally tested before pushing.
- Never commit API tokens, passwords,
.env,.dev.vars, or credential-bearing repository URLs. .env,.dev.vars,.wrangler/,node_modules/, and.DS_Storeare ignored.- Cloudflare's GitHub App was granted access only to this repository.
- A credential embedded in an earlier Cloudflare artifact clone URL was exposed during initial setup. Confirm that credential has been revoked or expired; never reuse it or copy it into this repository.
- Git remotes should remain credential-free. The expected remote is:
https://github.com/matt22/cosmic-lab-api.git.