Skip to main content

Integrating with Contentful

Contentful Logo

In this tutorials we will look at dynamic routing using the extension from part 1.

Overview

Using the course data from part 1 we will make routes like https://your-site.com/course/course-name render content for that course.

1. Create an API to access course data based on it's ID

At the moment we have a courses query that allows us to access all of the courses. We now want to create a course query so we can get a specific course.

i. Add to your API

server/src/falcon-contentful-api/index.js

...
async course(_, { id }) {
const { environment, spaceId, contentDeliveryApi } = this.config;
const query = {
access_token: contentDeliveryApi,
content_type: 'course'
};
const data = await this.get(`/spaces/${spaceId}/environments/${environment}/entries/${id}`, query);
return JSON.parse(data);
}
...

ii. Add to your extensions GraphQL Schema

We will also need to add this query to our GraphQL schema.

server/src/falcon-content-extension/schema.graphql

extend type Query {
courses: Courses! @cache(ttl: 15)
course(id: ID!): Course! @cache(ttl: 15)
}
...

iii. Test your endpoint.

You should now be able to test your GraphQL. Make sure falcon-server is running and is on https://localhost:4000/graphql.

Also remember to replace YOUR_CONTENT_ID with your course ID.

2. Create your content query and component

You now need to create a component to access this data.

As before we want to add a content component and a query component.

|-client
|-src
|-components
|- Course
index.js
CourseList.js
CourseListQuery.js

Course.js
CourseQuery.js

i. CourseQuery.js

client/src/components/Course/CourseQuery.js

import gql from 'graphql-tag';
import { Query } from '@deity/falcon-data';

const GET_COURSE = gql`
query Course($id: ID!) {
course(id: $id) {
fields {
title
description
}
}
}
`;

export class CourseQuery extends Query {
static defaultProps = {
query: GET_COURSE
};
}

At the moment we are only querying the title and description. If you want more data you'd have to add it to server/src/falcon-content-extension/schema.graphql as well as your query here.

ii. Course.js

At the moment we just rendering the title, description and changing the page title.

Notice we are using <Helmet> to change the page title. To learn more about this read how falcon manages meta data via helmet.

client/src/components/Course/Course.js

import React from 'react';
import { PageLayout } from '@deity/falcon-ui-kit';
import { Helmet } from 'react-helmet-async';
import { CourseQuery } from './CourseQuery';

const Course = ({ match: { params } }) => {
const { id } = params;
return (
<PageLayout>
<CourseQuery variables={{ id }}>
{({ data: { course } }) => {
const {
fields: { title, description }
} = course;
return (
<React.Fragment>
<Helmet>
<title>{title}</title>
</Helmet>

<h1>{title}</h1>
<p>{description}</p>
</React.Fragment>
);
}}
</CourseQuery>
</PageLayout>
);
};

export default Course;

3. Return URL data from our API

<SwitchDynamicURL> is used for routing in Falcon Platform, it's worth looking at routing details here.

All APIs that are registered in our server/config/default.json and contain both the below methods will be checked.

  • fetchUrl
  • getFetchUrlPriority

getFetchUrlPriority is used to set priority of your API. You might want to alter the priority if you're worried about URLs clashing. This is covered in our docs.

server/src/falcon-contentful-api/index.js

...
module.exports = class ContentfulApi extends ApiDataSource {
...

async getCourses() {
const { environment, spaceId, contentDeliveryApi } = this.config;

const query = {
access_token: contentDeliveryApi,
content_type: 'course'
};

const data = await this.get(`/spaces/${spaceId}/environments/${environment}/entries`, query);
const courseCollection = JSON.parse(data);

const { items } = courseCollection;

return items;
}

async fetchUrl(_, params) {
const { path } = params;

if (path.split('/')[1] === 'course') {
const items = await this.getCourses();
const current = items.find(item => `/course/${item.fields.slug}` === path);

if (current) {
const { fields, sys } = current;
return {
id: sys.id,
path: fields.slug,
type: 'content-course'
}
}
}
return null;
}

getFetchUrlPriority() {
return this.fetchUrlPriority;
}
}

There is quite a bit going on here.

i. getFetchUrlPriority

We are just returning the default priority set in ApiDataSource.

If you wanted to change this you could follow a similar pattern to the example below. This returns a low priority for all urls except 'hello'.

EXAMPLE

import { ApiUrlPriority } from '@deity/falcon-server-env';
...
getFetchUrlPriority(path) {
return (path === 'hello') ? ApiUrlPriority.HIGH : ApiUrlPriority.LOW;
}

ii. fetchUrl

In this method we are checking a few things. Firstly we are checking the current path starts with course.

if (path.split('/')[1] === 'course') {

We then check all the courses to see if any of them have a matching slug.

const items = await this.getCourses();
const current = items.find(item => `/course/${item.fields.slug}` === path);

If they match then we return an FetchUrlResult object.

@deity/falcon-server-env/src/types.ts

export interface FetchUrlResult {
id: string | number;
type: string;
path: string;
redirect: boolean;
}

In our case we don't need to pass a redirect so just pass a type, id and path.

return {
id: sys.id,
path: fields.slug,
type: 'content-course'
};

If your URLs don't match remember to return null

4. Add a <Route> check for your content type.

Now we need to check for our type, content-course.

In our example apps the routing is handled in client/src/App.js.

client/src/App.js

...
import { SwitchDynamicURL } from '@deity/falcon-front-kit';
...
const Course = loadable(() => import('./components/Course/Course'));
...
<SwitchDynamicURL>
<Route exact type="content-course" component={Course} />
</SwitchDynamicURL>
...

You will see here we pass the prop type to our <Route>. This will return the Course component if the type matches.

n.b. If your'e using loadable your component needs to be the default export

5. Finished

That's it. There is a lot covered in this short tutorial and it's highly recommended to read up on routing in more details.