Building Applications with Contentful, React, and GraphQL

19. jun. 24

Developing content-rich applications can be challenging, but leveraging Contentful, React, and GraphQL simplifies the process significantly. Contentful, a widely used headless CMS, enables you to manage and structure content independently from your application's codebase. This makes it an ideal solution for applications requiring frequent updates or supporting multiple content types without frequent code changes.

In this guide, we'll explore how to integrate Contentful with a React application using GraphQL. We'll cover the advantages of Contentful, from its flexible content modeling to efficient content management. Additionally, we'll delve into setting up Contentful models using migration files to ensure scalability. You'll also learn how to generate TypeScript types from Contentful's GraphQL API to enhance code reliability. Finally, we'll use Apollo Client to fetch and display the content in a seamless workflow.

By the end of this guide, you'll have a comprehensive understanding of how to harness Contentful, React, and GraphQL to create dynamic applications with ease.

Contentful Content Modeling

Contentful’s content modeling revolves around defining content types, which serve as templates consisting of customizable fields such as text, media, and references to other content types. Unlike traditional databases that require table and relationship design, Contentful abstracts these complexities by allowing developers to manage relationships and hierarchies through its UI or APIs. This approach offers an intuitive experience for content creators and provides flexibility and scalability for developers.

Setting Up a Contentful Model with Migration Files

Contentful offers a free plan that allows developers to experiment and get started easily.

Step 1: Create a Contentful Account and CMA Token

  1. Sign up for a free Contentful account.
  2. Navigate to Settings and generate a CMA (Content Management API) token.

Step 2: Create a React Project with Migration Files

Run the following commands to initialize a React project with the required dependencies and file structure:

npm create vite@latest contentful-react -- --template react-ts && cd contentful-react
npm install
npm install -D dotenv ts-node contentful-migration
mkdir migrations

Create the necessary migration files:

touch .env migrations/create_model.ts migrations/index.ts

Step 3: Configure the Migration Script

Edit migrations/index.ts to set up the migration process:

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 Completed Successfully!"))
  .catch((e) => console.error(e));

Step 4: Define the Content Model

In migrations/create_model.ts, create a "Dog" content model with fields:

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;

Step 5: Run the Migration

Execute the migration script with:

ts-node migrations/index.ts

Adding Content to Contentful

Once the migration completes, you can add content via the Contentful UI by navigating to the Content tab and clicking on Add entry to provide values for the defined fields.

Fetching Content with GraphQL

Step 1: Obtain API Key

Generate an API key from the Contentful settings and use the GraphQL playground to query content:

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

Sample query:

query getDogs {
  dogCollection {
    items {
      name
      image {
        url
      }
    }
  }
}

Generating TypeScript Types

To improve type safety, install necessary packages:

npm install -D @graphql-codegen/cli @graphql-codegen/typescript-react-apollo

Create a codegen.ts file at the project root:

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;

Run code generation:

npm run graphql-codegen

Fetching Data with Apollo Client

Install Apollo dependencies:

npm install @apollo/client graphql

Set up Apollo Client in main.ts:

import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import App from "./App";

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(),
});

Displaying Content in React Components

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

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

  if (loading) return <>Loading...</>;
  if (error) return <>Error occurred</>;

  return (
    <>
      {data?.dogCollection?.items.map((dog) => (
        <div key={dog.name}>
          <img src={dog.image?.url || ""} alt={dog.name} />
          <h5>{dog.name}</h5>
        </div>
      ))}
    </>
  );
}

Conclusion

With Contentful, React, and GraphQL, creating dynamic, scalable applications is seamless. By following this guide, you'll have a well-structured and maintainable content-driven application ready for production.

Happy coding!