# Exercise: Dog Breed Explorer

Finish an animal shelter's dog breed explorer, writing the functions that read the Dog CEO API's breed list, build a photo's address and load a photo with friendly errors, with offline tests for the functions and a checklist for the page's loading, empty and error states.

Journeyman · JavaScript · REST API · Web Development · Updated October 30, 2026

Riverbend Animal Shelter has dogs of every shape and size, and the people who come to adopt often ask the same question: "What does a Shiba Inu actually look like?" The shelter's volunteers have built a **Dog Breed Explorer** page: a search box, a button for every breed, and a panel for a photo. It already loads the breed list live from the Dog CEO API. What's missing is the part that talks to the API about photos, and what the page says while it waits or when something goes wrong. You'll write it, and tests will check your functions without even touching the internet.

## Before you start

You need VS Code with Live Preview, a browser and Node.js, which *Setup: VS Code, a Browser and Node.js* installs. The exercise uses `fetch`, `response.ok` and `AbortSignal.timeout` from this chapter, `try`/`catch` (*Handling Errors with try, catch and finally*), and `import` and `export` (*Modules: import and export*). Nothing from any other exercise is needed: the folder has the whole page.

## Get the exercise code

In the **Exercise** box beside the lesson, choose **Get the starter on GitHub** to open its GitHub repository. Click **Code**, then **Download ZIP**, and unzip it into your course folder. Or clone it with Git.

The folder holds:

- `index.html` and `styles.css`: the explorer page and its look. Ready to use: you don't write any HTML or CSS.
- `saved-breeds.js`: a saved copy of the API's breed list. The page uses it when the live list can't load, and the tests use it so they run offline. Ready to use.
- `dogs.js`: three functions that work with the Dog CEO API, with no page involved. For you to finish.
- `app.js`: connects the page to `dogs.js`. Loading the breed list and drawing the buttons are ready to use; two TODOs are for you to finish.
- `dogs.test.js`: the tests for `dogs.js`. You don't need to change this file.
- `package.json` and `README.md`: ready to use. The README has the same steps as this lesson, and the page checklist.

Open the folder in VS Code (**File > Open Folder**).

## What to do

The comment at the top of `dogs.js` says what each function should do, with examples. Each one returns an empty value for now:

```javascript
export function breedNames(apiData) {
  return [];
}

export function imageUrlFor(breed) {
  return '';
}

export async function loadRandomPhoto(breed, fetchFn = fetch) {
  return '';
}
```

1. **`breedNames(apiData)`** takes the API's whole answer, `{ message: { ... }, status: 'success' }`, and gives back the breed names as an array in alphabetical order.
2. **`imageUrlFor(breed)`** gives back the random-photo address for a breed, such as `https://dog.ceo/api/breed/beagle/images/random`, after tidying the name: no spaces at the ends, all lowercase.
3. **`loadRandomPhoto(breed, fetchFn = fetch)`** asks for a photo with an 8-second timeout and gives back its address. When something goes wrong, it throws an error whose message a visitor understands: one for a `404`, one for any other failing status, one for a timeout, and one for no answer at all. The exact words are in the comment.
4. **Finish `app.js`.** TODO 1 is the search's empty state (*No breeds match "zebra". Try fewer letters.*). TODO 2 is the photo panel: a loading message, then the photo, or the error's message and a **Try again** button.
5. **Stretch goal:** make the **Surprise me** button show a photo of a random breed.

**What's `fetchFn = fetch`?** The `= fetch` is a default parameter: when the page calls `loadRandomPhoto('beagle')` with one argument, `fetchFn` is the real `fetch`. The tests call it with a second argument, a **fake fetch**: a small function that answers the way the Dog CEO API would (a `200` with a photo, a `404`, a timeout) without going online. A stand-in like that is called a **test double**, and it lets the tests try every failure on purpose, every time.

## Check your work

Open VS Code's terminal (**Terminal > New Terminal**) and run the tests:

```bash
node --test
```

At first all 11 tests fail. That's expected: each function you finish turns its tests green. When a test fails, its message says what it expected, such as *A 404 is still an answer, so fetch doesn't reject: check response.ok, then response.status.*

Then open `index.html` with Live Preview in your browser (**Live Preview: Show Preview (External Browser)** from the Command Palette) and go through the checklist in `README.md`. It has you search for `terr` and `zebra`, fetch a beagle, switch the Network tab to **Offline** to see your error state and **Try again**, and break the breed list's address on purpose to see the saved copy take over. The page has to be opened with Live Preview: opened straight from its file, it can't load its modules.

## Hints

- **The names are the keys.** The breed list's `message` is an object with one key per breed, and *Getting Data with fetch* shows a way to get an object's keys as an array. Then put them in order.
- **Keep the try small.** In `loadRandomPhoto`, put `try`/`catch` around the `fetchFn` call only. If the `throw` for a bad status sits inside the same `try`, your own error lands in your own `catch`, and a `404` gets reported as "couldn't reach".
- **Status first, body second.** Check `response.ok` before `response.json()`. When it's `false`, `response.status` tells a `404` from the rest (*When fetch Fails*).
- **The error's name tells them apart.** A timeout rejects with an error named `TimeoutError`; anything else that rejects means there was no answer.
- **The page's states.** In `showPhoto`, set the loading message and hide the photo and **Try again** before the `await`, as in *Loading, Empty and Error States*. Then `try` for the photo and `catch` for the message.

## When you're done

Every test is green, and the summary at the end of the run says:

```text
ℹ tests 11
ℹ suites 0
ℹ pass 11
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
```

On the page, clicking *beagle* shows *Fetching your beagle photo...*, then a beagle, and the panel says:

```text
Here's your beagle! Pick it again for another photo.
```

With the Network tab set to **Offline**, clicking *pug* says *We couldn't reach the dog photo service. Check your internet connection.* and shows **Try again**.

Now look back at your `loadRandomPhoto`. The tests tried a `404`, a `500`, a timeout and no internet, in a fraction of a second, without a network. How long would it take to check all four by hand, on a real connection, every time you changed the function?

## So what

Live data, a timeout, a message for every failure and a fake for the tests: that's how professional apps call APIs, from a weather widget to a banking app, and now the shelter's visitors can meet any breed in a click.

## Key terms

- [Fetch API](https://www.ask-codey.com/glossary/fetch-api)
- [Response Object](https://www.ask-codey.com/glossary/response-object)
- [Status Code](https://www.ask-codey.com/glossary/status-code)
- [Test Double](https://www.ask-codey.com/glossary/test-double)
- [Default Parameter](https://www.ask-codey.com/glossary/default-parameter)

## Related

- [What a Web API Is](https://www.ask-codey.com/lessons/javascript-web-apis-what-is-an-api)
- [Getting Data with fetch](https://www.ask-codey.com/lessons/javascript-fetch-getting-data)
- [When fetch Fails](https://www.ask-codey.com/lessons/javascript-fetch-when-it-fails)
- [Loading, Empty and Error States](https://www.ask-codey.com/lessons/javascript-fetch-loading-and-error-states)

Source: https://www.ask-codey.com/lessons/exercise-javascript-dog-breed-explorer
