Programmatic publishing to Contentful
This is note 5 of 5 in a series on publishing automation.
The world of headless content management systems (CMS) and Jamstack architecture is an exciting movement that puts content, not apps, back at the heart of the web. The programmer Daniel Janus delineates this app/content divide clearly:
These days, the WWW is mostly a Web of Applications. An application is a broader concept: it can display text or images, but also lets you interact not just with itself, but with the world at large. And that's all well and good, as long as you consciously intend these interactions to happen.
A document is safe. A book is safe: it will not explode in your hands, it will not magically alter its contents tomorrow, and if it happens to be illegal to possess, it will not call the authorities to denounce you. You can implicitly trust a document by virtue of it being one. An application, not so much.
I'm a fan of this historical view of the web as a place to view content, or documents, via static websites. With this view as an ideal, the problem that CMS' try to solve, despite often existing as web apps in and of themselves, is paramount: how to create, modify and organize these documents.
This last note in the series shows how we can keep content at the heart of our site's technical implementation and increase speed and convenience of delivery to the web. By leveraging a modern headless CMS like Contentful, we can develop a custom publishing automation pipeline to make the place we author and publish one and the same.
Programmatic Contentful
In the previous note in this series, we built and deployed the Lambda function from this site, parsed the text of the note passed from Bear, and prepared to send the structured content to Contentful's Content Management API. We then call our createBlogPost
function which will create/update and publish our blog post to Contentful:
/**
* Creates/updates and publishes a Contentful entry.
* @param {Object} payload The raw note text to parse
*/
Breaking down this function:
- We pass our parsed blog post object to
getEntry
, which gets a single Contentful entry and returns it. We then restructure the returns to expose the version of the entry, which we will use later to know whether we need to create a new entry or update an existing one. - Next, we call
createEntry
and pass it the blog post object and version, which creates or updates a single Contentful entry. - Finally, we call
publishEntry
, which takes the newly created/updated blog post and publishes it, triggering a webhook that signals Netlify to rebuild the site with the new content included.
But before looking deeper into each of the calls to the Content Management API, we really should talk about content modeling.
Content modeling can make or break you
When configuring any CMS for the first time, you'll need to engage with the ritual that is content modeling. In other words, in order to create that blog post piece of content, the CMS needs to know what pieces of information or fields make up said blog post. In our case, I defined our blog post fields to be as flat as possible, with the name and type of field:
|- title (short text)
|- slug (short text)
|- date (date & time)
|- short description (long text)
|- category (short text, list)
|- body (long text)
Simple enough. But this is a revised state—the original structure looked more like this:
|- title (short text)
|- slug (short text)
|- date (date & time)
|- short description (long text)
|- category (short text, list)
|- content (container)
|- body (long text)
The key difference is the last item. If we define our blog post content type to have a container filled with different individual blocks of content (text, images, maybe video in the future, etc.), then programmatic publishing is much less straightforward. With this nested structure, you must explicitly keep track of links between content blocks. This means we have to first create the most nested blocks, such as a text block, then create the container and link the text block to the container. This was what the first version of the function above looked like as a result:
/**
* Creates an individual entry in Contentful.
* @param {Object} entry An individual entry
* @param {string} entry.key The content type of the entry
* @param {Object} entry.data The payload to send
* @returns {Object} response The request response from the Content Management API.
*/
;
/**
* Creates a series of individual entries in Contentful that are linked together.
* Starts from the most nested entry and ends with the blog post itself.
*/
To avoid this pain, I re-defined my blog post to be completely flat, enabling the function to make a single call to create and a single call to publish. If you're starting from scratch for a simple project, I highly recommend you keep your content model as flat as possible.
Get, create and publish
Back to our createBlogPost
function above, let's look at each of the three smaller functions it calls.
First, getEntry
to get the version of the blog post if it already exists:
/**
* Gets an individual entry in Contentful.
* @returns {Object} response from the Content Management API
*/
;
Then, createEntry
using the version as a parameter:
/**
* Creates an individual entry in Contentful.
* @param {Object} payload blog post payload to send
* @returns {Object} response from the Content Management API
*/
Finally, publish the newly created/updated entry:
/**
* Publishes the blog post that was just created in Contentful.
* @param {Object} entry entry object returned from Contentful
* @param {Object} entry.sys system object returned from Contentful
* @param {string} entry.sys.id id of the entry to publish
* @param {string} entry.sys.version version of the entry to publish
*/
And once more, put all together in the createBlogPost
function:
/**
* Creates/updates and publishes a Contentful entry.
* @param {Object} payload The raw note text to parse
*/
And there we have it! Zooming out to the entire automated publishing pipeline:
- We write our note in Bear
- Call this function from Scriptable in our share sheet
- This function creates/updates and publishes our note to Contentful
- A Contentful webhook notifies Netlify, and Netlify rebuilds the site with the new content
As a direct result of this pipeline, I can say that my focus has improved and rate of publishing has increased at least 3 fold. The ability to edit on the fly on my phone and publish updates instantly enables a continuous flow state while commuting and better writing overall. Suffice to say, I enjoy writing again.
If you've read through this entire series, thank you, it's been a pleasure. Do let me know if you implement something similar!
Cheers 🍻
Previous articles in this series:
- Publishing automation
- Bear as an authoring tool for the web
- JavaScript in iOS with Scriptable
- AWS Lambda functions via Netlify
Thanks for reading! Go home for more notes.