wordexpress-schema
WordExpress Schema is a GraphQL schema that is modeled off of how WordPress stores data in a MySQL database. It provides modular GraphQL type definitions and GraphQL query resolvers, as well as an easy connection to a WordPress database.
WordExpress Schema exports the following:
-
WordExpress Database: provides a connection to your WordPress database and returns some models and queries using Sequelize. These queries replace MYSQL queries, and return promises.
-
WordExpress Resolvers: resolving functions that work with the
WordExpressDatabase
connectors to resolve GraphQL queries -
WordExpress Definitions: a modular GraphQL schema definition.
Combined, this package can be used with any GraphQL server (like Apollo Server) to provide an easy connection to your WordPress database. An example of using this package with Apollo Server and Webpack is provided below.
If you'd like a solution that already includes a GraphQL server for you, check out the WordExpress Server repository. WordExpress Server uses WordExpress Schema
and provides you with a GraphQL server out of the box.
Installation
npm install --save-dev wordexpress-schema
Usage
WordExpressDatabase
The first part of WordExpress Schema is WordExpressDatabase. This class provides an easy connection to your WordPress database using some connection settings. Typically, you'll want to put the database in its own file in case you want to extend the Models.
Below is the basic implementation:
//db.js
import Config from 'config'
import {WordExpressDatabase} from 'wordexpress-schema'
/*
Example settings object:
public: {
uploads: "http://wordexpress.s3.amazonaws.com/",
amazonS3: true
},
private: {
wp_prefix: "wp_",
database: {
name: "wpexpress_dev",
username: "root",
password: "",
host: "127.0.0.1"
}
}
*/
const publicSettings = Config.get('public')
const privateSettings = Config.get('private')
const Database = new WordExpressDatabase({publicSettings, privateSettings})
const {connectors, models} = Database
export default Database
export {connectors, models}
Connection Settings
In the above example, WordExpressDatabase is passed a settings object that contains some WordPress database settings. Name, username, password, and host are all self-explanatory.
WordExpress will work with Amazon S3; passing in a truthy value for amazonS3 will alter the query for getting Post Thumbnail images. If you are using S3, you just need the include the base path to your S3 bucket (which means you should exclude the wp-content/uploads/ part of the path). If you are hosting images on your own server, include the full path to the uploads folder.
Lastly, you can modify the wordpress database prefix. Some people don't use the default "wp_" prefix for various reasons. If that's you, I got your back.
The Database Class
The Database class above contains the connectionDetails, the actual Sequelize connection, the database queries, and the database models. Really, all you need for GraphQL setup are the queries; however, if you'd like to extend queries with your own, the Database Models are exposed.
The Models
Here are the models and their definitions. As you can see, for the Post model, not every column in the wp_posts table is included. I've included the most relevant columns; however because the Database class exposes the models, you can extend them to your liking.
Post: Conn.define(prefix + 'posts', {
id: { type: Sequelize.INTEGER, primaryKey: true},
post_author: { type: Sequelize.INTEGER },
post_title: { type: Sequelize.STRING },
post_content: { type: Sequelize.STRING },
post_excerpt: { type: Sequelize.STRING },
post_status: { type: Sequelize.STRING },
post_type: { type: Sequelize.STRING },
post_name: { type: Sequelize.STRING},
post_date: { type: Sequelize.STRING},
post_parent: { type: Sequelize.INTEGER},
menu_order: { type: Sequelize.INTEGER}
}),
Postmeta: Conn.define(prefix + 'postmeta', {
meta_id: { type: Sequelize.INTEGER, primaryKey: true, field: 'meta_id' },
post_id: { type: Sequelize.INTEGER },
meta_key: { type: Sequelize.STRING },
meta_value: { type: Sequelize.INTEGER },
}),
User: Conn.define(prefix + 'users', {
id: { type: Sequelize.INTEGER, primaryKey: true },
user_nicename: { type: Sequelize.STRING },
user_email: { type: Sequelize.STRING },
user_registered: { type: Sequelize.STRING },
display_name: { type: Sequelize.STRING }
}),
Terms: Conn.define(prefix + 'terms', {
term_id: { type: Sequelize.INTEGER, primaryKey: true },
name: { type: Sequelize.STRING },
slug: { type: Sequelize.STRING },
term_group: { type: Sequelize.INTEGER },
}),
TermRelationships: Conn.define(prefix + 'term_relationships', {
object_id: { type: Sequelize.INTEGER, primaryKey: true },
term_taxonomy_id: { type: Sequelize.INTEGER },
term_order: { type: Sequelize.INTEGER },
}),
TermTaxonomy: Conn.define(prefix + 'term_taxonomy', {
term_taxonomy_id: { type: Sequelize.INTEGER, primaryKey: true },
term_id: { type: Sequelize.INTEGER },
taxonomy: { type: Sequelize.STRING },
parent: { type: Sequelize.INTEGER },
count: { type: Sequelize.INTEGER },
})
Creating The Schema
WordExpress uses GraphQL Tools's makeExecutableSchema to generate an executable schema. makeExecutableSchema
requires type definitions and resolvers. WordExpress gives you both of those! Here's the basic implementation of the schema:
import {makeExecutableSchema} from 'graphql-tools'
import {WordExpressDefinitions, WordExpressResolvers} from 'wordexpress-schema'
import {connectors} from './db'
import Config from 'config'
const RootResolvers = WordExpressResolvers(connectors, Config.get('public'))
const schema = makeExecutableSchema({
typeDefs: [WordExpressDefinitions]
resolvers: RootResolvers
})
export default schema
WordExpressResolvers
requires some database connectors that the WordExpressDatabase
provides. These connectors provide the root sequelize queries. WordExpressResolvers
is simply a (resolving map)[https://www.apollographql.com/docs/graphql-tools/resolvers.html#Resolver-map] that tell the GraphQl queries how to fetch the data from the WordPress database.
WordExpressDefinitions
is a modular GraphQL schema written in the GraphQL Schema language.
Using Definitions and Resolvers with Apollo Server
This example is from the WordExpress Server. After creating an executable schema, all we need to do is provide the schema to apollo-server-express.
import {ApolloServer} from 'apollo-server'
import {WordExpressDefinitions, WordExpressResolvers} from 'wordexpress-schema'
import {connectors} from './db'
import Config from 'config'
const PORT = 4000
const resolvers = WordExpressResolvers(connectors, Config.get('public'))
const server = new ApolloServer({
typeDefs: [...WordExpressDefinitions],
resolvers
})
server.listen({port: PORT}, () => {
console.log(`wordexpress server is now running on port ${PORT}`)
})
Types
Post
import Postmeta from './Postmeta'
import User from './User'
const Post = `
type Post {
id: Int
post_title: String
post_content: String
post_excerpt: String
post_status: String
post_type: String
post_name: String
post_parent: Int
post_date: String
menu_order: Int
post_author: Int
layout: Postmeta
thumbnail: String
post_meta(keys: [MetaType], after: String, first: Int, before: String, last: Int): [Postmeta]
author: User
}
`
export default () => [Post, Postmeta, User]
Postmeta
import Post from './post'
const Postmeta = `
type Postmeta {
id: Int
meta_id: Int
post_id: Int
meta_key: String
meta_value: String
connecting_post: Post
}
`
export default () => [Postmeta, Post]
MetaType
const MetaType = `
enum MetaType {
_thumbnail_id
_wp_attached_file
react_layout
amazonS3_info
order
}
`
export default MetaType
Category
import Post from './post'
const Category = `
type Category {
term_id: Int!
name: String
slug: String
posts(post_type: String = "post", limit: Int, skip: Int): [Post]
}
`
export default () => [Category, Post]
Menu
import MenuItem from './menuItem'
const Menu = `
type Menu {
id: ID!
name: String
items: [MenuItem]
}
`
export default () => [Menu, MenuItem]
MenuItem
import Post from './post'
const MenuItem = `
type MenuItem {
id: ID!
post_title: String
linkedId: Int
object_type: String
order: Int
navitem: Post
children: [MenuItem]
}
`
export default () => [MenuItem, Post]
Setting
const Setting = `
type Setting {
uploads: String
amazonS3: Boolean
}
`
export default Setting
Inputs
OrderInput
const OrderInput = `
input OrderInput {
orderBy: String,
direction: String
}
`
export default OrderInput
Queries
WordExpress provides some out-of-the-box queries to do some basic stuff like getting posts, getting posts by category, getting a post by post_type, etc.
Posts
posts(post_type: String = "post", limit: Int, skip: Int, order: OrderInput): [Post]
You can query posts by post_type
. If you don't provide a post_type, it will default to 'post'. You can also limit the results and skip results (allowing for pagination). Also, you can provide a custom sorting object to sort the results. Here's an example of sorting:
Layouts for Pages and Posts
Posts and pages can be assigned a Component to use as a layout. You can use the WordExpress Companion Plugin for WordPress which will allow you to add the custom field to any page or post. Or you can add your own custom field. It needs to be called page_layout_component
. Here's an example of the querying with a layout:
Post
post(name: String, id: Int): Post
Returns a Post by either its ID or its name.
Menu
menus(name: String!): Menu
Returns a menu and the menu items associated with it, as well as children items. Uses the slug of the menu that is registered with WordPress.
Category
category(term_id: Int!): Category
Gets a category by its ID. Also capable of returning all posts with the category id. Here's an example:
Postmeta
postmeta(post_id: Int!, keys:[MetaType]): [Postmeta]
Gets the postmeta of a post by the post id.
If keys
are passed it, it will only return those keys which are part of the MetaType
. Example: