How to create a personal digital bookshelf with OpenLibrary

All the code you need to programmatically track your books and display them in your own personal web library

Ben Dexter Cooley
8 min readSep 24, 2021

Why we should all have a digital bookshelf

I love libraries. As someone who has move a lot, I have relied on libraries as my main way to read and gather physical books without having to store them. Kindle is ok, but everybody loves a good hardback. And you can get them for free! At libraries!

Books are a sort of cultural DNA, the code for who, as a society, we are, and what we know. — Susan Orlean, The Library Book

Still, at times I do miss that feeling of being able to gaze upon a physical bookshelf at my past reads. That’s why, a few years ago, I started using the Goodreads app after a friend recommended it to me. As an avid reader, I was instantly hooked and used it to religiously to track my “to-read”, “already-read” and “currently-reading”.

On the face of it, this makes total sense: my day job is to work with, visualize, and explain data. So the whole quantified self tendency to track my every move/read/watch/etc. seems to track. But it’s more than that with books.

To be fair, I have done the whole quantified self thing (for a year). I even designed a whole series of visual posters about it. But this is not about quantifying for the sake of quantifying.

It both amazes and depresses me how much of a book leaks through the cracks of my memory over time. I read so many wonderful things, and I want to retain every morsel that I come across. I have come to accept that this is near impossible; but perhaps with some practice, the memory recall can get easier.

I found this practice in recall to be successful last year when I published a longform data visualization essay titled To All the Books I Read in 2020. It was, by all accounts, a shit year. The COVID-19 pandemic began, lockdowns started, protests erupted, wildfires raged. I turned to books for an escape, but also in an effort to make sense of the chaos around me. Creating the essay reminded me of the beauty I found in the books along the way.

My data from Goodreads made all this possible, but I still had ambivalent feelings about the app. I nearly abandoned the app after they were acquired by Amazon, but found its utility too hard to give up.

And then, earlier this year, they announced they were shutting down their public API. No more programmatic access to your data. I’m out.

I have since migrated all of my data from Goodreads and made the switch to OpenLibrary, a non-profit maintained by the Internet Archive.

Open Library is an open, editable library catalog, building towards a web page for every book ever published.

So this is a tutorial about how to use OpenLibrary to pull your books, in real-time, onto a web page and then display the results. For me, being able to look back on my books is a practice of remembering. You can read more about all the other reasons why I built this in my digital garden.

Now onto the code! I am including snippets along the way, but if you want to look at the whole thing at once, see this Gist.

Create your account

First, create an account on OpenLibrary. OpenLibrary is run by the Internet Archive, a non-profit dedicated to building a digital library of everything on the Internet. So if you have a few dollars to spare, you should also consider donating!

Next, if you have been using a service like Goodreads, you can import your data. OpenLibrary has a whole blog post on how to do this, which they wrote after Goodreads announced it was retiring it’s API and disabling all keys (monstrous).

Alternatively, if you’re fairly new to book tracking, you can also just use the search bar to find your recent books and add them to your shelf.

Get the data

Ok, now we get to some coding. The great thing about OpenLibrary is that almost every page is itself an API. You can add the .json suffix to a page, and voila, you have a JSON object returned to you.

To keep things simple, I opted to use fetch as my Javascript method of choice to pull data. Let’s start by storing some of the base URLs that we want to access later:

// store URLs as variables for future referenceconst pastReadUrl ="https://openlibrary.org/people/bendextercooley/books/already-read.json";const currentlyReadingUrl ="https://openlibrary.org/people/bendextercooley/books/currently-reading.json";const coverUrl = "http://covers.openlibrary.org/b/olid/";const olBookUrl = "https://openlibrary.org/books/";

We also will want to setup a few empty global variables. As a heads up, my site runs on the Svelte framework, but most of this code is just pure Javascript and can be adapted. In Svelte, setting up empty vars early on ensures that these variables will always be accessible to the HTML template when we want to display them.

// create some empty variables for storing data later// these need to be before onMount so they are global, and can be used in the templatelet formattedBooks = [];let formattedReading = [];let bookList = [];let currentList = [];let finalBooks, nowReading, booksLength;

Then we will set two main functions for accessing the data. First, we will use the Svelte lifecycle method onMount to do some things right when the component is mounted. Then we will make a formatData function that will accept the JSON object from OpenLibrary, pull out the relevant attributes, and transform them into a some output.

Inside of onMount, we make two fetch calls using await to ensure that the code waits for a valid response before continuing. When it’s done, we pass the fetch response into our formatData function and then store it in a global variable.

onMount(async () => {currentList = await fetch(currentlyReadingUrl).then((x) => x.json());bookList = await fetch(pastReadUrl).then((x) => x.json());nowReading = formatData(currentList, formattedReading);finalBooks = formatData(bookList, formattedBooks);booksLength = finalBooks.length;console.log(nowReading);});

