wp-graphql
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.0 • Public • Published

    wp-graphql Build Status codecov npm

    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));
    Result
    {
        user: {
            id: 1,
            name: 'root',
            email: 'admin@wordpress.com',
        },
        firstThreeUsers: [
            { name: 'Bob' },
            { name: 'Sue' },
            { name: 'Jan' },
        ],
        settings: {
            title: 'My WordPress Site',
        },
    }
    

    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));
    Result
    {
        extract: {
            id: 1,
            name: 'Bob Smith',
        },
        user: {
            id: 1,
            name: 'Bob Smith',
        },
        updateUser: {
            id: 2,
            name: 'Sally Jones',
        },
    }
    

    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 currently typescript@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.

    Install

    npm i wp-graphql

    DownloadsWeekly Downloads

    4

    Version

    1.0.0

    License

    MIT

    Last publish

    Collaborators

    • dsifford