Creating High Performance Low Latency Apis With Hugo

· 3 min read March 21, 2022

banner

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

Some Thing to say ?