Ok, time to format the data response. This is going to be more specific to how you choose to display your bookshelf, so I’ll just walk through mine as an example. I use this function to do a few main things:

  • Setup variables that the template will need (colors, widths, etc) which will be passed to each “book”.
  • Loop through the dataObject and create a new object, only keeping the variables you want to display in the HTML template.
// function to pull out the data that I wantfunction formatData(dataObject, arr) {const colors = ["#730A16", "#BB4F24", "#2F7894", "#D28F33"];let widths = [60, 70, 90];
function getRandomInt(max) { return Math.floor(Math.random() * max);}// pull out relevant datadataObject.reading_log_entries.forEach((d) => {let obj = {};// some splitting to make sure I get the right editionif (d.logged_edition == null) {obj.ol_id = d.work.key.split("/")[2];} else {obj.ol_id = d.logged_edition.split("/")[2];}
// store all the values
(obj.title = d.work.title),(obj.color = colors[getRandomInt(4)]),(obj.width = widths[getRandomInt(3)]),(obj.tilt = getRandomInt(4) * 1.3),(obj.read_date = d.logged_date),(obj.read_timestamp = new Date(d.logged_date.split(",")[0])),(obj.book_link = d.work.key),(obj.cover_url =coverUrl + d.work.cover_edition_key + "-M.jpg"),(obj.author = d.work.author_names[0]),(obj.author_link = d.work.author_keys[0]);arr.push(obj);});return arr;}

If we console out the array of objects at the end, we should have a nicely formatted dataset to work with!

Build your bookshelf

Now it’s time to put some books on the shelf. Obviously, do what you want here. A simple list of titles and authors would be a great start.

I decided to have two methods of displaying my books: as a 2d digital bookshelf replica, and a Goodreads-style list. I saw plenty of clean, list-type designs for personal digital libraries out on the web. But I wanted to recreate the feeling of an actual bookshelf, so I opted for something a bit more visual.

The bookshelf obviously took a bit more code tweaking. To create the bookshelf, I used an {each} loop to render each book as a div. This is a very Svelte-y thing to do, but the concept is universal: it’s just a for loop. If you use Vue, it’s v-for. If jQuery, it’s a plain for loop with some appends.

Inside of this loop, I create the HTML elements and also pass in various attributes from the data object for each “book” div.

{#each finalBooks as book}    <div class="book" on:click={() => pullOffShelf(book)}    in:fade="{{ duration: 2000 }}"    style="background-color: {book.color}; transform:     rotate({book.tilt}deg); width: {book.width}px"    >        <div class="spine">            <p class="inner rotate">{book.title}</p>        </div>    </div>{/each}

Now, in order to get my books to stack on the shelf, I add some CSS classes. I used flex-wrap in my bookshelf #container to make sure the books stack horizontally and are auto-responsive to page width changes. The inner and rotate classes make sure the title rotates 45(ish) degrees to line up with the book spine.

#container {display: flex;flex-wrap: wrap;border: solid 2px brown;padding: 5px;margin: 20px;min-height: 500px;}

And finally, to make sure that elements do not render out before the data is ready, I added a null checker before my for loop using {#if finalBooks != null}. That way, the template is only rendered after our response has come back and is fully formatted.

We have a bookshelf! Compared to a list, there are some obvious limitations. You can’t see as much information with the books being so small. Plus the text is rotated (which I like, feels more library-like, but it does wear on the neck having to tilt the head).

To get more information, I added a click handler to “pull a book off the shelf”. In Svelte, this is dead simple.

First I create a div that sits next to the bookshelf. Inside this div, I add the null checker {#if bookOffShelf != null}. Then inside of this if statement, I create a few HTML elements to display data for a given book: image, title, author, and link to the book on OpenLibrary.

Now we need to populate the empty variable bookOffShelf with data. We can do that with the following function:

let bookOffShelf;function pullOffShelf(data) {bookOffShelf = data;}

Then, we just need to call the function from each “book” element in the template when it is clicked on:

on:click={() => pullOffShelf(book)}

When a user clicks on a book, it identifies this data object and passes it to the function we just wrote. This object then populates the HTML to the left (at least on desktop) of the bookshelf like below.

I have also added a “Currently Reading” section to my site, which just pulls my active books out and displays the cover, title, and author above my bookshelf. In a new version, I’d like to add:

  • date I finished the book (this is harder to figure out on OpenLibrary if you imported from Goodreads)
  • review of the book (when I remembered to write one)
  • my rating of the book
  • books on my “Want to read” list, maybe as a second bookshelf

I’ll think of more later. If you’d like to create your own digital bookshelf (and you are familiar with Svelte), all my code is open source. So feel free to grab the full component from this Gist, and replace it with your own OpenLibrary profile URL! If you don’t know Svelte, you can also checkout the code behind this minimal prototype I made on Glitch (using only jQuery).

Happy reading!

--

--

Ben Dexter Cooley

Visualization Software Engineer @ Pattern (Broad Institute). Designer, developer, data artist. Portfolio: bendoesdataviz.com | Art: bdexter.com