How to Build a Workout by Equipment Type Using TypeScript

You can build a type-safe workout generator by importing the data/exercises.json file from the exercises-dataset repository, filtering the array on the equipment property, and sampling the results to create a randomized routine.

The hasaneyldrm/exercises-dataset repository provides a complete, open-source exercise database with 1,324 records, each containing detailed metadata including the required equipment. By leveraging TypeScript's type system alongside this JSON dataset, you can rapidly develop client-side workout builders that filter exercises by equipment—such as "barbell," "dumbbell," or "body weight"—without requiring a backend server.

Defining the Exercise Interface

Before filtering the data, create a type-safe model that mirrors the schema defined in data/exercises.schema.json. This ensures compile-time validation for all 10 multilingual instruction fields and media URLs.

// src/types.ts
export interface Exercise {
  id: string;
  name: string;
  category: string;
  body_part: string;
  equipment: string;
  instructions: {
    en: string; es: string; it: string; tr: string; ru: string;
    zh: string; hi: string; pl: string; ko: string; fr: string;
  };
  instruction_steps: {
    en: string[]; es: string[]; it: string[]; tr: string[]; ru: string[];
    zh: string[]; hi: string[]; pl: string[]; ko: string[]; fr: string[];
  };
  muscle_group: string;
  secondary_muscles: string[];
  target: string;
  media_id: string;
  image: string;
  gif_url: string;
  attribution: string;
  created_at: string;
}

This interface matches the structure documented in the repository's README under the TypeScript usage section, ensuring your types align with the actual JSON structure.

Importing the Dataset

Import the raw JSON and cast it to your defined type. This approach works in both Node.js and browser environments when using a bundler that supports JSON imports.

// src/data.ts
import exercisesRaw from "./data/exercises.json";
import { Exercise } from "./types";

export const exercises: Exercise[] = exercisesRaw as Exercise[];

The source data resides in data/exercises.json, which contains the complete array of exercise objects ready for client-side consumption.

Filtering Exercises by Equipment

The core logic for building a workout by equipment type involves filtering the imported array using Array.filter on the equipment property. Implement a case-insensitive partial match to allow flexible queries.

// src/workout.ts
import { exercises } from "./data";

/**
 * Returns a workout consisting of `count` exercises that require the
 * given equipment type.
 *
 * @param equipment - e.g. "barbell", "dumbbell", "body weight"
 * @param count - number of exercises to include
 */
export function buildWorkoutByEquipment(equipment: string, count: number = 6): Exercise[] {
  // Filter by equipment (case-insensitive partial match)
  const filtered = exercises.filter(
    ex => ex.equipment.toLowerCase().includes(equipment.toLowerCase())
  );

  if (filtered.length === 0) {
    throw new Error(`No exercises found for equipment "${equipment}"`);
  }

  // Shuffle the filtered list using Fisher-Yates algorithm
  const shuffled = [...filtered];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }

  // Return the first `count` items
  return shuffled.slice(0, Math.min(count, shuffled.length));
}

This implementation handles three critical requirements: partial matching (allowing "barbell" to match variations like "EZ Barbell"), randomization (ensuring varied workouts on each call), and error handling (providing clear feedback when no matches exist).

Rendering the Workout in React

Consume the generated workout in a React component to display GIFs, instructions, and equipment details in any of the supported languages.

// src/components/Workout.tsx
import React from "react";
import { buildWorkoutByEquipment } from "../workout";
import { Exercise } from "../types";

interface Props {
  equipment: string;
  count?: number;
  language?: keyof Exercise["instructions"];
}

export const Workout: React.FC<Props> = ({
  equipment,
  count = 6,
  language = "en",
}) => {
  const workoutExercises = React.useMemo(() => 
    buildWorkoutByEquipment(equipment, count), 
    [equipment, count]
  );

  return (
    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
      {workoutExercises.map((ex) => (
        <article key={ex.id} className="p-4 border rounded">
          <h3 className="font-semibold">{ex.name}</h3>
          <img src={ex.gif_url} alt={ex.name} className="w-full h-auto" />
          <p className="mt-2">{ex.instructions[language]}</p>
          <p className="text-sm text-gray-600">Equipment: {ex.equipment}</p>
        </article>
      ))}
    </div>
  );
};

The component uses React.useMemo to prevent unnecessary recalculations and accepts a language parameter to leverage the multilingual instruction fields available in data/exercises.json.

Summary

  • Type Safety: Define an Exercise interface matching data/exercises.schema.json to ensure compile-time validation of the 1,324-record dataset.
  • Equipment Filtering: Use Array.filter on the equipment property with case-insensitive matching to select exercises compatible with specific gear.
  • Randomization: Apply the Fisher-Yates shuffle to the filtered results to generate varied workout routines on each execution.
  • Client-Side Only: The entire workflow runs in the browser or Node.js without server dependencies, making it ideal for React, Next.js, or mobile applications.
  • Rich Media: Leverage the gif_url and multilingual instruction fields to display full-motion demonstrations and localized guidance.

Frequently Asked Questions

How do I handle equipment types that aren't listed in the dataset?

The buildWorkoutByEquipment function throws an explicit error when no exercises match the requested equipment. Wrap the call in a try-catch block to gracefully handle these cases, or implement a fallback that suggests alternative equipment types by analyzing the unique values present in data/exercises.json.

Can I filter by multiple equipment types simultaneously?

Yes. Modify the filter predicate to accept an array of equipment strings and use Array.some or Array.includes to check for multiple matches. For example, equipmentList.some(eq => ex.equipment.toLowerCase().includes(eq.toLowerCase())) will return exercises that match any item in your equipment list.

Is the dataset suitable for commercial fitness applications?

According to the data/exercises.json source and README.md, the dataset includes attribution fields for each exercise. You must preserve the attribution field values when displaying exercise data to comply with the licensing requirements, but the data structure itself is designed to support commercial implementations with proper credit.

How can I extend this to filter by muscle group and equipment together?

Chain additional filter predicates to the existing equipment filter. Access the target, muscle_group, or body_part fields on the Exercise interface to create compound filters. For example: exercises.filter(ex => ex.equipment === "dumbbell" && ex.target === "chest") will return dumbbell exercises specifically targeting the chest.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →