How to Build a Workout App
Using a REST API
A complete guide to integrating the WorkoutX exercise database API into your fitness application. Covers authentication, fetching exercises, displaying GIF animations, and filtering by muscle group — with real code examples in JavaScript, Python, and Swift.
What You'll Build
By the end of this guide, your app will be able to: fetch a list of exercises from a REST API, filter them by body part or target muscle, display animated GIF previews for each exercise, and handle pagination for large result sets. We'll use the WorkoutX API — a free exercise database with 1,300+ exercises and GIF animations.
Prerequisites: Basic knowledge of HTTP requests and JSON. No specific framework required — examples work in vanilla JavaScript, Node.js, Python, or any language that can make HTTP requests.
Step 1: Get Your API Key
Every request to the WorkoutX API requires an API key in the X-WorkoutX-Key header. The free plan gives you 500 requests per month with no credit card required — enough to prototype and build your MVP.
- Go to workoutxapp.com/dashboard and create a free account
- Your API key appears instantly in the Developer Portal
- Format:
wx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Step 2: Make Your First Request
The base URL for all API calls is https://api.workoutxapp.com/v1. Pass your key in the request header. Here's a minimal example that fetches 5 exercises:
const API_KEY = 'wx_your_key_here'; const BASE_URL = 'https://api.workoutxapp.com/v1'; async function fetchExercises() { const response = await fetch(`${BASE_URL}/exercises?limit=5`, { headers: { 'X-WorkoutX-Key': API_KEY } }); if (!response.ok) { throw new Error(`API error: ${response.status}`); } const exercises = await response.json(); // exercises is an array of exercise objects console.log(exercises); return exercises; } fetchExercises();
import requests API_KEY = 'wx_your_key_here' BASE_URL = 'https://api.workoutxapp.com/v1' def fetch_exercises(limit=5): response = requests.get( f'{BASE_URL}/exercises', headers={'X-WorkoutX-Key': API_KEY}, params={'limit': limit} ) response.raise_for_status() return response.json() exercises = fetch_exercises() for ex in exercises: print(f"{ex['name']} — {ex['bodyPart']}")
Understanding the Response Shape
Each exercise object contains the following fields:
{
"id": "0001",
"name": "3/4 sit-up",
"bodyPart": "waist",
"target": "abs",
"equipment": "body weight",
"gifUrl": "https://cdn.workoutxapp.com/gifs/0001.gif",
"effortLevel": "beginner",
"caloriesBurnPerMin": 4.2,
"instructions": [
"Lie down on your back...",
"Bend your knees..."
]
}
Step 3: Filter by Body Part
The most common use case is letting users pick a body part (chest, back, shoulders, etc.) and see relevant exercises. Use the /exercises/bodyPart/:bodyPart endpoint:
async function getExercisesByBodyPart(bodyPart) { // Valid: chest, back, waist, shoulders, upper arms, // lower arms, upper legs, lower legs, cardio, neck const url = `${BASE_URL}/exercises/bodyPart/${encodeURIComponent(bodyPart)}?limit=20`; const res = await fetch(url, { headers: { 'X-WorkoutX-Key': API_KEY } }); return res.json(); } // Example: get 20 chest exercises const chestExercises = await getExercisesByBodyPart('chest');
Step 4: Display GIF Animations
The gifUrl field points to a hosted GIF animation for each exercise. Use lazy loading to avoid loading all GIFs at once — especially important on mobile:
function renderExerciseCard(exercise) { return ` <div class="exercise-card"> <img src="${exercise.gifUrl}" alt="${exercise.name} exercise animation" loading="lazy" width="320" height="320" /> <div class="exercise-info"> <h3>${exercise.name}</h3> <span class="badge">${exercise.bodyPart}</span> <span class="badge">${exercise.target}</span> <span class="badge">${exercise.equipment}</span> <p class="effort">${exercise.effortLevel} · ~${exercise.caloriesBurnPerMin} cal/min</p> </div> </div> `; } // Render all exercises const container = document.getElementById('exercises'); container.innerHTML = exercises.map(renderExerciseCard).join('');
Step 5: Filter by Target Muscle
For more granular control, use the target muscle endpoint. This is useful when your users want to specifically target biceps, triceps, or quads:
async function getExercisesByTarget(target) { // Examples: abs, biceps, triceps, quads, glutes, // hamstrings, calves, deltoids, lats, pecs const url = `${BASE_URL}/exercises/target/${encodeURIComponent(target)}?limit=15`; const res = await fetch(url, { headers: { 'X-WorkoutX-Key': API_KEY }}); return res.json(); } // Get available target muscles async function getTargetList() { const res = await fetch(`${BASE_URL}/exercises/targetList`, { headers: { 'X-WorkoutX-Key': API_KEY } }); return res.json(); // Returns array of target muscle names }
Step 6: Handle Pagination
The database has 1,300+ exercises. Use limit and offset to paginate results efficiently. A "Load More" pattern works well for fitness apps:
let offset = 0; const PAGE_SIZE = 12; async function loadMore(bodyPart) { const url = `${BASE_URL}/exercises/bodyPart/${bodyPart}` + `?limit=${PAGE_SIZE}&offset=${offset}`; const res = await fetch(url, { headers: { 'X-WorkoutX-Key': API_KEY }}); const exercises = await res.json(); appendExercises(exercises); offset += PAGE_SIZE; // Hide button if fewer results returned than page size if (exercises.length < PAGE_SIZE) { document.getElementById('load-more').style.display = 'none'; } } // Initialize on page load loadMore('chest');
Step 7: Search Exercises by Name
Let users search for a specific exercise by name using the name query parameter on the main endpoint:
async function searchExercises(query) { const url = `${BASE_URL}/exercises/name/${encodeURIComponent(query)}`; const res = await fetch(url, { headers: { 'X-WorkoutX-Key': API_KEY }}); return res.json(); } // Debounce for search input let searchTimeout; document.getElementById('search').addEventListener('input', (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { searchExercises(e.target.value); }, 300); });
Error Handling Best Practices
Always handle the common API error codes your app may encounter:
async function apiRequest(endpoint) { try { const res = await fetch(`${BASE_URL}${endpoint}`, { headers: { 'X-WorkoutX-Key': API_KEY } }); switch (res.status) { case 200: return res.json(); case 401: throw new Error('Invalid API key'); case 429: throw new Error('Rate limit exceeded — upgrade your plan'); case 404: throw new Error('Exercise not found'); default: throw new Error(`API error ${res.status}`); } } catch (err) { console.error('WorkoutX API:', err.message); throw err; } }
Complete Minimal App
Here's a working single-file HTML app that ties everything together — a body part picker with exercise cards and lazy-loaded GIFs:
<!DOCTYPE html> <html lang="en"> <head> <title>My Workout App</title> <style> .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px,1fr)); gap: 16px; } .card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; } .card img { width: 100%; height: 200px; object-fit: cover; } .card-body { padding: 12px; } .filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px; } button { padding: 8px 16px; border-radius: 6px; cursor: pointer; border: 1px solid #ddd; } button.active { background: #4f46e5; color: white; border-color: #4f46e5; } </style> </head> <body> <h1>Exercise Library</h1> <div class="filters" id="filters"></div> <div class="grid" id="exercises"></div> <script> const KEY = 'wx_your_key_here'; const BASE = 'https://api.workoutxapp.com/v1'; const PARTS = ['chest','back','shoulders','waist','upper arms','upper legs']; async function load(part) { const res = await fetch( `${BASE}/exercises/bodyPart/${encodeURIComponent(part)}?limit=12`, { headers: { 'X-WorkoutX-Key': KEY } } ); const data = await res.json(); document.getElementById('exercises').innerHTML = data.map(ex => ` <div class="card"> <img src="${ex.gifUrl}" alt="${ex.name}" loading="lazy" /> <div class="card-body"> <h3>${ex.name}</h3> <p>${ex.target} · ${ex.equipment}</p> </div> </div> `).join(''); } // Render filter buttons document.getElementById('filters').innerHTML = PARTS.map(p => ` <button onclick="load('${p}')">${p}</button> `).join(''); load('chest'); // default </script> </body> </html>
Next Steps
You now have a working exercise browser. Here's where to go from here:
- Add workout builder: Let users save exercises into a "workout" and track sets/reps with localStorage
- Filter by equipment: Use
/exercises/equipment/:equipmentfor home vs. gym workouts - Sort by calories: Add
sortMethod=caloriesBurnPerMin&sortOrder=descendingto rank exercises by caloric burn - Search by name: Implement
/exercises/name/:namefor fuzzy name search - Upgrade your plan: When you're ready to scale, the Pro plan gives you 10,000 requests/month for $15.99
Full API reference: See all available endpoints, parameters, and response schemas in the WorkoutX API Documentation.
Ready to start building?
Get your free API key in 30 seconds. 500 requests/month, no credit card required.