loading...
Cover image for How I used the Goodreads API to pick my next read

How I used the Goodreads API to pick my next read

tara profile image Tara ・4 min read

Introduction

I like to read. A lot. I also (shockingly enough) like to code. So a little while ago when I had the idea to combine these two interests of mine, I hit up the Goodreads API docs to see what sorts of data I could get my hands on. After perusing the docs (aka doing a lot of command + f to find things because the docs aren't the most user-friendly), I decided to make a small program that would pick out a book for me to read next.

Getting Started

Since I had already picked out the language I was going to use, I dove right in and created the project directory on my Desktop and initialized it with Git.

If you're using this as a tutorial and haven't worked with JavaScript previously, you should download Node.js and get yourself a package manager. I currently use npm.

I also knew from the docs that I needed to get myself an API key. As a Goodreads user, all I had to do was login to get them. I believe non-Goodreads users will need to create an account to get access.

Getting Down to Business

For this all I needed was a list of the books I had on my to-read shelf. After the aforementioned "command + f"-ing, I found the GET request to "get the books on a members shelf" which, for no reason other than previous experience with it, led me to want to use the request promise package.

At this point, I also decided to install dotenv so I could pass in my API key and other info that I didn't want hardcoded.

I set up my .env file in the following format:

KEY=YOUR_GOODREADS_KEY
SECRET=YOUR_GOODREADS_SECRET
USER_ID=YOUR_GOODREADS_ID
VERSION=2
PER_PAGE=200
SHELF=to-read

Then I created my main.js file. My first objective was to log out what Goodreads returned from my request. Much to my surprise, there was a nice long chunk of XML in my terminal.

Gif of Lauren Conrad going "whoa!"

Lauren Conrad saying, "Whoa." via GIPHY

One of the good things about the documentation is that, if you're logged in and have an API key, when you click on the sample URL for the request you want to make it'll show you what the API returns.

var request = require('request-promise');

require('dotenv').config();

let options = {
    method: 'GET',
    uri: `https://www.goodreads.com/review/list/${process.env.USER_ID}.xml`,
    qs: {
        key: process.env.KEY,
        v: process.env.VERSION,
        shelf: process.env.SHELF,
        per_page: process.env.PER_PAGE
    }
}

request(options).then((shelf) => {
    console.log(shelf);
}).catch(err => console.error(err));

Even though I had already seen the sample URL and what it returned, I was still surprised to see the XML in terminal so I did a quick perusal of the Goodreads developer forums to see if there was a JSON endpoint for this.

Spoiler alert: there's not.

DJ from Full House sighing dramatically

DJ from Full House sighing dramatically. via GIPHY

After doing some quick searching, I decided to install xml2js so I could get the response into a more manageable and readable format.

I like to work incrementally so after requiring the new package with var xml2js = require('xml2js');, I modified my .then() block to parse the response and logged out the end result of that.

request(options).then((shelf) => {
    xml2js.parseString(shelf, function (err, result) {
        console.log(result);
    });
}).catch(err => console.error(err));

Now that I had some nice JavaScript object action going on, it was just a matter of figuring out how to access the titles within the arrays and objects.

My first step was getting access to the list of books:

let books = result['GoodreadsResponse']['reviews'][0]['review'];

The books were stored in an array which meant randomly selecting an index value was just a matter of picking a number from 0 to the last index in the array.

let index = Math.floor(Math.random() * books.length);

Normally, I like to set up intermediate variables when I'm traversing through dense objects and arrays like this but since the only thing I needed was the title and I wasn't going to be doing any more operations, I figured I'd skip the intermediate variables and put it all in the assignment for the title.

let title = books[index]['book'][0]['title'][0];

At this point, the only thing left to do was print out the title and run the app!

var request = require('request-promise');
var xml2js = require('xml2js');

require('dotenv').config();

let options = {
    method: 'GET',
    uri: `https://www.goodreads.com/review/list/${process.env.USER_ID}.xml`,
    qs: {
        key: process.env.KEY,
        v: process.env.VERSION,
        shelf: process.env.SHELF,
        per_page: process.env.PER_PAGE
    }
}

request(options).then((shelf) => {
    xml2js.parseString(shelf, function (err, result) {
        let books = result['GoodreadsResponse']['reviews'][0]['review'];
        let index = Math.floor(Math.random() * books.length);
        let title = books[index]['book'][0]['title'][0];
        console.log(title);
    });

}).catch(err => console.error(err));

Next Steps

Although I don't fancy myself a designer or particularly skilled in terms of creating visually appealing things, I think my next steps will be to make a UI that shows more information like the cover, the author, and the rating and deploy it to Heroku. I may even make a fun feature where users can enter their Goodreads user id to have it randomly select a book from their to-read shelf.

Final Thoughts

I've always found it difficult to do side projects outside of work because I could never come up with an idea I liked enough to dedicate time to when I could be reading or doing something else I like to do in my free time. But I think this was a fun way to combine two things I enjoy into a project I may actually use.

Posted on by:

Discussion

pic
Editor guide
 

This is so cool! I've been meaning to do something like this! I also wanted to add a randomizer button so that I could pick from a specific shelf. Ex: If I wanted a tbr nonfiction book I could pick the nonfiction shelf and it would pick one from that.

 

Thank you! And yes, I totally agree with a randomizer button, it would be cool to be able to pick a book with more specific limitations. I also thought about doing some additional things like only returning books that have been released since I know I've got at least a handful of ones that aren't set to come out for a few months.

 

True. I think I have a couple like that from favorite authors but I'm so bad at keeping up with new releases. There's so much you can do with it!

 

I just couldn't hold my fingers still and had to setup a basic site where it does exactly what you did but in the form of a web UI.

berniwittmann.github.io/pick-a-book/

GitHub logo BerniWittmann / pick-a-book

Pick your next book from your Goodreads reading list

Welcome to Pick a Book 👋

Version Build Status License

Pick your next book to read from your Goodreads To-Read shelf on berniwittmann.github.io/pick-a-book/

Demo Video

Use it online

Use it online on berniwittmann.github.io/pick-a-book/

Install

npm install

Usage

npm run serve

Author

👤 Bernhard Wittmann

Show your support

Give a ⭐️ if this project helped you!

Inspiration

Huge thanks to @thackley for the inspiration I got from her blogpost

Questions

If you have any questions, don't hesitate to drop me a message






And since a lot of people here had some ideas I wanted to share it and hope for some contributions (in the sake of #hacktoberfest 😉).

 

YAY THIS IS AWESOME.

 

Greetings,

This is so cool, being a Goodreads user for years myself, I never imagined doing something like this, is so nice when you get to see the technical side of the things you use on your day to day.

Thanks,
Best Regards

 

Hey Tara, I am trying to get a list of reviews for a book based on the ID using the Goodreads API, but all I am getting is a reviews_widget. Any idea on how to get this? I tried link method.

 

Thank you for the inspiration Tara. I didn't know Goodreads had an API and I'm definitely going to fiddle around with it myself now :)

 

So glad it was useful! The Goodreads API isn't the most robust but I think there's a lot of potential to do some cool things with what they do have. I was poking around just before this trying to see if they have any endpoints for their yearly reading challenges (they don't) but I think their existing endpoints will still allow me to do what I want to do, it'll just take a bit more work on my end.

 

One thing that should be noted about the Goodreads API is that it is pretty slow and is also rate limited to 1 request per second (per endpoint, I believe).

I've been fiddling around with the API for a while now, but the idea that I had for it would require potentially quite a lot of requests so I keep putting it off.

Goodreads Terms of Service

 

Yep! The rate limit was a non-issue for me since I didn't need to make multiple requests for what I wanted to accomplish

 

Oh snap, this is clever! I just got back into reading a lot and now want to fiddle with this API :D I didn't know it existed 🤔

 

Thank you! Yes, please, definitely use it! Maybe if we get enough people on there, they'll make a more robust API we can use 👀

 

Like native JSON endpoints 😁

 

I've been wanting to do something with a book-related API for a while but haven't had a good idea.

 

If you have a Goodreads account or are open to making one, I'd definitely recommend seeing what you can do with their API. There's also the Google Books API that I've used before that could be helpful!