Fetching data from Headless WordPress with Faust.js
This doc assumes you're storing your pages/styles/etc. in a directory called src
, and you have the baseUrl
option set to src
in your tsconfig.json
for importing modules from the root.
NOTE: If you followed the instructions in the Getting Started with Next.js guide, you already have a working instance of the client. The following guide assumes you are setting up a client on an already existing application.
Faust.js uses GQty as the primary way to fetch data from Headless WordPress. GQty is a proxy-based GraphQL client. GQty preforms an invisible or skeleton render to identify what data is needed.
Setting Up Your Client
GQty works primarily based on TypeScript typings generated using introspection on your GraphQL API. Using WPGraphQL as your Headless WordPress API, you can enable introspection and then generate typings for GQty. You will need to do the following:
-
Run
npm install -D @gqty/cli dotenv-flow
-
Create a
generate
script in yourpackage.json
that runsgqty generate
. -
Create a
gqty.config.js
file at the root of your project. Use the following example config:require('dotenv-flow').config();
/**
* @type {import("@gqty/cli").GQtyConfig}
*/
const config = {
react: false,
scalarTypes: { DateTime: 'string' },
introspection: {
endpoint: `${process.env.NEXT_PUBLIC_WORDPRESS_URL}/graphql`,
headers: {},
},
destination: './src/client/index.ts',
subscriptions: false,
javascriptOutput: false,
};
console.log(
`Using "${config.introspection.endpoint}" to generate schema...`,
);
module.exports = config; -
Run
npm run generate
.
If everything runs smoothly, you will end up with an index.ts
and schema.generated.ts
file in your src/client
directory. The index.ts
file contains your client code, and the schema.generated.ts
file contains the typings for GQty. You can use the client as-is, but you will not get some of the benefits Faust.js provides on top of the standard GQty client. To use Faust.js with your Headless WordPress API, you will need to add some additional functionality. Replace the contents of index.ts
with the following:
/**
* GQTY: You can safely modify this file and Query Fetcher based on your needs
*/
import type { IncomingMessage } from 'http';
import { getClient } from '@faustjs/next';
import {
generatedSchema,
scalarsEnumsHash,
GeneratedSchema,
SchemaObjectTypes,
SchemaObjectTypesNames,
} from './schema.generated';
export const client = getClient<
GeneratedSchema,
SchemaObjectTypesNames,
SchemaObjectTypes
>({
schema: generatedSchema,
scalarsEnumsHash,
});
export function serverClient(req: IncomingMessage) {
return getClient<GeneratedSchema, SchemaObjectTypesNames, SchemaObjectTypes>({
schema: generatedSchema,
scalarsEnumsHash,
context: req,
});
}
export * from './schema.generated';
The code above is a modified version of the default index.ts
file that GQty generates. The getClient
function is a helper function that returns a client configured to work with the Headless WordPress API. Note the additional serverClient
function used to create a client configured to work with the server by passing in the IncomingMessage
object. This object allows Faust.js to read cookies on the server and pass them along to the Headless WordPress API.
Troubleshooting
"GraphQL introspection is not allowed"
If you run into the error message GraphQL introspection is not allowed, but the query contained __schema or __type
, you will have to enable introspection temporarily.
Introspection is disabled by default in WPGraphQL. To enable it, go to WP Admin -> GraphQL -> Enable Public Introspection.
If you are using something other than WPGraphQL
you will need to refer to the documentation to enable introspection.
Once the schema file has been generated, you can then disable introspection again.
Updating the GraphQL Schema Typings
If you followed the steps above or started a project using the examples/next/getting-started
boilerplate, you will have a schema.generated.ts
file in your src/client
directory. The typings in this file are generated from the Headless WordPress API. If you are using a different Headless WordPress API or adding additional queries or mutations to your existing Headless WordPress API, you will need to update the typings in this file. Possible reasons you might need to generate a new typings file include:
- Adding a plugin to your WordPress site that adds additional queries
- Using plugins like
Atlas Content Modeler
that add additional queries based on custom content types you create
To do this, you will need to run gqty generate
again. Running gqty generate
will update the typings in the schema.generated.ts
file and leave the index.ts
unchanged.
Providing the GQty Client to Faust.js
Using the boilerplate client code will provide two different GQty clients that you can use depending upon whether you are on the client or server. However, you will still need to give the client to Faust.js to use to fetch data. To do this, you can use the FaustProvider
component published by Faust.js, and provide it to the GQty client you want to use. This is done in your _app.tsx
file as follows:
import 'faust.config';
import { FaustProvider } from '@faustjs/next';
import { client } from 'client';
import type { AppProps } from 'next/app';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<FaustProvider client={client} pageProps={pageProps}>
<Component {...pageProps} />
</FaustProvider>
</>
);
}
This code ensures Faust.js uses the correct client to make requests on your behalf.
Using the Client to Make Queries
Assuming you have created a client using the Faust.js getClient
function, you will be able to take advantage of many of the added features that Faust.js provides and the general features provided by GQty. You can read our hooks for fetching data reference for examples of using some of the built-in hooks using the Faust.js client, but the client will support any query your Headless WordPress API. Let's look at a few examples of how to use the client to make queries.
The useQuery Hook
If you cannot use one of the WordPress-specific hooks you can use the useQuery
hook to make a query to the Headless WordPress API. This hook helps make any query supported by your Headless WordPress API. It essentially exposes your entire generated GQL schema to you for you to use what you need. For example, say you have a Header
component, and you want to fetch menu items from your "Primary" menu in WordPress. You could do so as follows:
import React from 'react';
import styles from 'scss/components/Header.module.scss';
import Link from 'next/link';
import { client, MenuLocationEnum } from 'client';
interface Props {
title?: string;
description?: string;
}
function Header({ title = 'My Site Title', description }: Props): JSX.Element {
const { menuItems } = client.useQuery();
const links = menuItems({
where: { location: MenuLocationEnum.PRIMARY },
}).nodes;
return (
<header>
<div className={styles.wrap}>
<div className={styles['title-wrap']}>
<p className={styles['site-title']}>
<Link href="/">
<a>{title}</a>
</Link>
</p>
{description && <p className={styles.description}>{description}</p>}
</div>
<div className={styles.menu}>
<ul>
{links?.map((link) => (
<li key={`${link.label}$-menu`}>
<Link href={link.url ?? ''}>
<a href={link.url}>{link.label}</a>
</Link>
</li>
))}
</ul>
</div>
</div>
</header>
);
}
export default Header;
The code above demonstrates how you can use the useQuery
hook to make a query to the Headless WordPress API for menuItems
, filter your menuItems
to be only those for the PRIMARY
menu, then use the results to render links in your Header
. Notice there is no code regarding any server-side data fetching, but if your page uses SSR or SSG, you will not have any client-side queries.
The useMutation Hook
While Faust.js does not provide any WordPress-specific hooks for mutations, it does provide the useMutation
hook. This hook is useful for making any mutation supported by your Headless WordPress API. For example, if you have a form on your site that admins can use to submit posts, it might look something similar to the following:
import React from 'react';
import { client } from 'client';
export interface FormData {
title: string;
content: string;
}
export function PostForm() {
const [submit, { isLoading, error }] = client.useMutation(
(mutation, { title, content }: FormData) => {
const result = mutation.createPost({
input: {
title: title,
content,
},
});
return result.post?.id;
},
);
const errorMessage = error?.message;
return (
<form
onSubmit={(e) => {
e.preventDefault();
const { postTitle, content } = e.currentTarget;
submit({
args: {
title: postTitle.value,
content: content.value,
},
});
}}
>
<input type="text" name="postTitle" placeholder="Title" />
<textarea name="content" placeholder="Content" />
<input disabled={isLoading} type="submit" value="Send" />
{errorMessage ? <p>Error: {errorMessage}</p> : null}
</form>
);
}
NOTE: The above code is not a complete example of how you would implement form submissions to your Headless WordPress API, and it demonstrates how mutations work using the Faust.js client.
The code above uses useMutation
combined with a form to create new posts by calling the WPGraphQL Headless WordPress API.
Logging Queries
Sometimes you want to understand what GraphQL queries do for debugging purposes. Faust.js provides this for you by exposing a logQueries
function, and the following code demonstrates how you might use it.
import type { IncomingMessage } from 'http';
import { getClient, logQueries } from '@faustjs/next';
import {
generatedSchema,
scalarsEnumsHash,
GeneratedSchema,
SchemaObjectTypes,
SchemaObjectTypesNames,
} from './schema.generated';
export const client = getClient<
GeneratedSchema,
SchemaObjectTypesNames,
SchemaObjectTypes
>({
schema: generatedSchema,
scalarsEnumsHash,
});
if (process.env.NODE_ENV === 'development') {
logQueries(client);
}
The logQueries
function returns a function that you can call to turn the log off.
if (process.env.NODE_ENV === 'development') {
const unsubscribe = logQueries(client);
// queries are now logging
// ...
unsubscribe();
// queries no more extended log
// ...
}
NOTE: We recommend that you turn this off in production or write code to use
process.env.NODE_ENV
to log queries safely.