# How to Build a REST API with FastAPI Using the Exercises Dataset

> Learn to build a REST API with FastAPI using the exercises dataset. Load JSON, create Pydantic models, and serve static files for a powerful API.

- Repository: [Hasan Emir Yıldırım/exercises-dataset](https://github.com/hasaneyldrm/exercises-dataset)
- Tags: how-to-guide
- Published: 2026-07-31

---

**You can build a REST API with FastAPI by loading the [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json), with a corresponding JSON Schema in [`data/exercises.schema.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) – Contains all 1,324 exercise records with fields like `id`, `name`, `category`, `equipment`, and nested `instructions` objects
- [`data/exercises.schema.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.schema.json) – JSON Schema for validating new or modified exercise entries
- [`setup.html`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/setup.html) – Interactive developer guide in the repository root with backend integration examples
- `images/` – Directory containing 1,324 thumbnail JPG files
- `videos/` – 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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json). This ensures automatic documentation generation and runtime validation.

```python
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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/app/main.py), read [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) into a global list, then initialize the FastAPI application and mount static directories for media delivery.

```python
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:

```python
@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:

```python
@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:

```python
@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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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:

```bash
uvicorn app.main:app --reload

```

The API documentation automatically generates at `http://localhost:8000/docs`. Test the endpoints with these example calls:

1. **Filter by category:** `GET /exercises?category=chest&limit=20`
2. **Filter by equipment:** `GET /exercises?equipment=barbell`
3. **Retrieve specific record:** `GET /exercises/0001`
4. **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.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) at 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.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.schema.json) to ensure all requests and responses conform to the expected structure.
- **Mount media:** Serve the `images/` and `videos/` directories via `StaticFiles` to 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.html`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/setup.html) provides 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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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.