Hi everyone! Welcome back to part 2 of The Ultimate Beginner’s Guide to GraphQL tutorial series. Before we start, if you haven’t seen part 1, you might want to check it out here. This tutorial will build off the concepts learned in part 1.
With all that said, I wanted to go more in-depth about something I only briefly mentioned in part 1: the GraphQL infrastructure. This is an important part of learning how GraphQL works, and what makes it so awesome.
The GraphQL Infrastructure
To get a better understanding of the advantages and disadvantages of GraphQL, I created this handy little infographic:
Alright! Hopefully that gives you a little insight into the inner workings of GraphQL, and helps you understand some of the operations at a deeper level.
Organizing the Code
Unfortunately, before we get to the fun stuff in this tutorial, we do have to work through the boring stuff. This means working on organizing our code.
If you don’t remember, we used our standard server code and made some significant changes to the index.js
file in part 1 of the tutorial. I’d recommend reading that part first so you’ll be up-to-date on what we’re doing. After completing part 1, the code in our index.js file should look like this:
const port = process.env.port || 3000;
const express = require('express');
const ejs = require('ejs');
const layouts = require('express-ejs-layouts');
const app = express();
app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use(layouts);
const homeController = require('./controllers/homeController.js');
app.get('/', homeController.renderIndex);
const { gql } = require('apollo-server-express');
const schema = gql`
type Query {
getUsers: User
}
type User {
id: Int!
username: String!
email: String!
password: String!
}
`;
let users = [
{
id:1,
username:'The Javascript Ninja',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
{
id:2,
username:'The Javascript Ninjas Best Friend',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
]
const resolvers = {
Query: {
getUsers: _ => 'Hello World'
}
}
const { ApolloServer } = require('apollo-server-express');
const serve = new ApolloServer({
typeDefs: schema,
resolvers: resolvers,
});
serve.applyMiddleware({ app });
const server = app.listen(port, () => {
console.log(`🚀 Server listening on port ${port}`);
});
For a full explanation, read part 1. Otherwise, note that the code above will create a GraphQL server alongside our express server, and define a simple query to execute. Don’t leave yet – read on. We’ll be expanding this database model later in the tutorial.
Since we’ll be greatly expanding the code in our index.js
file, it might do us some good to split it up between other files. This will reduce the amount of clutter in our main file, and keep our project file organized.
To organize our code, we can move our schema
and resolvers
objects to separate files. This may seem a little overkill at first, but after we expand them, it will be totally necessary.
To do this, first, create a models
folder at the root level of your project. We will still want everything in our project to correspond with MVC formatting.
Then, in our new models
folder, we’ll create the files schema.js
and resolvers.js
.
Next, in schema.js
, cut and paste the schema
object from index.js:
const { gql } = require('apollo-server-express');
const schema = gql`
type Query {
getUsers: User
}
type User {
id: Int!
username: String!
email: String!
password: String!
}
`;
Then, in resolvers.js
, cut and paste the resolvers
object and users
array from index.js
:
let users = [
{
id:1,
username:'The Javascript Ninja',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
{
id:2,
username:'The Javascript Ninjas Best Friend',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
];
const resolvers = {
Query: {
getUsers: _ => users;
}
}
Then, modify index.js
so it looks like this:
const port = process.env.port || 3000;
const express = require('express');
const ejs = require('ejs');
const layouts = require('express-ejs-layouts');
const app = express();
app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use(layouts);
const homeController = require('./controllers/homeController.js');
app.get('/', homeController.renderIndex);
const schema = require('./models/schema.js');
const resolvers = require('./models/resolvers.js');
const { ApolloServer } = require('apollo-server-express');
const serve = new ApolloServer({
typeDefs: schema,
resolvers: resolvers,
});
serve.applyMiddleware({ app });
const server = app.listen(port, () => {
console.log(`🚀 Server listening on port ${port}`);
});
Awesome! Now our code is all nice and organized. All that we’ve done above is just sort our resolvers
and schema
objects into modules so they don’t all clutter up the same file.
Writing More Advanced Queries With Parameters
Alright, now it’s time to work on the meat of GraphQL: querying. Querying is arguably the biggest and most important part of GraphQL (partly because the QL stands for Query Language). But, with all that said, it’s time to focus on writing more advanced query functions. The queries we wrote in part 1 were great, but they couldn’t do much and left a lot to be desired.
In a realistic situation, your GraphQL query is probably going to return a lot of data. There are multiple reasons why this could be bad:
• It’s hard to comprehend
• It will drastically slow down the site
• It’s impossible to filter through or perform operations on
As you can see, none of these options are good in the slightest. That’s why it’s important to write better queries by filtering through data to return only what we need, not the entire database. We can do this by adding query parameters.
Adding In Some Parameters
To add some parameters to our query, navigate to your schema.js
file.
Then, let’s add some parameters to the getUsers
query in the Query
type.
const { gql } = require('apollo-server-express');
const schema = gql`
type Query {
getUsers(id:Int, username:String, email:String, password:String): User
}
type User {
id: Int!
username: String!
email: String!
password: String!
}
`;
As you can see, we added all the arguments we wanted available to the getUsers
query. The reason I’ve added these arguments is so I’ll be able to filter through different users by these specific fields. There are no exclamation points after the object types in the parameters because I want all parameters to be optional.
However, before we can successfully execute our query with these parameters, we need to make some edits to our resolvers.
Go to resolvers.js
. Let’s update our getUsers
resolver. Right now, it looks like this:
let users = [
{
id:1,
username:'The Javascript Ninja',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
{
id:2,
username:'The Javascript Ninjas Best Friend',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
];
const resolvers = {
Query: {
getUsers: _ => 'Hello World'
}
}
Pretty lame, huh? Right now, this lame resolver can only return our pre-set array of objects. And even at that, we can’t even filter the results in our query.
Well, it’s time for things to change. Update resolvers.js
so it looks like the following:
let users = [
{
id:1,
username:'The Javascript Ninja',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
{
id:2,
username:'The Javascript Ninjas Best Friend',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
];
const resolvers = {
Query: {
getUsers: (parent, args) => {
if (args.id) {
return users.filter(user => user.id === args.id);
} else if (args.username) {
return users.filter(user => user.username === args.username);
} else if (args.email) {
return users.filter(user => user.email === args.email);
} else if (args.password) {
return users.filter(user => user.password === args.password);
} else {
return users;
}
}
}
}
Wow – Big improvement. However, there’s a lot going on; let me explain it for you:
1. First, the getUsers
method takes in two parameters: parent
, and args
. It is important that args
be the second parameter, otherwise you will get an error.
2. Second, we make a long if
statement. First, we check if the arguments id
, username
, email
, or password
were provided to filter through the data. If no parameters were provided, we return all the data in the users array.
3. If parameters were provided with the query, we filter through the data in the users
array with the array.filter()
method. Then, we return matching data – if there is any.
Now, let’s test our new-and-improved query. Run your server and navigate to localhost:3000/graphql
.
Then, enter the following query into the box on the left:
query {
getUsers(id:1) {
id
username
email
password
}
}
This should retrieve all data for the user with an id equal to 1.
GraphQL Mutations
The next big part of GraphQL is modifying data in the database. This includes adding and deleting users, in our scenario. Fortunately, GraphQL provides an easy way for us to do this: mutations.
In a brief summary, mutations are just like GraphQL queries, except they modify data. In order to make a mutation, we can define a mutation type just like we did a Query
type in our schema.
Modify your schema in schema.js
to look like the following:
const { gql } = require('apollo-server-express');
const schema = gql`
type Query {
getUsers(id:Int, username:String, email:String, password:String): User
}
type Mutation {
createUser(username:String, email:String, password:String): User
}
type User {
id: Int!
username: String!
email: String!
password: String!
}
`;
As you can see, mutations don’t look very different from queries. Of course, you can always get more advanced; these mutations are on a very basic level.
In the Mutation
type above, we define a createUser
mutation. This mutation takes in 3 parameters: username
, email
, and password
. The reason we’re not going to provide the id
property is because we want the id
to be defined by the computer, whether randomly or in order, not manually.
In order to put our mutation into effect, we’ll need to make some edits to our resolvers. Look at the new resolvers below:
let users = [
{
id:1,
username:'The Javascript Ninja',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
{
id:2,
username:'The Javascript Ninjas Best Friend',
email:'contact@thejavascriptninja.com',
password:'its-a-secret'
},
];
const resolvers = {
Query: {
getUsers: (parent, args) => {
if (args.id) {
return users.filter(user => user.id === args.id);
} else if (args.username) {
return users.filter(user => user.username === args.username);
} else if (args.email) {
return users.filter(user => user.email === args.email);
} else if (args.password) {
return users.filter(user => user.password === args.password);
} else {
return users;
}
}
},
Mutation: {
createUser: (parent, args) => {
let newUser = {
id: users.length + 1,
username: args.username,
email: args.email,
password: args.password
};
users.push(newUser);
return newUser;
}
}
}
As you can see, in our resolver, we have a new property after the Query
property! This is the Mutation
property. In the Mutation
property, we have the createUser
method. This is so far our first mutation. In the createUser
method we do 3 things:
1. Create a newUser
object. In this object, we set the id
, username
, email
, and password
of our new user.
Add the
newUser
object to the database. In reality, we would add thenewUser
to the database. However, since we’re just using a dummy database model, we just usearray.push()
to add thenewUser
to ourusers
array.Return the
newUser
. This is pretty straightforward. We just return thenewUser
object as a result of the mutation.
Running our First Mutation
Now that we’ve got our mutation all done, it’s time to run. (Yes, that rhymes 😀). In order to run our mutation, start your server and navigate to localhost:3000/graphql
.
To make sure everything is working right, let’s first run our query. Enter this query into the box on the left:
query {
getUsers {
id
username
email
password
}
}
You should see the following result:
Now that we’ve ensured everything works and we aren’t getting any errors, it’s time to test our mutation. Let’s enter the mutation we wrote earlier:
mutation myMutation {
createUser(username:"Subscriber to TJN", email:"contact@thejavascriptninja.com", password:"secret") {
id
username
email
password
}
}
As you can see, we call createUser
, and give it the specified params. It should return the following result:
{
"data": {
"createUser": {
"id": 3,
"username": "Subscriber to TJN",
"email": "contact@thejavascriptninja.com",
"password": "secret"
}
}
}
Then, if we run our query again, we can see our mutation has taken effect.
Run:
query {
getUsers {
id
username
email
password
}
}
You should see the following:
{
"data": {
"getUsers": [
{
"id": 1,
"username": "The Javascript Ninja",
"email": "contact@thejavascriptninja.com",
"password": "its-a-secret"
},
{
"id": 2,
"username": "The Javascript Ninjas Best Friend",
"email": "contact@thejavascriptninja.com",
"password": "its-a-secret"
},
{
"id": 3,
"username": "Subscriber to TJN",
"email": "contact@thejavascriptninja.com",
"password": "secret"
}
]
}
}
Hooray! Our mutation worked!
To Conclude…
A’ight folks!
Today we’ve talked about organizing our code, writing more advanced queries (using parameters and variables), and GraphQL mutations.
Pretty awesome stuff.
I am going to end the tutorial here so it doesn’t get too long, but make sure to subscribe so you don’t miss out on any great content!
Stay tuned and talk soon!
Top comments (0)