Creating a GraphQL server with Javascript
Hello! Welcome to the second article in my series on GraphQL. If you haven’t read the first part, here’s the link. In the first part of this article, I gave a high level intro to GraphQL, its features and query syntax.
In this article, I will show you how to spin up a simple GraphQL server for a document management application. The server will allow you to do the following:
- Create a user.
- Create a document for a user.
- Retrieve users and the documents associated with them.
When we’re through, you should be able to do this from scratch in about 10 minutes 😱. I will also show you how to create queries, mutations and resolvers for the server. Ready? Let’s get started! 😎
I’ve created a repo for this tutorial. You can go ahead and clone the repo and install dependencies.
git clone https://github.com/andela-rbabalola/dms-graphql.git
cd dms-graphql
git checkout start
npm install
First, we will create a server with Express, use express-graphql
as the route handler for the /graphql
HTTP endpoint and connect to a mongoDB database. Let’s see how to do that. Create a file called server.js
in the root directory and paste the following code
I have already defined the User and Document collections so we can focus on setting up the GraphQL server.
On lines 16–19 we are mounting express-graphql
as the route handler for the /graphql
HTTP endpoint and we set schema
to dmsSchema
which is a GraphQL schema we will create in a moment. We are also setting graphiql
to true
; this allows us make GraphQL queries via a graphical interactive in-browser IDE called GraphiQL.
Now let’s create the GraphQL schema for this tutorial.
This is a lot! Let me explain what we’ve just done. First we imported some GraphQL data types from the graphql
package. Next we defined the root query for the GraphQL API. The root query is the entry point we will use for making all our queries. The root query has 2 fields — User and Document.
The User
shows us all the information we can access for any user. Its type
is UserType
which is a custom GraphQL type we will create in a moment. Note that it is wrapped in GraphQLList
which means we’re expecting an array (or list) of users from the server. We can define UserType
inside the Root Query, but for clarity it’s always good to define custom GraphQL types in a different file, then import them. The User
field also has an optional argument called id
of type GraphQLString
. We will use this id when we want to get details for a particular user.
Something you may have noticed by now is that GraphQL is strongly typed, this means that any field or variable you add to the schema must have a type e.g. string, integer, boolean or a custom GraphQL type.
Now we can see all the fields available to query for User
; note that each field has a description
that tells us more about that field. One of the cool things about GraphQL is that it allows to document our API (with the description
field) as we code which is very convenient. 😎
Also note that we have wrapped some fields (firstName, lastName, email and isAdmin) in GraphQLNonNull
, this means that these fields cannot be null in the response if we include them in our query.
Now let’s import the UserType
we created in the GraphQL schema. At the top of index.js
add the following
// schema/index.jsimport UserType from './types/user';
Resolvers
The next thing we need to explain is the resolve
in the User
field. It is commonly referred to as a resolver
. The resolver is a function that gets called when you make queries. For example, when we query for the emails of all users, we are actually executing a function (the resolver) that retrieves users’ emails from the database.
The resolver takes 3 arguments but in this tutorial I will only explain the first two. They are:
obj
— This object refers to the parent object we are representing. For theUser
field it will be aUser
object; for theDocument
field it will be aDocument
object.args
— This object contains the arguments to the resolver function.
Let’s add functionality to the resolver on the User
field. The resolver is going to get data for all users if no ID is passed to it as an argument or it will get data for a user by the ID passed to it. Note that this { id }
means we are using Javascript destructuring to destructure the args
object so we can directly access the id
parameter for a specific user if an ID is passed.
Create a folder called resolvers
in the root directory and in it create a file user-resolver.js
and paste the following code.
The user resolver has 2 functions one to get a particular user by the id supplied and another to get all users. These functions return a Promise
because loading data from a database is asynchronous. When the Promise
resolves we get all the users (or a user) from the database.
Next import the file in schema/index.js
// schema/index.jsimport UserResolver from '../resolvers/user-resolver';
Let’s update the resolve
function of the User
field with the following:
// schema/index.js
import UserResolver from ‘../resolvers/user-resolver’;
const RootQueryType = new GraphQLObjectType({
...,
resolve: (obj, { id }) => {
let userData;
if (id) {
// get user by id
userData = UserResolver.getById(id);
} else {
// get all users
userData = UserResolver.get();
}
...
});
And we are done with the User
field! Next we need to do the same thing for the Document
field. In the types
folder create a file called document.js
and paste the following code:
DocumentType
depends on another GraphQL custom type called AccessType
. In the same types
folder, create a file access.js
and paste the code below:
Next, import it in schema/types/document.js
// schema/types/document.jsimport AccessType from './access';
Now that you’ve created DocumentType
, import it in schema/index.js
// schema/index.jsimport DocumentType from './types/document';
Finally, we need to create resolvers for the DocumentType
. In the resolvers
folder, create a file document-resolver.js
and paste the following code
Right now, the document resolver contains pretty much the same thing as the user resolver; later on in this tutorial, we will add more functions to it.
Next, import the file and update the resolve
function of the Document
field with the following:
// schema/index.js
import DocumentResolver from '../resolvers/document-resolver';
const RootQueryType = new GraphQLObjectType({
...,
resolve: (obj, { id }) => {
let docData;
if (id) {
// get document by id
docData = DocumentResolver.getById(id);
} else {
// get all documents
docData = DocumentResolver.get();
}
...
});
And we are done! Let us test what we have done so far
npm start
If all went well, you should see the following
GraphQL Server listening on PORT 3000
dms db opened
Navigate to this url in your browser localhost:3000/graphql and you should see the graphiql
interface. It looks like this
Let write a test query that will get the first names of all users in the database. This query will return an empty array because we currently don’t have any users in the database. We are doing this just to make sure everything works fine.
query {
User {
firstName
}
}
As expected this is the result
Writing mutations
We need to figure out a way to allow the GraphQL server we just created to support not only read operations but also write operations. This is where mutations come in 🎉. Let’s see how to write a mutation for our GraphQL server.
First, we need to define a RootMutationType
just like we had a RootQueryType
for queries. The RootMutationType
will have 2 fields — AddUser
to create a user and AddDocument
to create a new document. Let’s go ahead and do that. Paste this code in index.js
, below RootQueryType
.
// schema/index.jsconst RootMutationType = new GraphQLObjectType({
name: 'RootMutationType',
fields: () => ({
AddUser: AddUserMutation,
AddDocument: AddDocumentMutation,
}),
});
We also need to add the RootMutationType
to the GraphQL schema
// schema/index.jsconst dmsSchema = new GraphQLSchema({
query: RootQueryType,
mutation: RootMutationType,
});
Next, we need to define AddUserMutation
. In the schema
folder, create a folder called mutations
and in it a file called add-user.js
, paste the code below:
Let me explain what we’ve just done. First, we imported some GraphQL data-types. Next we created UserInputType
which is an instance of GraphQLInputObjectType
. The fields in UserInputType
are what we are going to supply when we are making this mutation. Again, some fields are wrapped in GraphQLNonNull
meaning that when we are making this mutation, these fields must be supplied for the new user we want to create.
Another thing I want you to notice is the object we are exporting in this file. The first field, type
, refers to what type the server will return when the mutation is done. In GraphQL, mutations always return something. In this case we want the AddUserMutation
to return a UserType
when it is done. Note that we can return anything here — a simple User created!
message would suffice. I’m choosing to return UserType
because we can query the server for the new user we just created to be sure everything worked fine.
args
is an object containing the arguments that the mutation accepts. Note that it can’t be null. When we want to add a new user we are going to pass the new user object as an argument to AddUserMutation
.
Finally, we have resolve
, the function that executes when we perform this mutation. We need to define it in user-resolver
and then import it. Let’s do that.
// resolvers/user-resolver.js
import User from '../models/user.model';const UserResolver = {
getById: ...,
get: ...,
create: (user) => {
const newUser = new User(user);
return new Promise((resolve, reject) => {
newUser.save((error, createdUser) => {
if (error) reject(error);
else resolve(createdUser);
});
});
},
}
The function basically creates a new entry in the User
collection and returns a Promise that resolves to the newly created user.
Great! We now have the resolver for AddUserMutation
defined. Let’s import it in add-user.js
. Paste the following in add-user.js
// schema/mutations/add-user.jsimport UserResolver from '../../resolvers/user-resolver';
Update the resolve
with this:
// schema/mutations/add-user.jsexport default {
type: ...,
args: ...,
resolve(obj, { input }) {
// create user
return UserResolver.create(input);
},
};
Finally, import AddUserMutation
in schema/index.js
and we are done with AddUserMutation
!
// schema/index.jsimport AddUserMutation from './mutations/add-user';
Next need to do the same thing for AddDocumentMutation
. First, we define it in a file add-document.js
in schema/mutations
.
Next, we write the resolver that will create a new document in document-resolver.js
// resolvers/document-resolver.js
import Document from '../models/document.model';const DocumentResolver = {
getById: ...,
get: ...,
create: (document) => {
const newDocument = new Document(document);
return new Promise((resolve, reject) => {
newDocument.save((error, createdDocument) => {
if (error) reject(error);
else resolve(createdDocument);
});
});
},
};
Import it in add-document.js
// schema/mutations/add-document.jsimport DocumentResolver from '../../resolvers/document-resolver';
Update the resolve
with this:
// schema/mutations/add-document.jsexport default {
type: ...,
args: ...,
resolve(obj, { input }) {
// create document
return DocumentResolver.create(input);
},
};
Finally, import AddDocumentMutation
in schema/index.js
and we are done!
// schema/index.jsimport AddDocumentMutation from './mutations/add-document';
Now we can test the mutation 🕺
npm start
Navigate to localhost:3000/graphql in your browser and run the following mutation
mutation {
AddUser(input: {
firstName: "John",
lastName: "Doe",
password: "johndoe123",
email: "john@email.com",
}) {
firstName
lastName
}
}
We are writing a mutation to create a new user and querying the server for the firstName
and lastName
of the new user when the mutation is done. Here is the result
Remember the first query we wrote in this article? Let’s run it now
query {
User {
firstName
}
}
Here is the result
We also need to test AddDocumentMutation
. Run this mutation to create a new document.
mutation {
AddDocument(input: {
title: "some title",
text: "some text",
owner: "599566738248d99115e6d8bb"
}){
title
}
}
If you are wondering how I got the string for owner
you can get it by adding id
to the first query. The string should be different for you because MongoDB generates a random id for every document in a collection.
Here is the result
Voila! Our GraphQL server can support queries and mutations! Let’s celebrate!
One last resolver
For us to see the awesomeness of GraphQL, we need to add one more resolver to the UserType
. This resolver will get the documents owned by a user. Let’s do this!
First, we need to define the resolver function in document-resolver.js
. Paste the code below in the file
// resolvers/document-resolver.js
import Document from '../models/document.model';const DocumentResolver = {
getById: ...,
get: ...,
create: ...,
getDocumentsForUser: userId => new Promise((resolve, reject) => {
Document.find({ owner: userId }, (error, foundDocuments) => {
if (error) reject(error);
else resolve(foundDocuments);
});
}),
};
Next, import it in schema/types/user.js
// schema/types/user.jsimport DocumentResolver from '../../resolvers/document-resolver';
We also need to import DocumentType
in user.js
because we are now returning documents in UserType
.
// schema/types/user.jsimport DocumentType from './document';
Finally, add a new field called documents
to UserType
// schema/types/user.jsexport default new GraphQLObjectType({
name: 'UserType'
fields: () => ({
...,
documents: {
type: new GraphQLList(DocumentType),
description: 'Documents owned by user',
resolve: obj => DocumentResolver.getDocumentsForUser(obj._id),
},
}),
});
Note that we are using the obj
argument in the resolve
function. This is because obj
is a reference to the parent object — User
which will contain the user’s id. Also we wrapped DocumentType
in GraphQLList
because we are expecting an array (or list) of documents for each user.
Let’s test! Run the query below in the graphiql interface
query {
User {
firstName
lastName
documents {
title
text
}
}
}
We are querying the server for all users and the title and text of the documents associated with them. Here is the result of the query:
If we want to get details for just one user, pass the id as an argument to the query like this:
query {
User(id: "599566738248d99115e6d8bb") {
firstName
lastName
documents {
title
text
}
}
}
Here is the result
Awesome! I think this deserves another celebration
As an exercise for you to try out, can you add a field to UserType
that will return the count of the documents owned by a user? I know you can do it 😎 You can check my solution on the article-2
branch of this project’s GitHub repo.
Summary
We’ve done a lot in this article! 💪 Thanks for sticking with me to the end and I hope you’ve learnt something from this article.
If you have questions, comments or feedback feel free to write them in the comments section. Also if you encountered any errors while setting up let me know in the comments section. You can also hit me up with ideas for the next article in this series.
Thank you very much for reading this, click the 👏 below if you enjoyed reading it so others can read it too.
Gracias!