The Grafbase Edge Gateway gives developers the power to write custom resolvers, connect APIs, add edge caching, auth, permissions, and much more.
In this guide we'll explore extending the Contentful GraphQL API with custom Edge Resolvers.
Let's imagine we use Contentful to store a list of beautiful properties we want to rent out. As a developer, we want to show the name and location of each of our properties as well as the current temperature so potential guests can feel those holiday vibes.
You'll need an account with Contentful to follow along with this guide.
Let's begin by making a new model for Property
from inside the Contentful app:
Next, make sure to add fields for:
- Name — Short text field type
- Location — Location field type
Now save the content model.
We'll next move to the Content section to add our very first Property entry.
You'll want to give your Property a name, in this case, we'll use the name Grand Hotel Central and search for the location in Barcelona using the embedded Google Maps.
Once you've found the location, click Publish.
Repeat the steps above for every location you want to store inside Contentful. You could extend the model to contain images, ratings etc.
From within your Contentful Space, go to Settings > API Keys > New API Key and give the access token a name.
Now click save and reveal the Content Delivery API access token value and save it for later along with the Space ID.
We're now ready to create a Grafbase Edge Gateway that we can run locally, and later deploy to the edge.
We'll create a new project in the directory contentful-grafbase
but you can also run the command below inside of an existing project, just remove the directory name contentful-grafbase
if you do.
npx grafbase init contentful-grafbase --template graphql-contentful
This will scaffold a new project that uses the GraphQL connector using the Grafbase SDK. The file grafbase/grafbase.config.ts
should look something like this:
import { config, connector, graph } from '@grafbase/sdk'
const g = graph.Standalone()
const contentful = connector.GraphQL('Contentful', {
url: g.env('CONTENTFUL_API_URL'),
headers: headers => {
headers.set('Authorization', `Bearer ${g.env('CONTENTFUL_API_TOKEN')}`)
},
})
g.datasource(contentful)
export default config({
graph: g,
})
Now open the file grafbase/.env
and add the following values (make sure to use the same SPACE_ID
value from the previous step):
CONTENTFUL_API_URL=https://graphql.contentful.com/content/v1/spaces/SPACE_ID
CONTENTFUL_API_TOKEN=
We're now ready to try fetching data from Contentful using the Grafbase Edge Gateway.
Run the following command:
npx grafbase dev
Now open http://127.0.0.1:4000
and execute a GraphQL query to fetch all properties:
{
contentful {
propertyCollection {
items {
name
location {
lat
lon
}
}
}
}
}
You should see in the response something like:
{
"data": {
"contentful": {
"propertyCollection": {
"items": [
{
"name": "Grand Hotel Central",
"location": {
"lat": 41.38497,
"lon": 2.177765
}
}
]
}
}
}
}
Viola! We are now merging the Contentful GraphQL API with the Grafbase Edge Gateway.
We'll now create a field resolver for weather
that returns the current temperature from the OpenWeather API.
Grafbase automatically prefixes the connected APIs with the namespace
we set inside grafbase/grafbase.config.ts
. This means that the type Property
we created earlier inside Contentful will now be referenced as ContentfulProperty
in the Grafbase Edge Gateway.
Inside grafbase.config.ts
we can extend the Contentful type and add our new weather: Float
field:
g.extend('ContentfulProperty', {
weather: {
returns: g.float().optional(),
resolver: 'contentful/property/weather',
},
})
Now create the file resolvers/contentful/property/weather.ts
and add the following:
export default function Resolver() {
// ...
}
Before we can make a request to fetch the current weather, we need to get the location of the Property
entry.
Make sure to update the resolver and name the first arg root
. Then we can destructure from root
the value location
:
export default function Resolver(root) {
const { location } = root
}
It's now time to create an API key with OpenWeather so we can get the current weather data.
Now add your OpenWeather API key to the file grafbase/.env
:
OPENWEATHER_API_KEY=
You can now use the environment variable inside the resolver using process.env
:
export default function Resolver(root) {
const { location } = root
const apiKey = process.env.OPENWEATHER_API_KEY
}
It's now time to piece together the location.lat
and location.lon
values from Contentful with an API request to OpenWeather.
OpenWeather API returns the current weather in the following JSON structure:
{
"coord": {
"lon": -16.5193,
"lat": 9.6257
},
"weather": [
{
"id": 804,
"main": "Clouds",
"description": "overcast clouds",
"icon": "04d"
}
],
"base": "stations",
"main": {
"temp": 301.42,
"feels_like": 304.95,
"temp_min": 301.42,
"temp_max": 301.42,
"pressure": 1012,
"humidity": 74,
"sea_level": 1012,
"grnd_level": 1012
},
"visibility": 10000,
"wind": {
"speed": 2.97,
"deg": 181,
"gust": 3.58
},
"clouds": {
"all": 95
},
"dt": 1685459581,
"sys": {
"sunrise": 1685429076,
"sunset": 1685474573
},
"timezone": -3600,
"id": 0,
"name": "",
"cod": 200
}
The response temperatures are by default Kevlin, but we can change this to Celsius by adding &units=metric
to the URL.
Now let's put all of this together by adding the request to the resolver function:
export default function Resolver(root) {
const { location } = root
const apiKey = process.env.OPENWEATHER_API_KEY
if (!location) return null
return fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${location.lat}&lon=${location.lon}&units=metric&appid=${apiKey}`,
)
.then(res => res.json())
.then(({ main }) => main.temp)
}
For the purposes of this guide we'll only return main.temp
from the resolver.
We're now ready to test everything out. Make sure you have the Grafbase development server running:
npx grafbase dev
Now go to Pathfinder and run the same GraphQL query before but this time add weather
to the list of returned fields:
{
contentful {
propertyCollection {
items {
name
location {
lat
lon
}
weather
}
}
}
}
You should see a response that looks something like the below, including the current temperature:
{
"data": {
"contentful": {
"propertyCollection": {
"items": [
{
"name": "Grand Hotel Central",
"weather": 30.43,
"location": {
"lat": 41.38497,
"lon": 2.177765
}
}
]
}
}
}
}
Note: You must also request for location.lat
and location.lon
in the query, we'll make this optional in the future.
Earlier we hard coded &units=metric
into the URL but this isn't good for people who are expecting the temperature in other formats.
Edge Resolvers also allow you to configure arguments that can be passed to fields, we'll do this for the units and set the default to metric
.
To do this, we will:
- Add the enumeration definition
Unit
- Add
args
toweather
with a default value
Inside grafbase.config.ts
you should update it to include the new enum
and args
:
const unit = g.enum('Unit', ['standard', 'metric', 'imperial'])
g.extend('ContentfulProperty', {
weather: {
args: { unit: g.enumRef(unit).default('metric') },
returns: g.float(),
resolver: 'contentful/property/weather',
},
})
Now inside the resolver code you can fetch the unit
value from the second argument, and update the URL used by fetch
to now include the unit
value &units=${unit}
:
export default function Resolver(root, { unit }) {
const { location } = root
const apiKey = process.env.OPENWEATHER_API_KEY
if (!location) return null
return fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${location.lat}&lon=${location.long}&units=${unit}&appid=${apiKey}`,
)
.then(res => res.json())
.then(({ main }) => main.temp)
}
You can now execute the following GraphQL query and pass any of the enums defined to the weather field unit
argument:
{
contentful {
propertyCollection {
items {
name
location {
lat
lon
}
weather(unit: imperial)
}
}
}
}
That's it! This guide has been a quick deep dive into how you can extend the Contentful GraphQL API with your own GraphQL types and resolvers using the Grafbase Edge Gateway.
Remember that you can do this with any GraphQL API or REST API using OpenAPI.