Designing with APIs

30m

Intermediate

Manually entering data into a prototype can be frustratingly slow. I recently felt this pain while building a music player in Framer that displayed a grid of 8 albums. To make this prototype believable, I needed to enter album artwork, artist info, and track names. I was looking at over 100 pieces of information I needed to type in. No thanks.

That’s why, when I design information-dense interfaces, I prefer to find an API that can feed my prototype the data it needs. There is a significant upfront cost but once I got the hang of working with APIs, I never looked back.

In this guide, I will teach you how to:

  1. Fetch data from an API
  2. Turn an API’s response into JSON
  3. Map JSON data onto components

We’ll be using an API found at realdata.dev to build a music player prototype. You can get the final project file here.

First, what is an API?

An API is an interface for one computer to talk to another computer. Because computers don’t need visual elements (like buttons) to communicate, APIs are basically just strings of text sent back and forth—request and response.

Request and Response

Let’s say you wanted to play Taylor Swift’s latest album on Spotify. You’d probably do something like this:

  1. Sign in to Spotify
  2. Search for “Taylor Swift”
  3. Click on the album “Lover”
  4. Press the play button

Spotify’s API follows a similiar flow but instead of clicking on visual elements, the code makes requests to specific URL endpoints:

  1. Authenticate your application with https://api.spotify.com
  2. Search for “Taylor Swift” via /search/?q=taylor%20swift
  3. Get a list of her albums via /artists/06HLXpf02/albums
  4. Select the first album and play via /player/play

Secrets, Keys, and Tokens

Just like human interfaces require a user to sign in first, APIs also expect each incoming request to have the proper authorization.

Requests usually fall into one of two buckets:

1. Public ActionsUnsplash API

When an API responds with data that isn’t tied to a specific user, it typically only requires a single key. Each developer gets a unique key which lets the API monitor their requests and limit the amount of requests one developer can make in a given period of time.

2. User ActionsDropobx API

Any time an API reads user data or writes data on behalf of a user, it likely follows the OAuth protocol. In this model, the application needs to get the user’s consent by directing them to a special URL. In fact, any time you signed in to an application with Google, you went through an OAuth flow. What you don’t see is that after you accept, Google sends the developer a unique token that can be used to make API requests on your behalf.

Even within these two authentication buckets, you’ll find small differences. Some APIs require you to refresh your token every hour and others give you permanent tokens. Some expect credentials to be sent the URL like /?client_id=3d0dpl15v and others look for credentials in the request header.

SDKs Make Life (a bit) Easier

Thankfully, mature APIs often have Software Development Kits that help with managing tokens and formatting requests. For example, Dropbox has a Javascript SDK that makes it possible to pull down a list of folder with just dropbox.filesListFolder().

Well, it’s not that simple. You still need to create a Dropbox developer account, get the user’s access token, and initialize your connection.

An API for Designers

Unfortunately, the pain of setting up an API often overshadows the reward.

That’s why I built realdata.dev—a dead-simple API that lets you pull basic data sets into your prototype without any secret keys, user tokens, or complex headers.

1. Fetching the Data

To build our music player, we need a list of albums. I’m in a Taylor Swift mood so I went ahead and added all her albums to realdata.dev. We can get them with the fetch() function like this:

fetch("https://api.realdata.dev/v1/taylor-swift-discography")
COPY

Fetch Returns a Promise

While fetch() waits patiently for the API to respond, the rest of our code rushes on without a care. If we try to use the data before the API responds, we’ll get undefined.

const albumData = fetch("https://api.realdata.dev/v1/taylor-swift-discography")
console.log(albumData) // undefined
COPY

That’s because fetch() is an async function that returns a special object called a Promise. This Promise object automatically updates when the API responds. The idea is that fetch() promises to turn over the data once it arrives but since that could take a really long time, we shouldn’t stop everything else while we wait.

fetch("https://api.realdata.dev/v1/taylor-swift-discography")
.then(response => {
console.log("We got the data!", response)
})
COPY

When the API responds, the data is passed to the .then() method. We access the data by passing .then() a function with a single parameter: response.

Extract JSON from the Response

I should note that response contains more than just the Taylor Swift data we’re looking for. It’s actually a special Response object with properties and methods of its own. One of those methods is .json() and we’re going to use that to extract the data in a format we can work with.

fetch("https://api.realdata.dev/v1/taylor-swift-discography")
.then(response => response.json())
.then(data => {
console.log("Ok, now we really have the data!", data)
})
COPY

This is where things get a bit weird. As it turns out, the .json() method also returns a promise so we need to use another .then() to get the Taylor Swift data.

Save the JSON to State

We have the Taylor Swift data but it’s trapped inside the fetch() function. To set the JSON free, we need to save it to a new variable and tell our component to re-render.

If you read my last post on React Hooks, you’ll know that the useState() hook is a perfect tool for this job.

const [data, setData] = useState(null)
useEffect(() => {
fetch("https://api.realdata.dev/v1/taylor-swift-discography")
.then(response => response.json())
.then(data => {
setData(data)
})
}, [])
COPY

A warning about endless loops

APIs do not like being hit thousands of times per second which is exactly what will happen if you forget to wrap you fetch call in the useEffect() hook. Updating our component’s state will trigger a re-render. If each state change triggers a fetch(), and each fetch() triggers a state change, we’ll create an infinite loop that queries the API thousands of times per second. Not fun.

2. Working with JSON

