wp-graphql
v1.0.0
Published
A client-side GraphQL wrapper for the WordPress REST API
Downloads
26
Readme
wp-graphql
Client-side GraphQL convenience wrapper for the WordPress REST API
Why?
- Declarative data fetching.
- Human-readable queries/mutations.
- Zero nested callbacks or long promise chains.
Install
$ npm install --save wp-graphql
GraphiQL Demo
Note: The demo above queries against the public WordPress REST API demo provided by WordPress. Because of this, some queries and all mutations will not work.
Usage
Load and localize script
When loading the script, localize it with a rest_url
and generated nonce
(only required if you need to make authenticated requests).
<?php
function load_scripts() {
wp_register_script('myscript', 'path/to/myscript.js');
wp_localize_script('myscript', 'AUTH', array(
root => esc_url_raw(rest_url()),
nonce => wp_create_nonce('wp_rest'),
));
wp_enqueue_script('myscript');
}
add_action('wp_enqueue_scripts', 'load_scripts');
Create and use instance
Once an instance is created, you are able to query and mutate data using standard GraphQL syntax.
// myscript.js
import WPGraphQL from 'wp-graphql';
const gql = new WPGraphQL(AUTH.root, { auth: AUTH.nonce });
gql.send(`
query {
user(id: 1, context: edit) {
id
name
email
}
firstThreeUsers: users(per_page: 3, orderby: id) {
name
}
settings {
title
}
}
`).then(data => console.log(data));
API
Configuration
The WPGraphQL
class accepts an object with the Config
interface as the second argument to configure the instance.
/** Authenticate with Basic Auth (Requires Basic Auth plugin) */
interface BasicAuth {
username: string;
password: string;
}
/** Either a BasicAuth object or a WordPress nonce string */
type Authentication = BasicAuth | string;
interface CustomPostTypeParams {
/** The singular name of the custom content type. (e.g. "book") */
name: string;
/** The plural name of the custom content type. (e.g. "books") */
namePlural: string;
/** The URL base name for the custom content type. (e.g. "books") */
restBase: string;
}
interface Config {
/** Additional context to be passed to all resolvers. */
context?: any;
/** Custom mutations to be merged into the library upon instantiation. */
mutations?: GraphQLFieldConfigMap<any, any>;
/** Cookie generated by WordPress used for authentication. */
nonce?: string;
/** List of `postTypeConfig` to be merged into the library upon instantiation. */
postTypes?: CustomPostTypeParams[];
/** Custom queries to be merged into the library upon instantiation. */
queries?: GraphQLFieldConfigMap<any, any>;
}
Methods
send
Execute a single GraphQL query or mutation.
type send = <TResponse>(gql: string, variables?: object, operationName?: string) => PromiseLike<TResponse>
batch
Execute a chain of GraphQL queries and/or mutations.
If at any point a response returns with a field labelled extract
, all first-level fields are available to be used in the immediately subsequent query or mutation. Variables may also still be passed to batch
, but keep in mind that the variables passed to the batch
signature will always win if a name conflict occurs in an extracted variable.
Keep in mind also that if queries are batched with the same field names, each subsequent query will overwrite the last one. To avoid this, just tag the fields with a unique name.
type batch = <TResponse>(gql: string, operationNames: string[], variables?: object) => PromiseLike<TResponse>
Example:
import WPGraphQL from 'wp-graphql';
const gql = new WPGraphQL(AUTH.root, { auth: AUTH.nonce });
gql.batch(`
query first {
extract: me {
id
name
}
}
query second($id: Int!) {
user(id: $id) {
name
}
}
mutation third($anotherId: Int!, $name: String) {
updateUser(id: $anotherId, name: $name) {
id
name
}
}
`, ['first', 'second', 'third'], { anotherId: 2, name: 'Sally Jones' }).then(data => console.log(data));
Resolvers
The preloaded queries
and mutations
are listed below with their required parameters where applicable.
Note: Some of the parameters are enum
type (e.g. context
). Do not surround these in quotes when performing your queries/mutations. Additionally, parameters of type String
must be surrounded in double quotes ("
).
Queries
Query | Description
---|---
categories
| Fetch a list of categories.
category(id: Int!)
| Fetch a single category by ID.
comments
| Fetch a list of comments.
comment(id: Int!)
| Fetch a single comment by ID.
mediaList
| Fetch a list of media items.
media(id: Int!)
| Fetch a single media item by ID.
pages
| Fetch a list of pages.
page(id: Int!)
| Fetch a single page by ID.
postStatuses
| Fetch a list of post statuses.
postStatus(id: PostStatus!)
| Fetch a single post status by name.
postTypes
| Fetch a list of post types.
postType(slug: String!)
| Fetch a post type by slug.
posts
| Fetch a list of posts.
post(id: Int!)
| Fetch a single post by ID.
revisions(id: Int!)
| Fetch a list of revisions by post ID.
revision(id: Int!, parentId: Int!)
| Fetch a single revision by revision ID and parent post ID.
settings
| Fetch site settings.
tags
| Fetch a list of tags.
tag(id: Int!)
| Fetch a single tag by ID.
taxonomies
| Fetch a list of taxonomies.
taxonomy(slug: String!)
| Fetch a single taxonomy by slug.
users
| Fetch a list of users.
user(id: Int!)
| Fetch a single user by ID.
me
| Fetch the currently logged in user.
Mutations
Mutation | Description
---|---
addCategory(name: String!)
| Create a new category.
updateCategory(id: Int!)
| Update a category by ID.
deleteCategory(id: Int!)
| Delete a category by ID.
addComment(content: String!)
| Create a new comment.
updateComment(id: Int!)
| Update a comment by ID.
deleteComment(id: Int!)
| Delete a comment by ID.
addMedia(file: String!, filename: String!)
| Create a new media item.Note: file
must be of type Blob
, File
, or ArrayBuffer
.
updateMedia(id: Int!)
| Update media by ID.
deleteMedia(id: Int!)
| Delete media by ID.
addPage()
| Create a new page.
updatePage(id: Int!)
| Update a page by ID.
deletePage(id: Int!)
| Delete a page by ID.
addPost(title: String!, content: String!, excerpt: String!)
| Create a new post.Note: Only one of title
, content
, or excerpt
is required for a sucessful request.
updatePost(id: Int!)
| Update a post by ID.
deletePost(id: Int!)
| Delete a post by ID.
deleteRevision(id: Int!, parentId: Int!)
| Delete a single revision by revision ID and parent post ID.
updateSettings()
| Update site settings.
addTag(name: String!)
| Create a new tag.
updateTag(id: Int!)
| Update a tag by ID.
deleteTag(id: Int!)
| Delete a tag by ID.
addUser(email: String!, password: String!, username: String!)
| Create a new user.
updateUser(id: Int!)
| Update a user by ID.
deleteUser(id: Int!)
| Delete a user by ID.
Advanced Usage
Static type checking with TypeScript
Note: Requires TypeScript
^2.3.0
. (At time of writing, that's currentlytypescript@next
).
All types in this library are available for consumption. Types that contain a meta
property can optionally be passed the shape of the expected metadata as a Generic to expand your type checking into the meta fields. The API response returned from the send()
method can also optionally be typed by passing the expected shape of the response as a Generic.
If desired, the fields of each type interface can be narrowed by using TypeScript's built-in Pick
method.
import WPGraphQL, { User as U, Settings } from 'wp-graphql';
const gql = new WPGraphQL(AUTH.root, { auth: AUTH.nonce });
interface UserMeta {
hobby: string;
luckyNumber: number;
}
type User = U<UserMeta>;
interface Response {
user: Pick<User, 'id'|'name'|'meta'|'email'>;
firstThreeUsers: Pick<User, 'name'>;
settings: Pick<Settings, 'title'>;
}
gql.send<Response>(`
query {
user(id: 1, context: edit) {
id
name
meta
email
}
firstThreeUsers: users(per_page: 3, orderby: id) {
name
}
settings {
title
}
}
`).then(data => {
console.log(data.user.description); // Compile Error: The description field is not defined in the Response interface.
const hobby = data.user.meta.hobby; // Type inferred as "string".
});
Custom queries and mutations
// customQuery.js
import {
GraphQLInt,
GraphQLNonNull,
GraphQLString,
} from 'graphql';
const NAMESPACE = 'myRoute/v1';
const getStringData = {
description: 'Get a string from a custom endpoint.',
type: GraphQLString,
args: {
id: {
description: 'The ID of the string I need.',
type: new GraphQLNonNull(GraphQLInt),
},
someOtherArg: {
description: 'Some other argument to pass as a parameter',
type: GraphQLString,
},
}
resolve: (root, { id, ...args }) => (
root.get(`/${NAMESPACE}/path/to/endpoint/${id}`, args)
),
};
export default {
getStringData,
}
// customMutation.js
import {
GraphQLInt,
GraphQLNonNull,
GraphQLString,
} from 'graphql';
const NAMESPACE = 'myRoute/v1';
const postStringData = {
description: 'Post a string to a custom endpoint.',
type: GraphQLString,
args: {
myString: {
description: 'Some other argument to pass as a parameter',
type: new GraphQLNonNull(GraphQLString),
},
}
resolve: (root, args) => (
root.post(`/${NAMESPACE}/path/to/endpoint`, args)
),
};
const deleteStringData = {
description: 'Delete a string from a custom endpoint.',
type: GraphQLString,
args: {
id: {
description: 'The ID of the string to delete.',
type: new GraphQLNonNull(GraphQLInt),
},
}
resolve: (root, { id }) => (
root.delete(`/${NAMESPACE}/path/to/endpoint/${id}`)
),
};
export default {
postStringData,
deleteStringData,
}
// Using the custom queries and mutations.
import WPGraphQL from 'wp-graphql';
import queries from './customQuery';
import mutations from './customMutation';
const gql = new WPGraphQL('http://localhost:8080/wp-json', { queries, mutations });
Generating queries and mutations for custom post types
Let's assume that your site has a custom post type called books
. If you'd like to query and mutate this custom post type similarly to how you would for regular posts, all you need to do is register the custom type with wp-graphql
on instantiation by setting the postTypes
config parameter. This will set up resolvers using the same conventions as regular posts.
import WPGraphQL from 'wp-graphql';
const gql = new WPGraphQL('http://localhost:8080/wp-json', {
postTypes: [
{ name: 'book', namePlural: 'books', restBase: 'books' },
],
});
gql.send(`
mutation {
addBook(title: "My book", content: "My book content") {
title
}
updateBook(id: 1, title: "My new book title") {
id
}
deleteBook(id: 1) {
... on DeletedBook {
deleted
}
}
}
query {
books {
id
}
book(id: 1) {
author
}
}
`);
Using default queries, mutations, and schema in your own server side JS codebase
import * as express from 'express';
import * as graphqlHTTP from 'express-graphql';
import WPGraphQL, { schema } from 'wp-graphql';
const app = express();
const gql = new WPGraphQL('https://my-wordpess-site.com/wp-json');
app.use('/', graphqlHTTP({
schema,
rootValue: gql,
}));
app.listen(3000);
Limitations
The primary limitation of this library is that it only provides a convenient way to fetch and mutate data over top of the existing REST API on the client side. Because of this, the primary benefit of GraphQL, querying and mutating data in a single round trip, is lost.
The "Meta" Limitiation
In order to be declarative, GraphQL requires users to be explicit about the shape of their API responses. This creates a unique problem with Post
, User
, and Comment
meta, since all three objects can have essentially an unlimited number of shapes.
Because there is no reasonable way to know up front what shape the Meta fields are going to be, the meta
must be JSON.stringified
prior to being transacted by GraphQL. This process is done automatically for queries, but must be done manually for mutations.
With that in mind, it's important to remember that GraphQL only sees the meta fields as a String
. When the meta field is returned to you, it will be converted to back to an object by wp-graphql
. Although it is an object when you receive it, you will not be able to query into the meta fields like you would with a typical GraphQL object. In other words, the meta
field is a leaf type.