Tutorial: A Gatsby Photo Blog Powered by Flickr
Tags: tutorial, javascript, gatsby, webdev
Published: 2020-06-16 16:28:43

### 🤓 What this tutorial covers

- Setting up a paginated feed of photos in Gatsby, sourced from Flickr
- [You can see the finished project here](https://www.rosey.dev/demos/basic-flickr-feed/) and [You can see its source code here](https://github.com/Rosey/flickr-gatsby-basic-example)

### 🙅‍♀️ What this tutorial does NOT cover

- Making it look nice
- Handling rebuilds when new photos are added to Flickr
- Optimizations such as using environment variables for Flickr API key, "load-up" of images for better performance, or any other nice-to-have you can think of. This is bare-bones. The expectation is that you'd use this tutorial to get started, then tidy it up and make it exactly how you want it 💖

### 🤯 Huh what who? Where am I?

[Gatsby](http://gatsbyjs.org) is a very popular framework for building static websites. I chose it for this tutorial because of its popularity. Popular frameworks = more resources to learn from and available plugins to use. And this tutorial will provide more value if it's something many people use!

[Flickr](https://www.flickr.com) is a photo hosting product. I chose it because I have nostalgic fondness for Flickr and they do allow you to embed their photos, as long as you [follow their terms](https://www.flickr.com/help/terms/api).

### 🧐 Are you an expert?

NO! I am a JavaScript developer and I do know React, but otherwise all the technologies in this tutorial are pretty new to me. 

I built out this project over a couple of hours. I've dabbled in Gatsby but don't really know it all that well. Same goes for GraphQL. I've never used the Flickr API before. So if I can fumble my way through this and then live to write a tutorial, so can you 😃

### 🥳 OK let's get started.

You're going to need a few things right off the bat:
- You'll need to [sign up for an account on Flickr](https://identity.flickr.com/sign-up) (free plan available)
- You'll then need to [get your own Flickr API key](https://www.flickr.com/services/apps/create/)
- You'll want to make sure you have [the Gatsby CLI (command line interface)](https://www.gatsbyjs.org/docs/quick-start) installed

### 👩‍💻 Start a new Gatsby Project

Got all that? Ok let's get down to it.

We're going to use the Gatsby CLI to create a new site. I ended up using their basic Hello World starter as my base template, because it really contains the bare minimum.

```zsh
gatsby new photo-blog https://github.com/gatsbyjs/gatsby-starter-hello-world
```

We then want to navigate into this directory

```zsh
cd photo-blog
```

And now that we're there, we'll want to install [gatsby-source-flickr](https://www.gatsbyjs.org/packages/gatsby-source-flickr/) which is what we'll be using to access the Flickr API. And this is the benefit of working with an established framework like Gatsby: Someone else has already done all the heavy lifting for us!

Depending on if you're using yarn or npm you'll add this differently. Here it is if you're using npm:

```zsh
npm install --save gatsby-source-flickr
```

Now all we're going to do is follow the setup documentation for `gatsby-source-flickr`. We need to open `gatsby-config.js` and configure our Flickr API key as well as the user ID on Flickr we want to get the photos from. `gatsby-config.js` should look something like this now:

```javascript
module.exports = {
  plugins: [
    {
      resolve: "gatsby-source-flickr",
      options: {
        api_key: "INSERT YOUR FLICKR API KEY HERE",
        user_id: "INSERT YOUR FLICKR USEER ID HERE"
      }
    }
  ],
}
```

**💁‍♀️ Hot tip: Once you have everything working as a proof-of-concept, if you want to use this in real life I'd advise looking into [environment variables](https://www.gatsbyjs.org/docs/environment-variables/) for these values, instead of saving these values directly in the config file.**

❓**Need help finding your API key?** You should have been given it after initial set up, but if you need to go back and find it, you can go to [the app garden on Flickr](https://www.flickr.com/services/) and click the link that says "Apps by you" on the right hand side.

❓ **Need help finding your user ID?** If you navigate to your photo stream and look at the URL bar, the URL looks something like this: `https://www.flickr.com/photos/USERIDHERE`. Grab that last value after `/photos/`!

-----

Now we can run

```zsh
gatsby develop
```

and then use our browser to navigate to [http://localhost:8000/___graphql](http://localhost:8000/___graphql). I _love_ this view because it gives you an overview of all the data available to you. Once Flickr is properly configured, it means we can play around and see what kind of information we can fetch from the Flickr API!

![screenshot of graphql query](https://dev-to-uploads.s3.amazonaws.com/i/xp2gg5rdzjrkhgxsmdn6.png)

For a feed of multiple photos, the interesting data is in `allFlickrPhoto` You can click around to see what kind of data is available there and then decide what you need. Or you can just keep reading to see what I ended up using 🙃

---

### ✍️ Let's set up some pages!

[Gatsby has a tutorial on adding pagination](https://www.gatsbyjs.org/docs/adding-pagination/) which is a good starting point. It's not Flickr-specific though, of course, so some tweaks need to be made. I also found [this tutorial](https://nickymeuleman.netlify.app/blog/gatsby-pagination) useful for the previous/next links in pagination.

Open up `gatsby-node.js` This is where we'll define all our page paths.

We need to know how many pages to generate. To do this we can query Flickr with our "number of photos per page" (aka 'limit') and then get the `pageInfo` which contains a handy page count.

```graphql
query {
      allFlickrPhoto(limit: 10) {
        pageInfo {
          pageCount
          currentPage
        }
      }
    }
```

Once we know how many pages there are, we can loop between 0 and totalPages and generate a path for each page. In the end, `gatsby-node.js` should look like this:


```javascript
const path = require('path');
const postsPerPage = 5; // change this value if you want a different number of posts per page.

exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions;
  const result = await graphql(
    `
    query($limit: Int!) {
      allFlickrPhoto(limit: $limit, filter: {media: {eq: "photo"}}) {
        pageInfo {
          pageCount
          currentPage
        }
      }
    }
    `
  , {limit: postsPerPage});

  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`);
    return;
  }

  const numPages = result.data.allFlickrPhoto.pageInfo.pageCount;

  Array.from({length: numPages}).forEach((_, i) => {
    createPage({
      path: i === 0 ? `/` : `/${i + 1}`,
      component: path.resolve('./src/templates/flickr-photo-list-template.js'),
      context: {
        limit: postsPerPage,
        skip: i * postsPerPage,
        numPages,
        currentPage: i + 1,
      },
    });
  });
}
```

You'll noticed that I moved `limit` into a variable to make it easier to change and have that change cascade nicely.

I also added a "filter" param so that we're only getting photos, not videos and other media.

----

Next up we need to set up `'./src/templates/flickr-photo-list-template.js'`, which is referenced here but doesn't actually exist yet.

🧙‍♀️ Create that file in the appropriate directory. 

🚮 You can also delete `pages/index.js` if you're following this tutorial, as I set the route of `/` to be page one of the Flickr photo list, so we don't need a default index page.

----

On this `flickr-photo-list-template.js` template we'll define the basic rendering of the data, as well as a GraphQL query for getting the information from Flickr we need in order to render that data.

The query will look something like this. "Skip" is for pagination (If you are having 5 photos per page and you are on page 2, skip would be "5". Page 3, skip would be "10" etc. This value was passed in when we set up `gatsby-node.js`, go check it out if you missed it.)

```graphQL
query ($skip: Int!, $limit: Int!) {
    allFlickrPhoto(limit:$limit, skip: $skip, sort: { order: DESC, fields: [dateupload_date] }, filter: {media: {eq: "photo"}}) {
      edges {
        node {
          id
          title
          url_m
          description
          photo_id
        }
      }
    }
  }
```

Then we can use the data we receive to render each individual photo by looping through them via `map` -

```javascript
render() {
    // get photos from response
    const photos = this.props.data.allFlickrPhoto.edges;

    // call `renderPhoto` method for each photo via map
    return (
      <div>
        {photos.map(renderPhoto)}
      </div>
    );
}
```

`renderPhoto` looks at an individual object in the array of results and displays its title, image, and description. You can tweak this to your heart's desire, and if you need different attributes, you can update your GraphQL query and then reference it here -

```javascript
function renderPhoto(item) {
  let photo = item.node;
  let flickrPhotoURL = `https://www.flickr.com/photos/[YOUR FLICKR USER ID HERE]/${photo.photo_id}`;
  return (
    <div key={photo.id}>
      <h1>{photo.title}</h1>
      <a href={flickrPhotoURL}>
        <img src={photo.url_m} alt={photo.title} />
      </a>
      <p>{photo.description}</p>
      <p>
        <a href={flickrPhotoURL}>View on Flickr</a>
      </p>
    </div>
  );
}
```

----

Add some pagination information and the entire file looks like this:

```javascript
import React from 'react';
import { graphql, Link } from 'gatsby';

function renderPhoto(item) {
  let photo = item.node;
  let flickrPhotoURL = `https://www.flickr.com/photos/[YOUR FLICKR USER ID HERE]/${photo.photo_id}`;
  return (
    <div key={photo.id}>
      <h1>{photo.title}</h1>
      <a href={flickrPhotoURL}>
        <img src={photo.url_m} alt={photo.title} />
      </a>
      <p>{photo.description}</p>
      <p>
        <a href={flickrPhotoURL}>View on Flickr</a>
      </p>
    </div>
  );
}

export default class PhotoList extends React.Component {

  renderPagination() {
    const { currentPage, numPages } = this.props.pageContext;
    const isFirst = currentPage === 1;
    const isLast = currentPage === numPages;
    const prevPage = currentPage - 1 === 1 ? "/" : (currentPage - 1).toString();
    const nextPage = (currentPage + 1).toString();

    return (
      <div>
        {!isFirst && (
          <Link to={prevPage} rel="prev">
            ← Previous Page
          </Link>
        )}
        {!isLast && (
          <Link to={nextPage} rel="next">
            Next Page →
          </Link>
        )}
      </div>
    );
  }

  render() {
    const photos = this.props.data.allFlickrPhoto.edges;
    return (
      <div>
        {photos.map(renderPhoto)}
        Page {this.props.pageContext.currentPage} of {this.props.pageContext.numPages}
        {this.renderPagination()}
      </div>
    );
  }
}

export const photoListQuery = graphql`
  query ($skip: Int!, $limit: Int!) {
    allFlickrPhoto(limit:$limit, skip: $skip, sort: { order: DESC, fields: [dateupload_date] }, filter: {media: {eq: "photo"}}) {
      edges {
        node {
          id
          title
          url_m
          description
          photo_id
        }
      }
    }
  }
`
```

If you now navigate to [http://localhost:8000](http://localhost:8000) you should see a feed of your photos. You have successfully set up a very basic Flickr powered photo blog! 🎊

It's up to you where you take it from here 🙂 And thanks very much for sticking with me for this long! 

❗️I already mentioned this at the beginning but I think it is worth re-iterating: If you set this up and run `gatsby build` to have this running in the wild... And then add photos to Flickr, it will not dynamically update whenever you upload a new photo. It is a _static site_. You'll need some kind of re-build process to handle this. You could check Flickr's documentation to see if Flickr has any kind of 'new photo' webhook and use it to trigger a re-build, for example.

🤓 Hopefully you found this useful and informative. Comment and let me know if there's any areas you think could be explained more thoroughly and I may tweak this post for future readers.