Skip to content

Instantly share code, notes, and snippets.

@MichaelCurrin
Last active Jan 23, 2022
Embed
What would you like to do?
Jekyll - how to build a REST API

Jekyll - how to build a REST API

Serve your data as static JSON

How to make a read-only JSON REST API using Jekyll.

This doesn't need any Ruby plugins - you just use some built-in templating features in Jekyll 3 or 4.

You will end up with a single JSON file contains data for all pages on the site, and another JSON file of just posts. Alternatively, you can replace every HTML page and post with a JSON version.

Notes

Data source

You might have data stored in your Jekyll project. Typically in YAML, CSV or JSON file in the _data/ directory. Or in the frontmatter of your pages. This tutorial shows you how to render as JSON pages.

The data files could be:

  • in version control (visible in Git and on GitHub)
  • dynamic (ignored by Git) - for example if you have a script to fetch API data and write to _data every time you deploy your site.

Why

This is useful if you want to make your data available for another service or for others to consume (like GitHub, Twitter and Facebook make their data available on APIs). Or perhaps you have JavaScript single-page application that reads from your API backend to serve the app or build a search index.

Example

Given data in page frontmatter or a YAML data file:

---
my_data:
  - a: 1
    b: 2
  - a: 100
    b: 200
---

Or CSV data as:

a,  b
1,  2
100,200

The rendered JSON page will look like this:

  • localhost:4000/foo.json
    [
      { "a": "1", "b": "2" },
      { "a": "100", "b": "200" }
    ]

Static site note

Note that Jekyll is a static site generator. So your JSON API data will only be updated whenever you make a commit and the site rebuilds. If your API needs to have data which changes based on many users or needs to change in realtime, then you're better off using Node.js or Python for your API.

Summary of approach

Here we will use .json as the file extension, instead of the usual .md or .html. This means your HTTP header will be set correctly:

Content-Type	application/json

We'll also make sure to set the layout to null so that we get a pure JSON page without any HTML styling.

We'll use our the YAML data to build the response. If you use the jsonify Jekyll, you can convert data straight to JSON without worrying about for loops or JSON syntax.

I recommend using a Jekyll extension for your IDE, to handle Liquid syntax highlighting and recognizing frontmatter. Then make sure you choose jekyll as your formatter for your .json files.

Sitemap note

It looks like JSON files are excluded from a sitemap file by default. The plugin recognizes just HTML and Markdown files.

Resources

Taking it further

See also Data files in the Jekyll docs. You might iterate over data in a JSON or YAML file or files to build up output on a page. The data files are your database and then Jekyll builds your API. This is also a way to build a databse JSON file to be consumed on the frontend such as for search functionality.

Or you might store your data in the frontmatter of Jekyll Collections which get outputted as pages similar to the Page example below. That way you can group your JSON data into collections as interate over them easily.

All pages endpoint

Make a single JSON file containing data for all pages.

See also Post listing section.

Use site.pages if you want to list pages. This will exclude posts and collections.

  • api/pages.json
    ---
    layout: none
    ---
    [
      {%- for page in site.pages %}
        {
          "title":      {{- page.title | jsonify }},
          "url":        {{- page.url | jsonify }}
        }
        {% unless forloop.last %},{% endunless %}
      {% endfor -%}
    ]

You can do something similar with items in site.my_collection or all collections with site.collections.

Mix formats

If you mix your pages as HTML and JSON, you can add a filter to get only JSON files.

{% if post.ext == '.json' %}

This field works for posts only.

For pages, you'll have to use item.name (e.g. foo.md) and check if it ends with .json.

Pages as JSON files

Render each Markdown page as a JSON file, using frontmatter for data. Note that no HTML file is generated here.

  • _layouts/page.html - build up the JSON object, quoting all values and in the case of the body text we convert from markdown to HTML.
    ---
    layout: none
    ---
    {"title":{{ page.title | jsonify }},"content":{{ page.body | markdownify | jsonify }},"links":{{ page.links | jsonify }}}
  • foo.json - a Jekyll HTML/markdown page with a .json extension.
    ---
    layout: page
    title: Foo
    body: |
      Hello, **world**.
      I am here.
      
    links:
      - title: Go track on Exercism
        url: https://exercism.io/tracks/go
    
      - title: Go tour  welcome page
        url: https://tour.golang.org/welcome/1
    
      - title: Learn Go with Tests
        url: https://quii.gitbook.io/learn-go-with-tests/
    
      - title: Go for Python programmers
        url: https://golang-for-python-programmers.readthedocs.io/en/latest/index.html
    ---

