How to get started with Contentful and GraphQL
We recently released a Node.js library called cf-graphql
that enables you to fetch data stored in Contentful using GraphQL. As of today, we decided to publish it as a standalone library that you can set it up in your own environment.
This sounds complicated, but it's very straightforward. Let me quickly guide you through the setup process and show you some cool things about GraphQL.
What is GraphQL?
GraphQL is a fairly new query language that gives developers the super powers to shape API call responses tailored to their needs. It was invented by Facebook to initially serve their mobile apps.
As a developer, something like GraphQL can make life a lot easier. Today, RESTful architectures are the de facto standard in the overall API landscape. The division of endpoints for every resource and the usage of the HTTP methods to model resource actions makes a lot of sense, but maybe you find yourself cursing this API architecture from time to time?
Usually, you have no way to control the shape of API responses. And very often you have to make several requests to fetch all the data from different resources which is usually needed for a UI interface. These parallel requests, and the asynchronous nature of JavaScript, increase the complexity developers have to deal with and very often more data is fetched than is actually needed. Query strings including things like includeSomething=true
or Contentful's very own include operator and select operator are solutions addressing the problems of unflexible RESTful response bodies. You can e.g. set include=1
to only retreive the entities of the first linked level in a single call and additionally use select=fields.name
to only retreive the name
field of the entities.
In a GraphQL world, there's no need for these solutions, because these problems just don't exist. A GraphQL query decides what resources and fields will be included in the response.
- Do you want to fetch several resources in a single call? No problem!
- Do you only want to retrieve an image and a name of a given resource? Easy peasy!
- Do you want to access resources linked to another resource? Go ahead!
GraphQL can help you with daily problems. Sounds too good to be true? I hear you – so let's dive right into it.
Setting up the Contentful GraphQL server
Install cf-graphql
The demo project included in the project makes use of the "Blog" space template, which you can choose to set up a newly created space. In case you don't want to set up a new space and just want to play around with cf-graphql
you can still use the demo project, which can run against a public space, too. So let's kick it off!
cf-graphql
is built to be used programmatically, which means that you can use it in plain Node.js or combine it with a web application framework like express to serve the API requests. This tutorial uses express to keep the complexity low.
In case you start a project from scratch set up a new package.json
using npm itself.
$ npm init
The next steps are to install all the needed dependencies and add them to the newly created package.json
. You can do so by executing npm install
with the --save
flag. Overall, what you need for the GraphQL server are the cf-graphql
, express
and express-graphql
. This tutorial guides you step-by-step through all of the modules and why they are needed.
$ npm install --save cf-graphql express express-graphql
// package.json
{
"dependencies": {
"cf-graphql": "^0.4.2",
"express": "^4.15.3",
"express-graphql": "^0.6.6",
}
}
Configure cf-graphql
To make cf-graphql
work you have to set up a new file server.js
which then accesses three needed authorization tokens:
- a space ID
- a content delivery token
- a content management token
The node script will access this information by reading the environment variables SPACE_ID
, CDA_TOKEN
and CMA_TOKEN
.
const spaceId = process.env.SPACE_ID;
const cdaToken = process.env.CDA_TOKEN;
const cmaToken = process.env.CMA_TOKEN;
You can then set these variables when executing the node script.
$ SPACE_ID=some-space-id CMA_TOKEN=xxx CDA_TOKEN=xxx node server.js
# wrapped in an npm script
$ SPACE_ID=some-space-id CMA_TOKEN=xxx CDA_TOKEN=xxx npm start
If you have used Contentful before you might ask yourself why you need to define a Content Management API token. The Content Management API is usually used to perform READ/WRITE operations and our GraphQL server should only perform READ operations.
The reason for this is, that cf-graphql
allows you to reference backlinks in the query document. This feature is only possible by retrieving detailed content model information that is not available in the Content Delivery API today.
Create a GraphQL schema
You can then create a new client interface which helps us in setting up a new GraphQL server.
const client = cfGraphql.createClient({spaceId, cdaToken, cmaToken});
client.getContentTypes()
.then(cfGraphql.prepareSpaceGraph)
.then(spaceGraph => {
const names = spaceGraph.map(ct => ct.names.type).join(', ');
console.log(`Contentful content types prepared: ${names}`);
return spaceGraph;
})
.then(cfGraphql.createSchema)
.then(schema => startServer(client, schema))
.catch(fail);
Let's go over this step-by-step to understand what's going on. These lines below do a few different things:
- the client fetches the complete content model from Contentful
cf-graphql
figures out the content graph for the fetched content modelcf-graphql
creates a GraphQL schema- a web server will be started
The most important part is the schema creation. A valid schema is the base for every GraphQL server. It defines the available data and the accessible resource types. In case you're interested in GraphQL schemas and type systems the documentation around it is actually a pretty good read.
Spin up the GraphQL server
The next step is then to connect the GraphQL schema with the express server that we already have on board as a dependency. Luckily there is already the express middleware express-graphql
available that helps you set this up. All it takes to start serving GraphQL is to connect the express app with the express GraphQL middleware. Following GraphQL conventions, you configure the middleware to be mounted to the /graphql
path.
const graphqlHTTP = require('express-graphql');
function startServer (client, schema) {
const app = express();
app.use('/graphql', graphqlHTTP({
context: {entryLoader: client.createEntryLoader()},
graphiql: true,
schema,
}));
app.listen(port);
console.log(`Running a GraphQL server, listening on ${port}`);
}
Let's have a look at the documentation of cf-graphql
for a moment:
The only caveat is how the context is constructed. The library expects the
entryLoader
key of the context to be set to an instance created with client.createEntryLoader().
That's important: according to the docs of cf-graphql
, you'll need to pass an entryLoader
property to the middleware which is provided by the cf-graphql
client itself.
One interesting thing to point out is the truthy graphiql
property. GraphiQL is an extremely handy graphical and interactive in-browser GraphQL IDE, which is really helpful when you build your first GraphQL queries. You'll use it to write your first GraphQL query in a minute. With these few lines of code you're ready to start the server and connect it to a GraphQL client.
This is a basic example. If you check the demo project you'll see that we decided to go with a bit more complex configuration.
function startServer (client, schema) {
const app = express();
app.use(cors());
// serve React Frontend
app.use('/client', express.static(path.join(__dirname,'dist')));
// ship own GraphiQL to be completely flexible
const ui = cfGraphql.helpers.graphiql({title: 'cf-graphql demo'});
app.get('/', (_, res) => res.set(ui.headers).status(ui.statusCode).end(ui.body));
// enable GraphQL extension to retrieve GraphQL versoin and
// detailed HTTP request information
const opts = {version: true, timeline: true, detailedErrors: false};
const ext = cfGraphql.helpers.expressGraphqlExtension(client, schema, opts);
app.use('/graphql', graphqlHTTP(ext));
app.listen(port);
}
We anabled CORS header to make it possible to access the GraphQL endpoint from within an application that is not running on the same origin and also used cf-graphql
helper utils to serve an encapsulated GraphiQL on a different endpoint and enrich the response with useful information like timing of the underlying HTTP requests.
Figure out your first GraphQL query
Before starting to implement GraphQL in a client you have to figure out your first query. GraphiQL is an extremely helpful tool to help you do just that. To access the GraphIDE you can use an online demo or execute npm start
inside of the demo project included in cf-graphql
itself.
Inside the IDE you can try out queries and see the results. This doesn't sound too exciting, but GraphiQL fetches the complete schema on startup which means that it can provide auto-completions for almost everything. This way of writing a query becomes a breeze and even if you're not familiar with GraphQL the whole concept makes sense then.
The example space "Photo Gallery" comes with a content model including the content types "Author", "Image" and "Photo Gallery". To access the entries for all three content types you would have to make three API calls in the RESTful world. In GraphQL you can combine them all into a single query.
{
authors {
name
}
categories {
title
}
posts {
title
}
}
The query response includes the following data plus some additional meta information:
{
"data": {
"authors": [
{
"name": "Lewis Carroll"
},
{
"name": "Mike Springer"
}
],
"categories": [
{
"title": "Literature"
},
{
"title": "Children"
}
],
"posts": [
{
"title": "Seven Tips From Ernest Hemingway on How to Write Fiction"
},
{
"title": "Down the Rabbit Hole"
}
]
}
}
Using GraphQL you could not only save two HTTP calls from the client but also cut off all the data you're not interested in. The query only requested the title
field of the photo galleries and images, and the name
field of all the authors. Pure magic!
How to use it in the Frontend
Now that you defined your first query it's time to look around for options to adopt GraphQL in a Frontend React stack – in this regard you definitely have the agony of choice. There are several frameworks you could use. This tutorial uses Apollo but alternatives like Relay or even Facebook's GraphQL.js will work just fine.
Implement the Apollo framework
To use Apollo in a React environment you have to install it.
$ npm install --save react-apollo
The demo project comes with a complete webpack and babel setup so that you don't have to worry about bundling and ES5 compatibility.
Then create an Apollo client which you configure to use the GraphQL server being available at /graphql/
.
import { ApolloClient, createNetworkInterface } from 'react-apollo';
const client = new ApolloClient({
networkInterface: createNetworkInterface({
uri: '/graphql/',
})
});
Next, you have to include an ApolloProvider
element in your React components. ApolloProvider
makes the GraphQL client available to all of your components enhanced by the graphql()
function which you'll use next.
So TL;DR without wrapping the GraphQL elements in your React components with an ApolloProvider
it won't work. ;)
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<!-- GraphQL components will go here -->
</ApolloProvider>
);
}
}
Enhanced components? Apollo follows the principle of higher-order components which means that you can enhance all your components with GraphQL capabilities. Define your GraphQL query using the gql
tagged template literal, and then combine it with the graphql
method which will return a function that's able to enhance your components after execution.
import {gql, graphql} from 'react-apollo';
const graphQLQuery = gql`
{
authors {
twitterHandle
}
images {
title
}
photoGalleries {
title
}
}
`;
const getGraphQLEnhancedComponent = graphql(graphQLQuery);
With the method getGraphQLEnhancedComponent
you can now wrap any component and give it access to the state and response of a GraphQL query.
Let's implement a DataViewer
component that displays the data we just fetched. Wrapped with getGraphQLEnhancedComponent
it now has access to the loading state of the query and also to the data of photoGalleries
, authors
and images
.
const DataViewer = ({ data: {loading, error, photoGalleries, authors, images }}) => {
if (loading) return <p>Loading ...</p>;
if (error) return <p>{error.message}</p>;
return (
<div>
// authors from GraphQL query available
<ul>{ authors.map( a => <li key={a.twitterHandle}>{a.twitterHandle}</li>) }</ul>
// images from GraphQL query available
<ul>{ images.map( i => <li key={i.title}>{i.title}</li>) }</ul
// galleries from GraphQL query available
<ul>{ photoGalleries.map( p => <li key={p.title}>{p.title}</li>) }</ul>
</div>
)
};
const DataViewerWithData = getGraphQLEnhancedComponent(DataViewer);
The last thing to do then is to implement the new higher-order component DataViewerWithData
in your app.
class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<DataViewerWithData/>
</ApolloProvider>
);
}
}
You can find a complete Frontend implementation in the demo project of cf-graphql
itself, too.
In case you're looking for more detailed information on how to use Apollo, head over to the official docs of react-apollo.
Other possible queries
In this example you only implemented a pretty basic query. cf-graphql
is able to resolve more advanced queries, too. It supports Contentful query parameters, id filtering, and even back references.
{
authors {
_backrefs {
posts__via__author {
title
}
}
}
post(id: "some-post-id") {
title
author
comments
}
posts(q: "fields.rating[gt]=5") {
title
rating
}
}
Sum up
GraphQL is definitely an approach worth checking out. It gives developers the freedom and the flexibility they need to write applications faster and with less complexity and cognitive overhead.
We at Contentful are really excited about this new way of API interfaces and would love to hear your thoughts on it.
Check out the demo to see your own local version of it. ;)
Further resources
Next steps
- Explore the JavaScript CDA SDK GitHub repository
- Learn how to use the Sync API with JavaScript
- Create an Express.js application with Contentful
Not what you’re looking for? Try our FAQ.