A RESTful Flask API backend for a workout tracking application used by personal trainers. The API tracks workouts and their associated exercises, with full validation at the table, model, and schema level.
- Flask 2.2.2 — web framework
- Flask-SQLAlchemy 3.0.3 — ORM (SQLite by default)
- Flask-Migrate — database migrations via Alembic
- Marshmallow 3.20.1 — serialization and schema validation
- uv — fast Python package manager
Table exercises {
id integer [primary key]
name varchar [unique, not null]
category varchar [not null]
equipment_needed boolean [not null]
}
Table workouts {
id integer [primary key]
date date [not null]
duration_minutes integer [not null]
notes text
}
Table workout_exercises {
id integer [primary key]
workout_id integer [not null, ref: > workouts.id]
exercise_id integer [not null, ref: > exercises.id]
reps integer
sets integer
duration_seconds integer
}
git clone https://github.com/pjperfect/workout-api.git
cd workout-apicurl -LsSf https://astral.sh/uv/install.sh | sh
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv synccd server
export FLASK_APP=app.py
flask db upgrade
flask db initandflask db migratehave already been run and themigrations/folder is committed to the repo. You only needflask db upgradeto apply the schema to your local database.
python seed.pyThis creates 5 exercises, 3 workouts, and 7 workout exercises.
flask run -p 5555The API runs at http://127.0.0.1:5555 by default.
| Method | Path | Description |
|---|---|---|
GET |
/workouts |
List all workouts |
GET |
/workouts/<id> |
Get a single workout with its exercises |
POST |
/workouts |
Create a new workout |
DELETE |
/workouts/<id> |
Delete a workout and its workout exercises |
// Request body
{ "date": "2024-01-15", "duration_minutes": 60, "notes": "Morning session" }
// 201 response
{ "id": 1, "date": "2024-01-15", "duration_minutes": 60, "notes": "Morning session", "workout_exercises": [] }| Method | Path | Description |
|---|---|---|
GET |
/exercises |
List all exercises |
GET |
/exercises/<id> |
Get a single exercise |
POST |
/exercises |
Create a new exercise |
DELETE |
/exercises/<id> |
Delete an exercise and its workout exercises |
// Request body
{ "name": "Bench Press", "category": "strength", "equipment_needed": true }
// 201 response
{ "id": 1, "name": "Bench Press", "category": "strength", "equipment_needed": true }| Method | Path | Description |
|---|---|---|
POST |
/workouts/<workout_id>/exercises/<exercise_id>/workout_exercises |
Add an exercise to a workout |
// Request body
{ "sets": 4, "reps": 10 }
// 201 response
{ "id": 1, "workout_id": 1, "exercise_id": 1, "sets": 4, "reps": 10, "duration_seconds": null }workout-api/
├── server/
│ ├── app.py # App entry-point and all endpoints
│ ├── models.py # SQLAlchemy models with constraints and validations
│ ├── schemas.py # Marshmallow schemas with validations
│ ├── seed.py # Database seed script
│ └── migrations/ # Flask-Migrate generated migrations
│ └── README # Migration usage instructions
├── pyproject.toml # uv dependencies
└── README.md
exercises.namemust be uniqueworkouts.duration_minutesmust be greater than 0workout_exercises.repsmust be greater than 0 if providedworkout_exercises.setsmust be greater than 0 if provided
- Exercise
namecannot be empty - Exercise
categorymust be one of:strength,cardio,flexibility,balance - Workout
duration_minutesmust be greater than 0 - Workout
dateis required - WorkoutExercise
repsmust be positive if provided - WorkoutExercise
setsmust be positive if provided
- Exercise
namecannot be empty - Exercise
categorymust be one of the valid categories - Workout
duration_minutesmust be greater than 0 - WorkoutExercise
repsmust be positive if provided - WorkoutExercise
setsmust be positive if provided