Output file

Visit at http://localhost:4000/foo.json

{"title":"Testing","content":"<p>Hello, <strong>world</strong>.\nI am here.</p>\n","links":[{"title":"Go track on Exercism","url":"https://exercism.io/tracks/go"},{"title":"Go tour  welcome page","url":"https://tour.golang.org/welcome/1"},{"title":"Learn Go with Tests","url":"https://quii.gitbook.io/learn-go-with-tests/"},{"title":"Go for Python programmers","url":"https://golang-for-python-programmers.readthedocs.io/en/latest/index.html"}]}

Here it is in a more readable format:

{
  "title":"Testing",
  "content":"<p>Hello, <strong>world</strong>.\nI am here.</p>\n",
  "links": [
    {"title":"Go track on Exercism","url":"https://exercism.io/tracks/go"},
    {"title":"Go tour  welcome page","url":"https://tour.golang.org/welcome/1"},
    {"title":"Learn Go with Tests","url":"https://quii.gitbook.io/learn-go-with-tests/"},
    {"title":"Go for Python programmers","url":"https://golang-for-python-programmers.readthedocs.io/en/latest/index.html"}
  ]
}

All posts endpoint

Make a single JSON file containing data for all pages.

Input file

Add content if you want that too, but it will make the output a lot longer.

  • api/posts.json
    ---
    layout: none
    ---
    [
      {%- for post in site.posts %}
        {
          "id":         {{- post.id | jsonify -}},
          "title":      {{- post.title | jsonify }},
          "date":       {{- post.date | jsonify }},
          "url":        {{- post.url | jsonify }},
          "tags":       {{- post.tags | jsonify }},
          "categories": {{- post.categories | jsonify }}
        }
        {% unless forloop.last %},{% endunless %}
      {% endfor -%}
    ]

Output file

  • /posts/
    [
        {
          "id":"/2020/05/15/meaning-and-recognition",
          "title":"Meaning and recognition",
          "date":"2020-05-15 00:00:00 +0200",
          "url":"/2020/05/15/meaning-and-recognition.html",
          "tags":["reflection","motivation"],
          "categories":[]
        }
        ,
        // more posts...
    ]

Permalink

Optionally add permalink:

permalink: /api/posts/

Then you can access as /api/posts/ instead of /api/posts.json.

Posts as JSON files

How to output each Markdown post into a JSON file.

Input file

Here we make a post in the usual _posts directory.

The layout is also much lighter and more flexible - using the data object without caring about keys, values and formatting.

The downside is that you have to be more verbose in your post frontmatter to define a YAML variable with & and then use it with *. Based on this YAML tutorial.

Also note unlike in the page example above, we don't covert to body content from markdown to HTML.

  • _layouts/post.html - a layout which uses frontmatter and converts it to JSON output.
    ---
    layout: none
    ---
    {{ page.data | jsonify }}
  • _posts/2020-12-12-hello.json - a post with a .jsonextension.
    ---
    layout: post
    
    title: &title Hello world
    categories: &categories
      - Go
      - Python
    content: &content |
      Hello, **world**
      I am here
    
    data:
      title: *title
      categories: *categories
      content: *content
    ---

Output file

Visit at http://localhost:4000/go/python/2020/12/12/hello.json

{"title":"Hello world","categories":["Go","Python"],"content":"Hello, **world**\nI am here\n"}
@shoaiyb
Copy link

shoaiyb commented Aug 15, 2021

Also, Posts does have ID's which can be included

---
layout: none
permalink: /posts/
---
[
{%- for post in site.posts %}
    {
    "title": {{ post.title | jsonify }},
    "date": {{ post.date | jsonify }},
    "url": {{ post.url | jsonify }},
    "id": {{ post.id | jsonify }}
    }
{% endfor -%}
]

@MichaelCurrin
Copy link
Author

MichaelCurrin commented Aug 16, 2021

Thank @shoaiyb fo the suggestion! I've added date, tags and categories. And also fixed a bug where there was no comma between { ... } items.

@rudSarkar
Copy link

rudSarkar commented Jan 23, 2022

Wow! Thanks, This is what I am looking for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment