Managing my reading list

A little while ago I was toying with building a lightweight web app that would make it easy to manage and share reading lists. I never got around to making it, but instead I built a very-MVP version by adding my reading list to this website. It was a fairly simple construction, I simply added a file called reading-list.11tydata.json to my source directory, that looked like this:

{
"books": [
{
"title": "Eyes of the Void",
"author": "Adrian Tchaikovsky",
"goodreads": "https://www.goodreads.com/book/show/58950674-eyes-of-the-void",
"status": "completed",
"completed_date": "25/05/22"
},
{
"title": "The Player of Games",
"author": "Iain M. Banks",
"goodreads": "https://www.goodreads.com/book/show/18630.The_Player_of_Games",
"status": "started"
},
]
}

Then, on my reading list page I could simply iterate over the books variable and output the data however I pleased. And it worked absolutely fine.

But it was't quite what I wanted, it doesn't give any information about the books I'm reading, e.g. if I wanted to add book covers I'd have to upload them manually. So, last night, when I really should have been sleeping, I started working on using data files, combined with using pagination to construct pages from data, to build something that was slightly closer to my original ambition.

Getting information about a book

Choosing a data source was pretty easy. Open Library has a very simple route-based API that would allow me to do all the things I needed to: search for a book by author and title, get information about that book, and get the book covers.

I did all this in a data file inside the _data directory, called readinglist.js. I added my original list of books, and then for each book I use the API to first search for the book, and choose the first result, then I get more detailed information about the book using the Works API, and then finally I look for a cover if it exists. Here's all the code to do that:

const getBook = async book => {
const res = await axios.get(`https://openlibrary.org/search.json?author=${book.author}&title=${book.title}`);
const key = res.data.docs[0].key;
const work = (await axios.get(`https://openlibrary.org${key}`)).data;
const cover = work.covers ? `https://covers.openlibrary.org/b/id/${work.covers[0]}-M.jpg` : undefined;

return {...book, cover, "data": work };
};

module.exports = async function() {
return await Promise.all(books.map(getBook));
}

Constructing pages from the data

Eleventy makes this incredibly easy. I just had to add some pagination rules to the front-matter data on a new template:

layout: layout.njk
pagination:
data: readinglist
size: 1
alias: book
permalink: book/{{ book.title | slugify }}/index.html
eleventyComputed:
title: "{{ book.title }} - {{ book.author }}"

Eleventy took care of the rest, and generated all of the pages. I did find that I had to specify the .html extension for the permalinks. If I left it out, Eleventy wouldn't generate a valid HTML page and instead navigating to it would download the file instead of displaying it in-browser.

The result

You can see the results for yourself on the reading list, or by viewing one of the information pages for a book, such as Murder on the Orient Express. Overall, I'm actually really happy with how it turned out. It's a step closer to the web app that I'd originally envisioned, but it only took a few hours to put together.

There are a few limitations: I can't fine-tune what version of the book the API returns without more work, and I've got no control over the descriptions provided. But, I think it's a fair compromise to achieve what I wanted.

Overall, this was a fun little late-night project to pick up. As usual, I love how easy Eleventy makes tasks like this.