What we are Building
we are going build a api to serve qoutes .but we wont use any backend server like nodejs or django. but rather we will use hugo to serve our static qoutes data
Motivation
If you have created rest api’s you would already know what are the tools we are going to need . for a restapi we need a server to serve and recieve the data. may a database that the server uses to fetch the data from . and tools like fastapi and nodejs have really made it intuitive to create robust apis .
If you are thinking what is wrong with tools and why are we going through the crazy path of using hugo . The answer is there is nothing wrong but imagine you want to create a api for serving quotations . the api is going to be readonly .using the server approach is a bit of overkill as we are just serving some static content .
So . to get best benefits of the static content we are serving . we can use a SSG like hugo . The best thing about this approach is that we can deploy the side on a cdn like netlify and enjoy the benefits of edge.
Why Hugo
We are using hugo for this project because of great inbuilt feature of choosing a custom output format for our content . and the other interesting feature is taxonomies which will help us serve content based on relations like author or genre.
Lets Get Started .
lets initialize a new hugo site
hugo new site qoutes-api
inside the project directory . add the following lines to config.toml to enable custom output format for our content .
# in congig.toml
[outputs]
page = ["json"] # A qoute
section = ["json"] # All qoutes
home = ["json"] # Everything
taxonomyTerm = ["json"]
taxonomy =["json"]
taxonomyKind =["json"]
Our content structure is going to be pretty simple . We have a section called qoutes . a qoute has a few params like id, author , genre and the qoute
lets create the default archtype for our qoute. the id is going to be same as the name of the content file
---
id: "{{ .Name | title }}"
quote: ""
genre: ""
author: ""
---
lets create of first qoute
hugo new qoutes/01.md
edit the qoute 01./md
---
id : 01
qoute : "test qoute 01"
author: "aabid"
genre : "test"
---
Now we have the content ready . we will create the json templates to serve the data .
lets first create the base_of.json.json in layouts/_default directory this is going to be the base for all of our responses.
{
"data" : {{ block "response" . }} {{ end }}
}
now lets create a template for single qoute . lets name it as item.json.json in the _default dir
{
"id" : "{{.Params.id}}" ,
"quoute" : "{{.Params.quote}}" ,
"author" : "{{.Params.author}}" ,
"genre" : "{{.Params.genre}}"
}
not to serve the single page for a qoute we will create single.json.json according to hugo lookup order. Inside single.json.json we are just rendering the item template.
{{ define "response" }} {{ .Render "item" }} {{end}}
Done that . the end point for a single qoute is ready and can be tested on
base_url/qoutes/{id}
Now we are left with the important job of serving a list of qoutes . we wont serve all the qoutes in single page as it can be very bad for performance . So we will take help of the hugo pagination to render a paginated list of our qoutes
lets create list.json.json inside /_default for serving a list of qoutes .
{{ define "response" }}
{{ $paginator := .Paginate (where .Data.Pages "Type" "quotes") }}
{
"total_elements":{{ $paginator.TotalNumberOfElements}},
"page_no": {{$paginator.PageNumber}} ,
"has_next": {{ $paginator.HasNext}},
"has_previous" :{{ $paginator.HasPrev}},
"total_pages" : {{ $paginator.TotalPages}} ,
"page_size" : {{$paginator.PageSize}},
"qoutes":[
{{ range $index, $e := $paginator.Pages }}
{{ if $index }}, {{ end }}{{ .Render "item" }}
{{ end }}
]
}
{{ end }}
in the template we are paginating over the Pages and also showing additional information like total_pages, page_no,total_items
we can check the endpoints at
base_url/qoutes/index.json first page
base_url/qoutes/page/{page_no}/index.json pages from page_no 2
As we said earlier we want our users to be able to get qoutes for a author or genre . the half work is already done as we are already using the taxonomy terms in our content files.
hugo as default only recognizes categories and tags as the default taxonomies, so, to configure the taxonomies for the site add the following to the config.toml .
[taxonomies]
genre = "genre"
author = "author"
[outputs]
page = ["json"] # A player
section = ["json"] # All players
home = ["json"] # Everything
taxonomyTerm = ["json"]
taxonomy =["json"]
taxonomyKind =["json"]
with this done we have the following endpoints now
base_url/genre/{name}/index.json base_url/genre/{name}/{page_no}/index.json base_url/author/{name}/index.json base_url/author/{name}/page/index.json
Now to be able to list all terms for a taxonomy like all authors or genres lets create a /_default/terms.json.json
{{ define "response" }}
[
{{ range $index, $e := .Data.Terms.Alphabetical }}
{{ if $index }}, {{ end }}"{{ $e.Page.Title }}"
{{ end }}
]
{{ end }}
now we view all authors at
/author/index.json
and all genres at
/genre/index.json
Thanks