Astro Quick-Start Guide

Using Craft CMS GraphQL with Astro

How to fetch content data from Craft CMS using GraphQL.

By imple­ment­ing the sup­port for Craft’s Ele­ment API JSON data, we’ve already done a lot of the leg work for GraphQL sup­port. There are some code changes we have to make but we can reuse the same com­po­nents for fetch­ing entries that we’ve already built.

To fol­low along with this video, you will need to clone and run the house-quest-craft project. Set­ting up this project is cov­ered in the pre­vi­ous video.

Com­plete EntriesFetch.astro component:

export async function getEntries(query: string) {
    const url = "http://localhost:8888/api/";
    const response = await fetch(url, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            query: query,
        }),
    });
    let json = await response.json();
    return json.data.entries;
}

Com­plete EntryFetch.astro component:

---
export async function getEntry(query: string) {
    const url = "http://localhost:8888/api";
    const response = await fetch(url, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            query: query,
        }),
    });

    let json = await response.json();
    return json.data.entry;
}
---

Com­plete MarketNews.astro component:

---
import { getEntries } from '../components/EntriesFetch.astro';

let query = `{
            entries(sectionId: 12, limit: 3) {
                id
                title
                slug
                postDate
                ... on marketNews_default_Entry {
                    summaryText
                }
            }
        } 
        `;
let marketNewsEntries = await getEntries(query);
---

<div class="bg-secondary pt-16 pb-20 px-4 sm:px-6 lg:pt-24 lg:pb-28 lg:px-8 border-opacity-50 border-t-8 border-primary">
    <div class="relative max-w-lg mx-auto lg:max-w-7xl">
      <div>
        <h2 class="text-3xl tracking-tight font-extrabold text-gray-900 sm:text-4xl">Market News</h2>
        <p class="mt-3 text-xl text-gray-800 sm:mt-4">An expert view of what's happening in the local housing market.</p>
      </div>
      <div class="mt-6 grid gap-16 pt-12 lg:grid-cols-3 lg:gap-x-5 lg:gap-y-12">
        {marketNewsEntries.map(entry => (
        <div>
          <a href={'/news/' + entry.slug} class="block mt-4">
            <p class="text-xl font-semibold text-gray-900">{entry.title}</p>
            <p class="mt-3 text-base text-gray-900">{entry.summaryText}</p>
          </a>
        </div>
        ))}
      </div>
    </div>
  </div>

Com­plete [slug].astro component:

---
import Layout from '../../layouts/Layout.astro';
import InteriorHeader from '../../components/InteriorHeader.astro';

import { getEntry } from '../../components/EntryFetch.astro';
import { getEntries } from '../../components/EntriesFetch.astro';

const { slug } = Astro.params;

let singleArticleQuery = `{
		entry(sectionId: 12, slug: "${slug}") {
			id
			title
			slug
			... on marketNews_default_Entry {
				summaryText
				richText
			}
		}
	}
`

let entry = await getEntry(singleArticleQuery);

export async function getStaticPaths() {

    let allArticlesQuery = `
		{
			entries(sectionId: 12) {
				id
				title
				slug
				postDate
				... on marketNews_default_Entry {
					summaryText
					richText
				}
			}
		}
		`

    let entries = await getEntries(allArticlesQuery);
    return entries.map(( entry: { slug: string }) => {
        return {
            params: { slug: entry.slug},
            props: { entry: entry },
        };
    });
}

---

<Layout>
    <InteriorHeader />
    <div class="bg-white">
        <div class="relative py-16 bg-tertiary overflow-hidden">
			<div class="relative px-4 sm:px-6 lg:px-8">
				<div class="mt-6 prose prose-slate prose-lg text-gray-500 mx-auto">
                    <h1>{entry.title}</h1>
                    <div set:html={entry.richText}></div>
                    <p><a href="/news">Back to News</a></p>
                </div>
            </div>
        </div>
    </div>
</Layout>

Com­plete index.astro (home­page) component:

---
import Layout from "../layouts/Layout.astro";
import { getEntry } from "../components/EntryFetch.astro";
import MarketNews from "../components/MarketNews.astro";

let query = `
	{
		entry(section: "homepage") {
			title
			id
			... on homepage_homepage_Entry {
				richText
			}
		}
	}
`
let homepageContent = await getEntry(query);
---
<Layout title="Welcome Home">
    <div class="bg-white">
      <div class="max-w-full mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8 bg-primary shadow-2xl shadow-secondary border-opacity-50 border-b-8 border-secondary">
        <div class="text-center">
          <img
            class="mx-auto h-40 w-40 rounded-full xl:w-56 xl:h-56 shadow-xl shadow-gray-500"
            src="/images/omar-lopez-Gx5-zf_HE9w-unsplash.jpg"
            alt=""
          />
          <p
            class="mt-1 text-4xl font-extrabold text-gray-900 sm:text-5xl sm:tracking-tight lg:text-6xl"
          >
            Olivia Lopez, CRS         
          </p>
          <p class="max-w-xl mt-5 mx-auto text-xl text-gray-800">
            In a competitive market, Olivia is the <strong>experienced real estate agent</strong> you want
            on your side.
          </p>
        </div>
      </div>
      <div class="relative py-16 bg-tertiary overflow-hidden">
        <div class="relative px-4 sm:px-6 lg:px-8">
          <div class="mt-6 prose prose-indigo prose-lg text-gray-500 mx-auto">
              <div set:html={homepageContent.richText} />
          </div>
        </div>
      </div>
	  <MarketNews />
    </div>
</Layout>

Astro Quick-Start Guide is made up of the following videos: