< All Blog
November 06, 2022
As I wrote in this blog, I created the Books page to list the books I have ever read. In this blog post, I'll explain how this page is built.
At first I decided how to describe each Book models. As for the requirements, each book has basic information like title
and authors
. However, I'm not building Amazon. I don't need Book's product information like ISBN
or published_at
. Instead, since this is my private history of books I've read, I want to have read_at
showing which year I finished reading that book.
In pseudocode, the data model looks like the following:
struct Book {
authors: Author[];
lang: String;
read_at: Int;
status: String;
tags: Tag[];
}
struct Author {
name: String;
url: String;
}
struct Tag {
value: String;
}
This site uses Gatsby as a framework for Static Site Generation (SSG). Gatsby has a concept of Transformer plugins, which is a pluggable architecture to stream and transform any data provided into nodes that can be queried via GraphQL during build steps.
JSON is apparently one of the most simplest data protocol to store books information. There is a gatsby-transformer-json plugin that works nicely to ingest and transform JSON into nodes that Gatsby can understand.
However, I want to have flexibility to manupulate books information. For example, some books have same authors. Tags can be used to group books, too. If I started editing those books with pure JSON, it'll be messy for renaming and refactoring data. I cannot put any source code comments into JSON.
This is where Jsonnet, a templating language for generating JSON, comes in.
Actual books.jsonnet
now looks like as follows:
locals authors = {
alice: {
name: 'Alice',
url: 'https://alice.example.com',
},
};
locals tags = {
programming: {
value: 'programming',
},
};
locals status = {
reading: 'reading',
completed: 'completed',
todo: 'todo',
};
locals lang = {
en: 'en',
ja: 'ja',
};
// books
[
{
authors: [
authors.alice,
],
lang: lang.en,
status: status.reading,
read_at: 2022,
tags: [
tags.programming,
],
title: 'Hacking Gatsby and React.js',
}
]
In order to make the data available as nodes in Gatsby, I need three steps to implement:
books.json
from books.jsonnet
gatsby-source-filesystem
to read that books.json
gatsby-transformer-json
to transform JSON data into nodesAt first, generate books.json
from the jsonnet file:
$ jsonnet "jsonnet/books.jsonnet" > "content/data/json/books.json"
Then, configure Gatsby to use two plugins:
// gatsby-config.js
module.exports = {
plugins: [
'gatsby-transformer-json',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'json',
path: `${__dirname}/content/json`,
},
},
]
}
Now that data is transformed into nodes. The data can be queried via GraphQL as follows:
query {
allBooksJson {
edges {
node {
title
id
lang
read_at
status
authors {
name
url
}
tags {
value
}
}
}
}
}
Finally a new file is created at pages/books.js
with page query and decorated fetched GraphQL result with React.js components.
import React from "react"
import { graphql } from "gatsby"
const Bookshelf = ({
data: {
allBooksJson: {
edges: allBooks
},
},
}) => {
console.log(allBooks)
returin //... use the GraphQL result to build the page!
}
export default Bookshelf
export const pageQuery = graphql`
query {
allBooksJson {
edges {
node {
title
id
lang
read_at
status
authors {
name
}
tags {
value
}
}
}
}
}
`
Gatbsy abstracts the way data is transformed using the plugrable architecture. Thanks to this, Gatsby allow developers to build and populate data with flexibility, which is one thing I like about Gatsby.