DEV Community

Cover image for Integrating Contentful in React: A Beginner’s Guide to Content Modeling and Fetching Data with GraphQL
Balázs Madarász
Balázs Madarász

Posted on

Integrating Contentful in React: A Beginner’s Guide to Content Modeling and Fetching Data with GraphQL

Creating dynamic, content-rich applications can seem complex, but with Contentful, React, and GraphQL, the process becomes much smoother. Contentful, a popular headless CMS, empowers you to manage and structure content independently of your app’s codebase. This makes it a fantastic choice for building apps that need frequent updates or support multiple content types without constant code tweaks.

In this post, we’ll walk through how to integrate Contentful with a React app using GraphQL. We’ll start by exploring Contentful’s advantages, from flexible content modeling to efficient content management. Then, we’ll dive into setting up Contentful models with migration files, which allow you to manage your content structure in a more scalable way. You’ll learn how to generate TypeScript types from Contentful's GraphQL API to keep your code robust and reliable. Finally, we’ll cover fetching data with Apollo Client to pull it all together, creating a seamless flow between Contentful and your React components.

By the end of this guide, you’ll have a solid foundation for using Contentful, React, and GraphQL together to power your projects with dynamic content. Let’s get started!

Image description

Contentful modeling

Contentful modeling focuses on defining content structures using "content types" that act like templates, each with customizable fields such as text, media, or references to other content types. Unlike traditional database modeling, which involves designing tables, relations, and queries, Contentful abstracts these complexities, letting developers manage relationships and hierarchies directly through its UI or APIs. This approach makes Contentful more intuitive for content creators while offering developers flexibility and scalability similar to database schema design.

Create a Contentful model with migration

The first step is to create a free account and create a CMA token in Contentful. You need this token to run migrations in Contentful.

Then you can create your React project with migration files. The next commands will create an empty project with the dependencies and file structure:

npm create vite@latest contentful-react -- --template react-ts && cd contentful-react && npm i && npm i -D dotenv ts-node contentful-migration && mkdir migrations && touch .env migrations/create_model.ts migrations/index.ts
Enter fullscreen mode Exit fullscreen mode

The index file in the migrations folder is the entrypoint to run your migrations, and you can setup the migration connection here.

import "dotenv/config";
import * as path from "path";
import { runMigration } from "contentful-migration";

const options = {
  filePath: path.resolve("migrations", "create_model.ts"),
  spaceId: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CMA_TOKEN,
};
runMigration(options)
  .then(() => console.log("Migration Done!"))
  .catch((e) => console.error(e));
Enter fullscreen mode Exit fullscreen mode

The config pointing to the first migration (create_model.ts), it will create a "Dog" model with two fields:

  • Name: this is a simple text field
  • Image: is an asset field, we can upload easily image to the entries.
import { MigrationFunction } from "contentful-migration";

module.exports = function (migration) {
  const dog = migration.createContentType("dog", {
    name: "Dog",
  });

  dog.createField("name").name("Name").type("Symbol").required(true);
  dog.createField("image").name("Image").type("Link").linkType("Asset");
} as MigrationFunction;
Enter fullscreen mode Exit fullscreen mode

That's it, you can run your migration with ts-node:

ts-node migrations/index.ts
Enter fullscreen mode Exit fullscreen mode

Add some data to Contentful

After the migration, you are able to add some data on Contentful UI, under the Content tab.

If you click on the "Add entry" button, then you can enter the dog's name and you can upload an image. Your entry will be saved automaticaly in draft mode. You can publish it later with the "Publish" button.

We would like to fetch the content, so you need an API key. You can generate one under the settings.

Contentful provide you a graphql playgrond, where you can retrieve your contents. You have to replace the space id and api key in the url.

https://graphql.contentful.com/content/v1/spaces/{SPACE_ID}/environments/master/explore?access_token={API_KEY}
Enter fullscreen mode Exit fullscreen mode

In the playground, if you run the the next query, then you will get the new entries in json format.

query getDogs {
  dogCollection {
    items {
      name
      image {
        url
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Generate Typescript types from the model

You can easily generate Typescript types from graphql queries, so it is much easier and safe fetch and use your content data in your app.

First, you have to install few packages:

npm i -D @graphql-codegen/cli @graphql-codegen/typescript-react-apollo
Enter fullscreen mode Exit fullscreen mode

Then you need to create a "codegen.ts" at the root of your app. It is a config file for the code generation.

import "dotenv/config";
import { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  schema: [
    {
      [`https://graphql.contentful.com/content/v1/spaces/${process.env.VITE_CONTENTFUL_SPACE_ID}`]: {
        headers: {
          Authorization: `Bearer ${process.env.VITE_CONTENTFUL_API_KEY}`,
        },
      },
    },
  ],
  documents: ["src/**/*.graphql"],
  ignoreNoDocuments: true,
  generates: {
    "./src/gql/graphql.ts": {
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-react-apollo",
      ],
      config: {
        withHooks: true,
      },
    },
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Now you can create a "dog.graphql" file in your src folder, it should contain the query what you tried in the playground. Codegen will create the types based on this query.
If you run now the next command, you will get all the types you need to fetch your content.

npm run graphql-codegen
Enter fullscreen mode Exit fullscreen mode

Fetch data from Contentful with Apollo Client

The final piece of the puzzle is to fetch and use the content in your app.
Apollo Client is a great choice to handle graphql calls in your app.

There is the dependencies for Apollo:

npm i @apollo/client graphql
Enter fullscreen mode Exit fullscreen mode

In main.ts, you have to setup Apollo Provider, then you can use Apollo anywhere in your app:

import React from "react";
import ReactDOM from "react-dom/client";
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import App from "./App.tsx";
import "./index.css";

const client = new ApolloClient({
  uri: `https://graphql.contentful.com/content/v1/spaces/${process.env.VITE_CONTENTFUL_SPACE_ID}`,
  headers: {
    Authorization: `Bearer ${import.meta.env.VITE_CONTENTFUL_API_KEY}`,
  },
  cache: new InMemoryCache(),
});

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>
);

Enter fullscreen mode Exit fullscreen mode

And finaly there is a component where we use the generated "useGetDogsQuery" hook. It give you a data object which contains all your content queried by graphql.

import "./App.css";
import { useGetDogsQuery } from "./gql/graphql";

function App() {
  const { loading, error, data } = useGetDogsQuery();
  console.log({ loading, error, data });

  if (loading) {
    return <>Loading...</>;
  }

  return (
    <>
      {data?.dogCollection?.items.map((dog) => (
        <div className="card" style={{ width: "18rem", margin: "10px" }}>
          <img className="card-img-top" src={dog?.image?.url || ""} alt="dog" />
          <div className="card-body">
            <h5 className="card-title">{dog?.name}</h5>
          </div>
        </div>
      ))}
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

If you run your app, you will see a list of dogs which includes the names and images of the dogs.

Finally, you have everything you need to build an app that’s both dynamic and easy to manage. I hope this guide helps you kickstart your project.

Feel free to share your thoughts or questions in the comments.

Happy coding!

Top comments (0)