Let’s recap. So far we’ve fetched Taylor Swift’s Discography data from realdata.dev, grabbed the JSON from the Response object, and stored that JSON in our component’s state.

Now we’re going to learn how to use dot notation to extract the data we need from this giant blob.

Dot notation

Imagine you were telling a coworker to open a specific file in a shared folder. You might yell out “Open the Design folder, then inside that is a Website Redesign folder, and inside that open the 3rd Sketch file.”

Similiarly, dot notation is how we tell our computer to navigate through JSON data.

Let’s take a quick break from Taylor Swift to get familiar with dot notation:

const author = {
name: {
first: 'Zach',
last: 'Johnston'
},
age: 28,
location: 'New York'
}
// Example 1
console.log(author.age) // 28
// Example 2
console.log(author.name.first) // 'Zach'
// Example 3
console.log(author.location) // 'New York'
console.log(author['location']) // 'New York'
COPY

Each item in this author object has a key-value pair. The key comes before the : and the value after. We can even nest data by putting objects and arrays in the value field.

Example 1: The key age returns the value 28.

Example 2: We can target nested items by stringing two keys together.

Example 3: Bracket notation can also be used with strings or numbers but notice there’s no . after author.

Bracket Notation and Arrays

In the examples above, we targeted keys with names, but what do we do with an array? Array items aren't key-value pairs, so to target these unnamed objects we reference their position in the array.

const authors = [
{ name: 'Zach' },
{ name: 'Brendan' },
{ name: 'Hunter' }
]
// Example 1
console.log(authors[0].name) // 'Zach'
COPY

Because arrays in JavaScript are zero-based, authors[0] returns the first item. If we want to target the name key in the first item, we can write authors[0].name.

Typically when working with arrays, we use the.map() or .forEach() method to loop through items automatically. I still find that knowing how to manually target items in an array comes in handy.

Stringing it Together

Let's bring Taylor Swift back, shall we?

const artist = {
name: 'Taylor Swift'
albums: [
{
name: 'Lover',
tracks: [
{ name: 'I Forgot That You Existed' },
{ name: 'Cruel Summer' },
{ name: 'Lover' }
]
}
]
}
console.log(artist.name) // 'Taylor Swift'
console.log(artist.albums[0].name) // 'Lover'
console.log(artist.albums[0].tracks[1].name) // 'Cruel Summer'
COPY

We can string together what we've learned about dot notation and bracket notation to get any value from this artist object. Take a look at each example above and see if you can predict what value it will return.

In the next section we're going to take these concepts to the next level by mapping the data to React components.

3. Mapping the Data

Now that we know how to work with the JSON data we fetched, let’s build our music player.

The Album Grid

We want to create a grid of albums as the home page of our app, so I created a design component in Framer that displays the album cover, album title, and artist name.

You could manually enter these values in the properties panel, but we’re going to do that in code by importing this design component.

The album array we need can be targeted with data.albums. To turn the array of album objects into a list of album components, we’re going to use the map() method.

const List = data.albums.map((album, index) => {
return (
<AlbumTile
key={album.name}
Primary={album.name}
Secondary={album.artist}
Artwork={album.artwork}
/>
)
})
COPY

The .map() method goes one by one through the array and executes this function which returns an instance of our <AlbumTile /> component. Each time it does, it will apply the album name, artist, and artwork to the component.

The Album Tracks

When a user taps on an album, we want to see a list of the album’s tracks. Let’s start by mapping a single album’s track data to this <TrackRow /> design component.

To target the track list for just one album we can write data.albums[0].tracks. Later we will dynamically target the album based on what a user has selected, but for now albums[0] is a quick way to test one album.

import * as React from "react"
import { Stack } from "framer"
import { TrackRow } from "./canvas"
function AlbumDetail({ data }) {
const TrackList = data.albums[0]tracks.map((track, index) => {
return (
<TrackRow
key={track.name}
Title={track.name}
Number={index + 1 + ""}
Length={track.length}
/>
)
})
return <Stack>{TrackList}</Stack>
}
COPY

Looking at the code above, you might be wondering what index + 1 + "" is for.

index is a variable provided by the map() method. It contains an integer of the current iteration of the array but because arrays are zero-based we need to add 1 to it. Finally, because our design component expects a string and index is an integer, we need to convert it to a string by adding an empty string ("").

Connecting the Two

Now let’s make our prototype dynamically load track data based on the album a user taps on. To do this, we will need to replace the 0 in data.albums[0].tracks with a state value. We can call this value activeAlbum.

const [activeAlbum, setActiveAlbum] = useState(0)
const List = data.albums.map((album, index) => {
return (
<AlbumTile
key={album.name}
Primary={album.name}
Secondary={album.artist}
Artwork={album.artwork}
onTap={() => setActiveAlbum(index)}
/>
)
})
COPY

When an <AlbumTile /> component receives an onTap event, it sets the activeAlbum value to its index value. So if a user taps on the third album in the grid, setActiveAlbum(2) will fire and our new activeAlbum state will become 2 (remember arrays start from 0).

Now we can pass just the data for the active album to the album detail component with data.albums[activeAlbum].

<AlbumDetail data={data.albums[activeAlbum]} />
COPY

Great, we now have an album grid that dynamically tells the album detail what set of tracks to display!

From here, we can add styles, animations, and all sorts of fun interactive elements. Check out the final project file if you want to see everything we covered in the context of a working prototype:

Wrap up

If you have any questions or hit any roadblocks, reach out to me on Twitter. You can also find help in our great communities on Spectrum and Facebook as well.