How to…
Integrate Contentful with DADI
Contentful is a hosted API solution which offers a fast and slick way to create an API-first product. The icing on the cake is a flexible and powerful interface that you do not have to self-host.
At DADI, we’re aiming at a slightly different use-case, but the two projects share similar goals and there is nothing to stop you mix-and-matching, especially if you don’t want the hassle of hosting your own API and database.
To show how easy it is to integrate DADI products with other solutions, we have created a sample project where you can use DADI Web with Contentful’s API service. It makes use of a new restapi
datasource type we’ll be launching with Web soon.
This tutorial assumes you have installed DADI Web already, but if you haven’t, you might find this post useful.
Generate an API key
After you have signed up for Contentful, you’ll need to generate an API key to allow DADI Web to communicate with the service.
You can do that by visiting Space settings > API Keys
and clicking Add API key
.
This will generate the info you need. Name the API key DADI Web
, so future you knows what it’s for.
You should also make a note of the values for Space ID
and Content Delivery API - access token
. We’ll make use of these in the next step.
Configure Web
New in Web 6.0 is the ability to add configurations for multiple APIs in your main config.json file.
{
"api": {
"contentful": {
"type": "restapi",
"provider": {
"contentful": {
"https://cdn.contentful.com": {
"__domain": {
"auth": {
"qs": {"access_token": "[0]"}
}
},
"spaces/YOUR_SPACE_KEY/{endpoint}": {
"__path": {
"alias": "__default"
}
}
}
}
},
"auth": {
"qs": {
"access_token": "YOUR_API_KEY"
}
},
}
}
}
Replace YOUR_SPACE_KEY
and YOUR_API_KEY
with the values from Contentful.
Note: It’s not best practice to store these keys in a GitHub repository, even though these are read-only keys.
Create a datasource
We’re going to assume you’re creating a blog-like site for the purposes of this article, however the process will be pretty similar for any other type.
In Contentful we have a Content Type of Post
. Once we have filtered view for that content we can grab the contentTypeId
in the url, which we’ll use asYOUR_CONTENT_KEY
.
https://app.contentful.com/spaces/YOUR_SPACE_KEY/entries?
contentTypeId=YOUR_CONTENT_KEY
Next, we can create the datasource in DADI Web:
/workspace/datasources/posts.js
{
"datasource": {
"key": "posts",
"source": {
"api": "contentful",
"endpoint": "entries"
},
"query": {
"content_type": "YOUR_CONTENT_KEY",
"limit": 10
},
"requestParams": [
{
"param": "slug",
"field": "fields.slug",
"target": "query"
},
{
"param": "skip",
"field": "skip",
"target": "query"
}
]
}
}
You can see we also have some requestParams
which will help us paginate the data and create individual pages for our posts later.
Anything in query
is sent directly to the Contenful API - review their documentation to choose what you need.
Resolving links
Contentful has a concept called Links which are ways of referencing content from other entities, for example the author name on a post. This is an important concept, because it means you can update the author name (for example), without having to update each post individually.
As an aside, in DADI API we resolve these connections automatically for you — a concept we call composition
which acts on reference fields
.
Luckily Contentful have an NPM package called contentful-resolve-response which can help us out. We can combine this with an Event in DADI Web:
/workspace/events/resolveResponse.js
const resolveResponse = require('contentful-resolve-response')const Event = function(req, res, data, callback) {
data.postsMeta = {
total: data.posts.total,
skip: data.posts.skip,
limit: data.posts.limit
}
data.posts = resolveResponse(data.posts) // Fin
callback(null)
}module.exports = function(req, res, data, callback) {
return new Event(req, res, data, callback)
}
We’ve also created a postsMeta
object which we’ll use later for pagination.
Create a page
Now we have the datasource we can create a page in Web, attach the datasource and event, and render out the data.
We’ll also setup a route so we can view individual post pages. The second route has an optional slug
parameter, which is used by in the requestParams
of our /workspace/datasources/posts.js
datasource to filter posts.
/workspace/pages/index.json
{
"page": {
"name": "index"
},
"events": [
"resolveResponse"
],
"datasources": [
"posts"
],
"routes": [
{
"path": "/"
},
{
"path": "/post/:slug?"
}
]
}
Write a template
We’re modifying the boilerplate Web templates here to give the bare minimum to make a viewable page. We already have some CSS and header and footer partials in place, but obviously you’re free to bring your own front-end code and template language too!
/workspace/pages/index.js
${partials_header}${posts.map(i => `
<article>
<header>
<h2><a href="/post/${i.fields.slug}">${i.fields.title}</a></h2>
<p>
By <strong>${i.fields.author[0].fields.name}</strong> on ${date(i.sys.createdAt, 'MMMM Do, YYYY')}
</time>
</p>
</header> ${params.slug ? markdown(i.fields.body) : truncate(markdown(i.fields.body), 250)}
</article>
`).join('')}<div class="pagination">
${postsMeta.skip > 0 && (postsMeta.skip - postsMeta.limit) !== 0 ? `<a class="pagination--prev" href="?skip=${postsMeta.skip - postsMeta.limit}">← Prev</a>` : ''} ${(postsMeta.skip - postsMeta.limit) === 0 ? `
<a class="pagination--prev" href="/">← Prev</a>
` : ''}
${postsMeta.total > (postsMeta.limit + postsMeta.skip) ? `
<a class="pagination--next" href="?skip=${(Number(params.skip) || 0) + postsMeta.limit}">Next →</a>
` : ''}
</div>${partials_footer}
We’re using three small helpers here to help with date formatting, Markdown parsing and truncating long content for the article preview on the homepage (don’t forget to NPM install the extra modules if you’re following along: npm install marked moment
).
/workspace/utils/helpers/date.js
const moment = require('moment')module.exports.date = (date, format, parseFormat) => {
return moment(date, parseFormat || null).format(format)
}
/workspace/utils/helpers/markdown.js
const marked = require('marked')module.exports.markdown = chunk => {
if (!chunk) return
return marked(chunk)
}
/workspace/utils/helpers/truncate.js
module.exports.truncate = (str, maxLen, separator = ' ') => {
if (str.length <= maxLen) return str;
return '<p>' + str.substr(0, str.lastIndexOf(separator, maxLen)).replace(/<(?:.|\n)*?>/gm, '') + '…</p>';
}
And we’re done, if you run npm start
you should see a simple site showing data from your Contentful service.
You can find all the assets for this project in this GitHub repo.
Written by David Longworth. David is the Design Director at DADI.