#express #javascript #node
July 20, 2019
by Matteo Bertozzo
Find more about
[development]
GraphQL is the new popular topic in the web development world: everybody’s speaking about it and everybody wants to give it a try. It’s no more the new kid in the block and it is very easy to setup even in small projects, thanks to an extensive documentation.
GraphQL is new (or rather popular, its first public release was actually in 2015), but this reason alone isn’t enough to start using it. The real game changer is the fact that it is the solution to at least two problems with standard REST architectures:
How does GraphQL solve these issues? Well, it introduces a query language that allows to decouple the UI from the services providing data. This way, the UI describes what data it needs, the backend what data it can provide and GraphQL matches the two.
We’re going to build a backend running on Node, that will be able to receive and process GraphQL queries. Data will be stored and then retrieved from a MySQL database.
We’re building our app on top of [react-boilerplate]: even though not so beginner-friendly, it provides us a production-ready codebase and an already configured environment if in the future we’ll want to extend the project with a React frontend app to interact with the API .
To get started, head over your terminal and clone the react-boilerplate repository:
$ git clone --depth=1 https://github.com/react-boilerplate/react-boilerplate.git your-folder
Move inside the folder you’ve just created and run the setup:
$ cd your-folder $ npm run setup
This will install the project dependencies and after a while will ask you if you want to initialize a new repository. Just press Enter to confirm and wait for the process to end. Once finished, run the following command to delete the demo app that comes together the boilerplate:
$ npm run clean
It’s now time to add the dependencies we’ll need in our project:
$ npm i mysql2 sequelize apollo-server-express apollo-boost graphql-tag graphql $ npm i sequelize-cli faker -D
As you can see, there’s a bunch of stuff:
As dev-dependencies we have:
Time to move to the server folder and to create an api folder in it:
$ cd server $ mkdir api $ cd api
We’re going to add in here everything we need to setup our GraphQL api. First of all, let’s connect to the database and create all the tables we need for our project. Fortunately, sequelize helps us a lot, just type in the terminal the following commands:
$ ./../../node_modules/sequelize-cli/lib/sequelize init:models $ ./../../node_modules/sequelize-cli/lib/sequelize init:config
If you’re on a Windows machine, the above commands may not be recognized. If that’s the case, you may want to install sequelize-cli globally with
npm install -g sequelize-cli
and simply runsequelize init:models
andsequelize init:config
We now find in our api folder a bunch of stuff, let’s start from the config folder: in it you can find a config.json file where you can store the data needed for the connection to the database. You may notice that sequelize provides us three environments (development, test, production), as common in enterprise-level app production. In our case we’ll be using the development environment only, but if you plan to release your GraphQL app online, make sure to set (at least) the production environment variables too.
{ "development": { "username": "your_username", "password": "your_password", "database": "your_database", "host": "127.0.0.1", "dialect": "mysql" } }
It’s now time to decide what to store in our database: let’s suppose we want to save some posts in a table and the author who wrote each of them in a second table. In order to define the structure for each table we need to create models. A model is basically a representation of a table structure. Let’s start from the model for the post table: create a post.js file in the server/api/models/ folder and write in it what follows:
module.exports = (sequelize, DataTypes) => { const Post = sequelize.define('post', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, title: DataTypes.STRING, content: { type: DataTypes.TEXT, allowNull: false }, }, { freezeTableName: true, } ); Post.associate = (models) => { Post.belongsTo(models.author); }; return Post; }
Basically, we’re defining a table named post with three columns: id, title and content. We then used sequelize associate function, that allows us to specify a relationship between the post table and the author table. We need to define a complete one-to-many relationship between the author table and the post table: for what concerns the post table, we use the belongsTo sequelize function. The effect is that we’ll get a fourth column, authorId, storing ids acting as foreign keys.
Time now to define the author table. In the server/api/models/ folder create a new file with the name author.js and write in it what follows:
module.exports = (sequelize, DataTypes) => { const Author = sequelize.define('author', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, firstName: DataTypes.STRING, lastName: DataTypes.STRING }, { freezeTableName: true, } ); Author.associate = (models) => { Author.hasMany(models.post); }; return Author; }
The author table definition is similar to the post table code, except here we used the hasMany function to complete the association definition, because each author can be linked to multiple posts.
If you want to know more about the database association we used above, you can look for the keywords one-to-many relationship or [click here] to go straight to the Wikipedia page.
We now have to describe the functionalities available to the client that connects to our GraphQL server. To do so we need to define a schema. GraphQL allows us to define schemas with a syntax called SDL, or Schema Definition Language, that we can use to define types and the relations between them. The schema defines just the shape and the relationships for the data to return, not how or where to retrieve it. For this last purpose, we’ll define a resolver for each schema field.
Let’s begin with the schema.
In server/api/ create a new folder with the name gql, containing two empty files, schema.js and resolvers.js.
$ mkdir gql $ cd gql $ touch schema.js $ touch resolvers.js
Open the schema.js file in your editor and write what follows:
module.exports = ` type Author { id: ID! firstName: String! lastName: String! posts: [Post!]! } type Post { id: ID! title: String content: String! authorId: ID! author: Author! } type Query { posts: [Post!]! post(id: ID!): Post author(id: ID!): Author authors: [Author!]! } type Mutation { createPost(title: String, content:String!, authorId: ID!): Post! updatePost(id: ID!, title: String, content:String!): [Int!]! deletePost(id: ID!): Int! } `;
Here we’re defining two object types, that are basically a group of fields. Each field maps another type, let’s take Author as example:
The exclamation mark means the field is non-nullable and GraphQL will always provide you a value when you query this field.
The last two types are Query and Mutation, and are reserved:
As you can see, in our schema we have the following queries:
The mutations are:
As we said before, we need to create the resolvers in order to actually define how to perform all these operations and with what data. To do so, just open in the editor the resolvers.js file we created before and write in it what follows:
module.exports = { Author: { posts: (obj, args, context, info) => obj.getPosts(), }, Post: { author: (obj, args, context, info) => obj.getAuthor(), }, Query: { posts: (obj, args, { db }, info) => db.post.findAll(), authors: (obj, args, { db }, info) => db.author.findAll(), post: (obj, { id }, { db }, info) => db.post.findByPk(id), author: (obj, { id }, { db }, info) => db.author.findByPk(id) }, Mutation: { createPost: (obj, { title, content, authorId }, { db }, info) => db.post.create({ title: title, content: content, authorId: authorId }), updatePost: (obj, { title, content, id }, { db }, info) => db.post.update({ title: title, content: content }, { where: { id: id } }), deletePost: (obj, {id}, { db }, info) => db.post.destroy({ where: { id: id } }) } };
As you can see, each resolver accepts four arguments:
Sequelize here helps us a lot, since it provides out-of-the-box getter functions for all the fields we defined in our models. Particularly noteworthy are the getPosts() and the getAuthor() functions, generated by sequelize based on the relationship between the tables we specified earlier. Other resolvers use standard sequelize methods, like findAll()
, findByPk()
, create()
, update()
and destroy()
. For further reference you can head over the [official documentation].
We’re almost done: just a last step to bind everything together and we’re ready to go!
The big moment is finally here: head over the server folder and open the index.js file in your editor. In there you’ll find the core configuration for our server, that we need to update in order to include the GraphQL functionalities.
First thing to do is to include some dependencies at the top of the file: add the following content after the const logger = require('./logger');
line.
const ApolloServer = require('apollo-server-express').ApolloServer; const gql = require('apollo-server-express').gql; const faker = require('faker'); const times = require('lodash').times; const random = require('lodash').random; const typeDefs = require('./api/gql/schema'); const resolvers = require('./api/gql/resolvers'); const db = require('./api/models');
Then, immediately before the line const app = express();
add the GraphQL server initialization:
const server = new ApolloServer({ typeDefs: gql(typeDefs), resolvers, context: { db } })
Here we’re passing to the Apollo Server instance the schema definition and the resolvers. As you can see, we are also passing a reference to our database as the context
: this is the exact same context we’ve used earlier in our resolvers.
Now we have to actually connect Apollo Server to Express: after the const app = express();
line, add the following to make the /graphql endpoint available:
server.applyMiddleware({ app });
Almost done: the last touch is to actually sync our database with the models and populate the newly created tables with some fake data. To do so, change the last part of the index file, moving the app.listen(...)
function inside the block that follows:
db.sequelize.sync({ force: true }).then(() => { db.author.bulkCreate( times(10, () => ({ firstName: faker.name.firstName(), lastName: faker.name.lastName() })) ); db.post.bulkCreate( times(10, () => ({ title: faker.lorem.sentence(), content: faker.lorem.paragraph(), authorId: random(1, 10) })) ); app.listen(port, host, async err => { if (err) { return logger.error(err.message); } // Connect to ngrok in dev mode if (ngrok) { let url; try { url = await ngrok.connect(port); } catch (e) { return logger.error(e); } logger.appStarted(port, prettyHost, url); } else { logger.appStarted(port, prettyHost); } }); });
We’re finally to the end of our journey, everything’s ready to be tested!
Fire up your console and move to your project root folder (if you’re not already in it), then type:
$ npm start
Wait for the server to start and then open your browser to http://localhost:3000/graphql. You’re now in the GraphQL Playground, a tool that allows you to create a query or mutation and run it against the server. A sample query can be the following:
query { posts { title content } }
A sample mutation:
mutation { createPost( title:"Test", content:"Sample content for the test post" authorId:4 ) { title content authorId } }
And that’s it! We have a fully working GraphQL API, running on a Node server. Another article about creating a React app providing a simple UI to interact with this API may come in the future… stay tuned!