# How to Build a Workout by Equipment Type Using TypeScript

> Create a type-safe workout generator with TypeScript. Import exercise data, filter by equipment, and sample for a randomized routine. Build better workouts now.

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

---

**You can build a type-safe workout generator by importing the [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.schema.json). This ensures compile-time validation for all 10 multilingual instruction fields and media URLs.

```typescript
// 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.

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

```typescript
// 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.

```tsx
// 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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json).

## Summary

- **Type Safety**: Define an `Exercise` interface matching [`data/exercises.schema.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) source and [`README.md`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/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.