Frank Robert Anderson

I am a Developer who does web good and likes to do other stuff good too.

Blog Tutorials Rants Everything Back to My Site

Add an API to Your Jekyll Blog

I really like github pages. I built my blog on it, even though I host it myself. When friends and family ask for me to build them a site I will point them to github pages. Markdown is so easy, liquid is so easy. What isn't easy is dynamic lists of content. A simple list of related content isn't as easy. Sure there are ways, but I want to learn Reactjs --so I will build it with that. That will require a RESTful API for my content.

The easy way

To be clear I didn't write this snippit --but only because someone else did. And working code wins.


    ---
    layout: nil
    ---

    [

    {% for post in site.posts %}
        {
          "title"    : "{{ post.title }}",
          "url"     : "{{ post.url }}",
          "date"     : "{{ post.date | date: "%B %d, %Y" }}",
          "content"  : "{{ post.content | escape }}"
        } {% if forloop.last %}{% else %},\{% endif %}
    {% endfor %}

    ]

With this snippit (added for convenience) we get most of the way there. My plan is to modify it a bit.

What I would do, or the not so easy way


    ---
    layout: nil
    ---

    [

    {% for post in site.posts %}
        {
          "title"    : "{{ post.title }}",
          "url"     : "{{ post.url }}",
          "date"     : "{{ post.date | date: "%B %d, %Y" }}",
          "tags"  : {{ post.tags }},
          "categories"  : {{ post.categories }},
          "description"  : "{{ post.description | escape }}"
        } {% if forloop.last %}{% else %},{% endif %}
    {% endfor %}

    ]

From here you should see the change that I made. I changed the content to description and added tags and categories to the listing. This will provide a good everything list for the blog api. (This should all be in the /api/v01/list.json file).

Hmmmm, that isn't working as expected. Instead of giving me a list of categories and tags it is concatenating them into a single string. So it looks like another modification is necessary.

I also added a check incase there isn't anything in tags or categories.

---
layout: nil
---

[

{% for post in site.posts %}
    {
      "title"    : "{{ post.title }}",
      "url"     : "{{ post.url }}",
      "date"     : "{{ post.date | date: "%B %d, %Y" }}",
      {% if post.tags %} "tags"  : [
        {% for tag in post.tags %} "{{ tag }}"
        {% if forloop.last %}{% else %},{% endif %}
        {% endfor %}
        ],
      {% endif %}
      {% if post.tags == nil %} "tags"  : [],  {% endif %}
      {% if post.categories %} "categories"  : [
        {% for category in post.categories %} "{{ category }}"
        {% if forloop.last %}{% else %},{% endif %}
        {% endfor %}
        ],
      {% endif %}
      {% if post.categories == nil %} "categories"  : [],  {% endif %}
      "description"  : "{{ post.description | escape }}"
    } {% if forloop.last %}{% else %},{% endif %}
{% endfor %}

]

This gives me a single listing page. Now we need posts.

Aside note. Jekyll is having a really hard time with this post. It keeps wanting to parse the liquid in the code examples. Thankfully Jekyll Liquid now has a proper escaping system. Use liquid{% raw %}.

Building the posts. NPM to the rescue (maybe)

Not npm exactly, Gulp instead saved the day. Ever since I moved my utility functions to Gulp I have been a fan. It is very well suited for building small repeatable tasks. So I add a new Gulp task to the gulpfule.

gulp build-api

With this command I duplicate the content in the _post directory over to the api/[version]/v01/posts directory. Thow in a little gulp piping to do a string replace (this is to change the layout template from html to json. This is the whole gulp command.

gulp.task('build-api', function() {
  return gulp.src('_posts/*')
    .pipe(replace(/layout\: post/, 'layout: json'))
    .pipe(vfs.dest('api/v01/posts', { overwrite: true }));
});

It is simple enough, ignore the vfs.dest part (I am using vinyl directly so I can use the overwrite option).

All that is left is to add a unique identifier, I figure the post url should work for that. I do that with the json template.

---
layout: json_default
---

{

  "title"    : "{{ page.title }}",
  "url"     : "{{ page.url }}",
  "permalink": "/posts/{{ page.date | date: "%Y/%B/%d" }}/{{ page.path | replace: 'api/v01/posts/', '' | replace: '.md', '' }}.html",
  "date"     : "{{ page.date | date: "%B %d, %Y" }}",
  {% if page.tags %} "tags"  : [
    {% for tag in page.tags %} "{{ tag }}"
    {% if forloop.last %}{% else %},{% endif %}
    {% endfor %}
    ],
  {% endif %}
  {% if page.tags == nil %} "tags"  : [],  {% endif %}
  {% if page.categories %} "categories"  : [
    {% for category in page.categories %} "{{ category }}"
    {% if forloop.last %}{% else %},{% endif %}
    {% endfor %}
    ],
  {% endif %}
  {% if page.categories == nil %} "categories"  : [],  {% endif %}
  "content"  : "{{ content | escape }}"

}

Footnotes

Jekyll complains about using ruby nill for a layout type. It doesn't break, but I also create two layout templates json_default.json and json.json. Now that it is working, I will rename the templates to something abit more meaningful:

default.json
post.json

I think these are better names.

Written on October 25, 2015 by Frank Robert (frob) Anderson

Social Networks

Blogroll

BTMash's blob of contradictions
LA Grafitti
Justin Biard's icodealot
Copyright Frank Robert Anderson 2020