How to Build a REST API with FastAPI Using the Exercises Dataset
You can build a REST API with FastAPI by loading the data/exercises.json file at startup, creating Pydantic models that mirror the dataset schema, and mounting the images/ and videos/ directories as static file routes.
The hasaneyldrm/exercises-dataset repository contains a self-contained JSON collection of 1,324 fitness exercises with multilingual instructions, metadata, and media references. When you build a REST API with FastAPI using this dataset, you leverage FastAPI's automatic validation against the provided data/exercises.schema.json while serving thumbnail images and GIF animations directly through ASGI static file mounts.
Project Setup and Dataset Structure
Before writing endpoints, examine the repository layout to understand how data and media are organized. The dataset resides in data/exercises.json, with a corresponding JSON Schema in data/exercises.schema.json that defines the structure for validation. Media assets are stored in images/ and videos/ directories, referenced by relative paths in each exercise record.
Key files to reference:
data/exercises.json– Contains all 1,324 exercise records with fields likeid,name,category,equipment, and nestedinstructionsobjectsdata/exercises.schema.json– JSON Schema for validating new or modified exercise entriessetup.html– Interactive developer guide in the repository root with backend integration examplesimages/– Directory containing 1,324 thumbnail JPG filesvideos/– Directory containing 1,324 animation GIF files
Creating Pydantic Models from the Schema
FastAPI uses Pydantic for request and response validation. Define models that reflect the multilingual instruction structure and exercise metadata found in data/exercises.json. This ensures automatic documentation generation and runtime validation.
from pydantic import BaseModel
from typing import List
class Instructions(BaseModel):
en: str
es: str
it: str
tr: str
ru: str
zh: str
hi: str
pl: str
ko: str
fr: str
class Exercise(BaseModel):
id: str
name: str
category: str
body_part: str
equipment: str
instructions: Instructions
instruction_steps: dict
muscle_group: str
secondary_muscles: List[str]
target: str
media_id: str
image: str
gif_url: str
attribution: str
created_at: str
Loading the Dataset and Initializing the App
Load the JSON data once at startup to avoid file I/O on every request. In app/main.py, read data/exercises.json into a global list, then initialize the FastAPI application and mount static directories for media delivery.
from fastapi import FastAPI, HTTPException, Query
from fastapi.staticfiles import StaticFiles
from pathlib import Path
import json
# Load dataset once at startup
DATA_FILE = Path(__file__).parent.parent / "data" / "exercises.json"
with DATA_FILE.open(encoding="utf-8") as f:
exercises = json.load(f)
app = FastAPI(title="Exercises API")
# Serve static media files
app.mount("/images", StaticFiles(directory=Path(__file__).parent.parent / "images"), name="images")
app.mount("/videos", StaticFiles(directory=Path(__file__).parent.parent / "videos"), name="videos")
Implementing CRUD Endpoints
With the dataset loaded and models defined, expose RESTful endpoints for listing, retrieving, and creating exercises. Use FastAPI's Query parameters to implement filtering and pagination without additional libraries.
List and Filter Exercises
The GET /exercises endpoint supports filtering by category and equipment, with limit and offset for pagination:
@app.get("/exercises", response_model=list[Exercise])
def list_exercises(
category: str | None = Query(default=None),
equipment: str | None = Query(default=None),
limit: int = Query(default=100, ge=1, le=500),
offset: int = Query(default=0, ge=0),
):
filtered = exercises
if category:
filtered = [e for e in filtered if e["category"] == category]
if equipment:
filtered = [e for e in filtered if e["equipment"] == equipment]
return filtered[offset : offset + limit]
Retrieve Single Exercise
Access individual records by their unique identifier:
@app.get("/exercises/{exercise_id}", response_model=Exercise)
def get_exercise(exercise_id: str):
for ex in exercises:
if ex["id"] == exercise_id:
return ex
raise HTTPException(status_code=404, detail="Exercise not found")
Create New Exercise
Validate incoming data against the Exercise model and check for duplicate IDs before appending to the dataset:
@app.post("/exercises", response_model=Exercise, status_code=201)
def create_exercise(payload: Exercise):
if any(e["id"] == payload.id for e in exercises):
raise HTTPException(status_code=400, detail="ID already exists")
exercises.append(payload.dict())
return payload
Serving Static Media Files
The image and gif_url fields in data/exercises.json contain relative filenames rather than full URLs. By mounting the images/ and videos/ directories at /images and /videos respectively, clients can request media assets directly:
- Thumbnail:
GET http://localhost:8000/images/0001-2gPfomN.jpg - Animation:
GET http://localhost:8000/videos/0001-2gPfomN.gif
This approach keeps the API self-contained, with all assets served through the same ASGI server without external CDN dependencies.
Running and Testing the API
Start the development server using Uvicorn:
uvicorn app.main:app --reload
The API documentation automatically generates at http://localhost:8000/docs. Test the endpoints with these example calls:
- Filter by category:
GET /exercises?category=chest&limit=20 - Filter by equipment:
GET /exercises?equipment=barbell - Retrieve specific record:
GET /exercises/0001 - Access media:
GET /images/0001-2gPfomN.jpg
For production deployment, containerize the application with Docker, ensuring the data/, images/, and videos/ directories are included in the container image or mounted as volumes.
Summary
- Load once: Read
data/exercises.jsonat startup to minimize disk I/O and leverage FastAPI's async capabilities for concurrent request handling. - Validate strictly: Use Pydantic models mirroring
data/exercises.schema.jsonto ensure all requests and responses conform to the expected structure. - Mount media: Serve the
images/andvideos/directories viaStaticFilesto expose thumbnails and GIFs at predictable URLs. - Filter efficiently: Implement query parameter filtering using Python list comprehensions for datasets of this size (1,324 records), or migrate to a database for larger volumes.
- Reference setup.html: The repository's
setup.htmlprovides additional backend integration patterns and client-side testing utilities.
Frequently Asked Questions
How do I validate new exercises against the official schema?
FastAPI automatically validates incoming JSON against your Pydantic Exercise model. For additional validation against the raw data/exercises.schema.json file, use the jsonschema library in your create_exercise endpoint before appending to the dataset.
Can I filter exercises by multiple criteria simultaneously?
Yes. Extend the list_exercises function to accept additional Query parameters such as target, body_part, or muscle_group, then chain multiple list comprehensions or use a single loop with compound conditional logic to narrow results.
How do I handle the multilingual instruction fields?
The Instructions Pydantic model defines each supported language (en, es, it, tr, ru, zh, hi, pl, ko, fr) as required strings. When creating exercises, ensure all language fields are populated, or modify the model to use Optional[str] if partial translations are acceptable for your use case.
Is this implementation suitable for production workloads?
For high-traffic production environments, migrate the in-memory list to a proper database such as PostgreSQL or MongoDB. The current implementation suits prototyping, small deployments, or read-heavy workloads where the 1,324 records fit comfortably in memory.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →