Simple GraphQL setup with MySQL and React

Nov. 11, 2018, 6:29 a.m.


GraphQL's docs to me were pretty disappointing when trying to get started. They aren't bad really. There is a lot of information, and it's hard to complain when usually I'm running my hands through my hair reading source code. But, for all of their effort, there was a lack of meaningful examples and it took longer to get started than I would have liked for such a large project. My need was to write a very simple API that would sit within an express server and I'd make request from my React application.

With GraphQL, my number one take away is don't over complicate it.

The second, holy smokes this is cool!

Okay - so how did I originally get my sour taste leading to my first take away? When I had started I could not shake the thought that nodes in my graph had to be linked to my tables in some way. If I had a table for a person, e.g. person_profile, then that would be a node "Profile" as well. While you might want to do that, GraphQL never claims any such notion. In GraphQL, you define nodes, and each node can return stuff, even other nodes.

What each node returns (defined as its fields) is completely up to you. Each field (you'll see soon) can resolve to "Hello World" or as in my case, run a Sequelize query against MySQL and return database results. It's important to note that GraphQL doesn't care what you do, but only that you must tell it how it will get its data for any node. This data you tell GraphQL to get in the resolve function on your field can return a promise, or a value.

Before I say anything, the small graph example I've put together below is based on this Sequelize connection and model:

const sequelize = new Sequelize(
  "my_db",
  "username",
  "password",
  {
    host: "localhost",
    dialect: "mysql",
    operatorsAliases: false,

    pool: {
      max: 5,
      min: 0,
      acquire: 30000,
      idle: 10000
    }
  }
);

const DBWebsite = sequelize.define("websites", {
  id: {
    type: Sequelize.INTEGER,
    primaryKey: true
  },
  name: {
    type: Sequelize.STRING
  }
});

// For brevity, pretend I defined a "Category" table as well in Sequelize :).

I did some work on a business website that had a table "website" which linked events to different websites they were offered on. Starting off, I decided I wanted this to be the access point for my queries. So I wanted an object "Website" that had an "id" and "name", and have a reference to a "category" node, which is actually a separate table. In GraphQL you can actually directly define the object types you want to use using GraphQLObjectType. My simple Website object looks like this.

var Website = new GraphQLObjectType({
  name: "Website",
  description: "website node",
  fields: () => ({
    id: {
      type: GraphQLInt,
      resolve: website => website.id
    },
    name: {
      type: GraphQLString,
      resolve: website => website.name
    }
    categories: {
      type: GraphQLList(Category),
      resolve: website =>  // Note! I'm just searching a `category` table which had a foreign key
        db.Category.findAll({
          where: {
            WebsiteId: website.id,
          },
        }),
    },
  })
});

I'd like to make a note that GraphQL would read the code below exactly the same way as a convenience. I prefer to leave the resolver as I wrote above in the code to keep it more explicit, but that's my cup of tea.

var Website = new GraphQLObjectType({
  name: "Website",
  description: "website diving database into separate business units",
  fields: () => ({
    id: { type: GraphQLInt },  // look mom! graphql implicitly interprets the resolver
    name: { type: GraphQLString }  // look mom! graphql implicitly interprets the resolver
  })
});

GraphQL needs a root object that will be the entry point to your API. Since I wanted to have websites be at the root, I went ahead and started with websites being one of my root queries. Note as they're just fields I can add as many as I want. Here's an example of my root node.

const RootQuery = new GraphQLObjectType({
  name: "RootQueryType",
  description: "This is a root query",
  fields: () => ({
    websites: {
      type: new GraphQLList(Website),
      resolve: (root, args) => {
        return DBWebsite.findAll();
      }
    }
  })
});

Take a look at the resolver there. Note that I'm just running a query against my database. This is not linked to MySQL in any way. In fact, I can have fields on a node pull from multiple tables. It's just javascript returning an object.

In this case, I said I wanted to return a list of objects (type is GraphQLList(Website)), so when I query

{
  websites {
    id,
    name,
  }
}

the Sequelize query will return data of the shape [{...}, {...}, ...] which GraphQL knows to read as [Website, Website, ...], and so I can write a resolver as simple as resolve: website => website.name for my Website node.

Read that 10 times ☝️.

Of course, in order to make that query, you have to first have a server. I chose express-graphql because I was layering this on top of an exister express server. You have to define your schema of your graph, which in this case is just my RootQuery. Then use that schema in your express app! This ended up looking like this:

var schema = new GraphQLSchema({
  query: RootQuery
});

expressApp.use(
  '/graphql',
  cors(),  // avoid cors erros from frontend, (really I should just use a proxy for React client to avoid CORS)
  graphqlHTTP({
    schema,
    pretty: true,
    graphiql: true,
  }),
);

Part 2 - expanding example

Now, just to grind things into stone, I'll make a new example. Here is a node where I have a field to get a users IP address, say "Hello World!", and print out a name.

For the query...

{
  example {
    ip,
    foo,
    name
  }
}

against the query example on my RootQuery like this

const WebsiteQuery = new GraphQLObjectType({
  name: "RootQueryType",
  description: "This is a root query",
  fields: () => ({
    websites: {
      type: new GraphQLList(Website),
      resolve: (root, args) => {
        return DBWebsite.findAll();
      }
    },
    example: {
      type: Profile,
      resolve: (root, args) => {
        return { name: "Nick" };  // I'm just resolving an object with the attribute "name" since I use it in my "Profile" type
      }
    }
  })
});

where the type Profile is defined as...

var Profile = new GraphQLObjectType({
  name: "Example",
  description: "I can do what I want because I am Javascript",
  fields: () => ({
    ip: {
      type: GraphQLString,
      resolve: getIP  // getIP is just a promise that returns IP address
    },
    foo: {
      type: GraphQLString,
      resolve: () => "Hello World"
    },
    name: {
      type: GraphQLString,
      resolve: person => person.name
    }
  })
});

const getIP = () =>
  new Promise((resolve, reject) => {
    https.get("https://httpbin.org/ip", resp => {
      let body = "";
      resp.on("data", d => {
        body += d;
      });
      resp.on("end", () => {
        const parsed = JSON.parse(body);
        resolve(parsed.origin);
      });
    });
  });

you'll receive a response like this!

{
  "data": {
    "example": {
      "ip": "13.130.137.19",  // I changed some numbers here :)
      "foo": "Hello World",
      "name": "Nick"
    }
  }
}

A few very cool things to note 😲:

  1. The "name" field resolved as expected, since the query passed an object with an attribute name equal to "Nick".
  2. The "foo" field resolved a direct value in our API
  3. The resolver for "ip" resolved a promise and gave me its value in the response (yes I know it is the IP from where I made the request)

I want to make a special note that React is completely separate from this as the client. I chose to use Apollo Client due to its many built in features (e.g. caching), but everything all the way down to the humble fetch would have sufficed. At the end of the day, I have just created a RESTful endpoint.

Comment Enter a new comment: