Librokapto: a simple book catalogation app

— 8 minute read

I used public APIs and Google Sheets for data storage to make this book catalogation web app that runs 100% on the client

Librokapto is an app I made for my own personal use, but I decided to share with anyone that might want to use it.

I needed an app to organize my books for easy access. After conducting some research, I wasn't satisfied with the available options, and began contemplating the idea of creating my own app.

My requirements were:

  • simple two-coordinate addressing, e.g. corridor bookcase, 3rd shelf.
  • light and easy to use on mobile.
  • must be possible to acquire book information through ISBN scanning.
  • the book catalog must be accessible from any device with internet acces without installing additional software.

In such a scenario, storing the book catalog in Google Sheets would drastically decrease the complexity of the project. The app would just serve as a helper to add books to the catalog through ISBN scanning and provide a nice book search view. The user would directly use Google Sheets for the rest of the CRUD operations as well as sharing the catalog.

Getting the book information permalink

Decoding a barcode in the browser using the camera is quite straighforward using quaggaJS, a JavaScript barcode-scanner that is able to decode a wide range of barcodes, including EAN13, the one used to encode books ISBN numbers and many other things.

Having a 13-digits ISBN code, it is possible to get book information using a public API by Google:

GET https://www.googleapis.com/books/v1/volumes?q=isbn:9780008144227
{
"kind": "books#volumes",
"totalItems": 1,
"items": [
{
"volumeInfo": {
"title": "The Alchemist",
"authors": [
"Paulo Coelho"
],
"publisher": "HarperThorsons",
"imageLinks": {
"thumbnail": "http://books.google.com/…",

This API allows cross-origin requests, so it's fine to consume it client side from any domain.

Adding a book to the data store permalink

Adding a book is done by appending a new line to Google Sheets. There are several ways to interact programmatically with Google Sheets. I chose to implement the append function in Google Apps Script and to expose that as a web service deploying the script as a web app.

To handle POST requests, in my Google Apps Script I defined a doPOST() function. This is how an echo service would look like:

function doPost(e) {
const data = JSON.parse(e.postData.contents)
return ContentService.createTextOutput(
JSON.stringify(e) // send the request back
).setMimeType(
ContentService.MimeType.JSON
);
}

Now sending POST requests to the deployment endpoint is possible to see the data getting echoed:

POST https://script.google.com/macros/s/…/exec
{"hello":"world"}
{
"parameters": {},
"parameter": {},
"queryString": "",
"postData": {
"contents": "{\"hello\":\"world\"}",
"length": 17,
"name": "postData",
"type": "text/plain"
},
"contentLength": 17,
"contextPath": ""
}

In my doPost() function I parse the JSON in e.postData.contents to read the received book data and append it to the sheet.

Retrieving the book catalog permalink

To retrieve the whole catalog, I needed a way to get the content of a Google Sheet using JavaScript. While I could implement a doGet() function in the aforementioned Google Apps Script, I decided to retrieve the content of the sheet in another way, using the Google Visualization Query. Its advantages are that it's not quota based, has a lower latency and does not require the implementation of a custom function.

It is possible to build the url for the query as such:

// gsheet_url = 'https://docs.google.com/spreadsheets/d/…/edit#gid=0'
const gviz_url = new URL('gviz/tq', gsheet_url)
gviz_url.searchParams.append('gid', '0') // sheet index
gviz_url.searchParams.append('tq', 'SELECT A,B,C')

A GET request to that URL would result in a response such as:

/*O_o*/
google.visualization.Query.setResponse({

"table":{
"cols":[],
"rows":[
{
"c":[
{"v":"first cell"},
{"v":"second cell"},
{"v":"third cell"}
],
},

});

Note that the response is not JSON but JavaScript code, since it is supposed to be used with the Google Chart SDK. Anyway, the argument of the setResponse() method is valid JSON and may be parsed without importing the Google Chart library, being aware that its structure may change in the future.

Wrapping up permalink

Every API used in this project is CORS enabled. This allows the app to run entirely on the browser, sparing the need to build a backend and the round trip time that goes with that at each request.

The user settings such as the Google Sheets catalog URL are stored on the device using IndexedDB.

I spent some time designing the UI for the book insertion page, since I think it's crucial to have a good flow when scanning and saving a series of books. The final decision was to have a floating action button that toggles between "scan" and "save" depending on the book title field being filled or not. A progress bar is placed on the top of the form, and its background color reflects the one of the floating action button, so that the user can see what the current state is even when the onscreen keyboard overlaps the floating action button.

The result is a simple web app that just does its job giving a pleasant user experience.

Check out the Librokapto GitHub repository.

You can see Librokapto in action here: