In this post we are going to learn how to set up a GraphQL server in NodeJs. If this is the first time you are learning about GraphQL, I suggest taking a look at graphql.org for an overview.
TL,DR;
Just want to see the code? Get the complete source code on github.
Dependencies
To implement our server in NodeJS, we are going to need the following components
- KoaJS – A http server for NodeJS.
- Apollo-server – An open-source GraphQL server implementation for node
Let’s get started.
Step 1: initialize the project and install dependencies
Initialize project
npm init -y
Install dependencies
npm install koa apollo-server-koa graphql casual
Here we install the packages
- koa: http server
- apollo-server-koa: open-source GraphQL server
- graphql: JavaScript reference implementation for GraphQL
- casual: a fake data generator
Install dev dependencies
npm install -D typescript @types/koa @types/node @types/graphql
Next we install typescript and type definitions for our installed packages.
Initialize typescript config file
npx tsc --init
This will create a file tsconfig.json
.
In tsconfig.json
add this field to redirect output structure to the dist.
...
"outDir": "./dist",
...
Step 2: Create GraphQL schema and resolvers
Next, we will create some files that will be needed by our GraphQL server. First up,
schema.ts.
A schema is basically type definitions for our data. It helps GraphQL understand what type of data to expect and respond with when handling a request.
We will define a simple user schema
import { gql } from 'apollo-server-koa';
const userSchema = gql`
type Query {
user(id: Int!): User
allUsers: [User]
}
type User {
age: Int!
email: String!
hobbies: [String!]
id: Int!
name: String!
}
`;
export default userSchema;
So let’s go through what we have here.
We import gql
from the apollo-server-koa
package. Next we define 2 queries
type Query {
user(id: Int!): User
allUsers: [User]
}
The first one gets the user details for a specific id. The second, gets all users. These will be the queries that a client will call from the frontend to get the desired data. This is similar to the following endpoints in a REST api.
GET /users/:id
GET /users
Next, we define a User type containing some basic fields.
type User {
age: Int!
email: String!
hobbies: [String!]
id: Int!
name: String!
}
Take note of the exclamation(!) mark beside the data types. This just means that the field can only be of that particular type and not null or undefined. To learn more, check out the documentation on GraphQL data types.
Finally, we export the schema.
interfaces.ts
We also define a Typescript interface to match our GraphQL data types. If we don’t do this, Typescript will complain when we try to access these fields when creating resolvers.
interface resolverArg {
id: number;
}
interface User {
age: number;
email: string;
hobbies: string[];
id: number;
name: string;
}
export { resolverArg, User };
We create an interface for the User
type and also for resolverArg
which we will use in a bit.
datastore.ts
We will create a mock data store of users based on our schema. In production you will probably be accessing your data in a database, but for this purpose, we create an array of user objects. We also define a simple method for getting data from the data store.
import casual from 'casual';
import { User } from './interfaces';
const hobbies = [
'soccer',
'travelling',
'dancing',
'painting',
'sailing',
'fishing',
'movies',
'coding',
];
function generateUser(): User {
return {
age: casual.integer(20, 30),
email: casual.email,
hobbies: [casual.random_element(hobbies)],
id: casual.integer(1, 10),
name: casual.name,
};
}
const USERS = new Array(10).fill(0).map((x) => generateUser());
function getUserByIndex(index: number): User {
return USERS[index];
}
export { getUserByIndex };
We import the casual
fake data generator and User
interface. Then declare a list of hobbies.
We then define a function to generate a user object.
function generateUser(): User {
return {
age: casual.integer(20, 30),
email: casual.email,
hobbies: [casual.random_element(hobbies)],
id: casual.integer(1, 10),
name: casual.name,
};
}
We create an array of users with randomly generated data
const USERS = new Array(10).fill(0).map((x) => generateUser());
Finally, we define a function to return a user at a specific index. Similar to a SQL query that selects a user by id.
function getUserByIndex(index: number): User {
return USERS[index];
}
resolvers.ts
Next, we will define resolvers. A resolver is
a function that’s responsible for populating the data for a single field in your schema.
Apollo server documentation
An important point to note here is that every field in your schema should have its own resolver function.
So for example, our schema definition above should have 7 resolver functions. Two for the queries and one for each field of the User type. This is conceptually different from other API definitions that we might be used to; e.g REST where we only need to define one function to return the whole user object.
A resolver can contain 4 positional arguments parent, args, context
and info
. For our resolvers, we will only need parent and args.
parent
– The return value of the resolver for this field’s parent, i.e the previous resolver.
args
– An object that contains all GraphQL arguments provided for this field
For more information, check out Apollo’s documentation on resolver arguments.
So let’s create resolver.ts
with the following content
const userQueryResolver = (_: any, args: resolverArg) => {
return args.id;
};
const allUsersQueryResolver = () => {
return new Array(10).fill(0).map((_, index) => index);
};
The userQueryResolver
simply returns the id that was passed as an argument, while the allUsersQueryResolver
returns an array of ids from 0 to 9, which is the index for each element in our User
datastore.
Because we specified in our schema that these queries return the User
type, GraphQL is smart enough to pass these ids as the parent argument to the resolvers of the User
fields. Knowing this, we can define those resolvers as follows:
const UserTypeResolver = {
age: (id: number) => {
const user = getUserByIndex(id);
return user.age;
},
email: (id: number) => {
const user = getUserByIndex(id);
return user.email;
},
hobbies: (id: number) => {
const user = getUserByIndex(id);
return user.hobbies;
},
id: (id: number) => {
const user = getUserByIndex(id);
return user.id;
},
name: (id: number) => {
const user = getUserByIndex(id);
return user.name;
},
};
This might seem repetitive and you might be tempted to have just one resolver that returns the full user object, but writing resolvers for each field does have some advantages.
- It helps GraphQL to return only the required fields.
- It makes testing easier as you can focus on individual fields.
- It keeps your code cleaner.
That said, it does have some disadvantages, which I will discuss at the end.
Finally, we can export the resolvers
export default {
Query: { user: userQueryResolver, allUsers: allUsersQueryResolver },
User: UserTypeResolver,
};
Step 3: Initialize the GraphQL server
Now we can create our server to handle incoming requests. Create server.ts
with the following content.
import { ApolloServer } from 'apollo-server-koa';
import Koa from 'koa';
import typeDefs from './schema';
import resolvers from './resolver';
const server = new ApolloServer({
debug: true,
playground: true,
tracing: true,
resolvers,
typeDefs
});
const app = new Koa();
app.use(server.getMiddleware());
const port = 8080;
app.listen(port, () => {
console.log('server listening at port %s', port);
});
Here we create an apollo graphql server by passing the following parameters
typedefs
: Our schema definitionresolvers
: Our resolver functionsplayground
: enables a browser console for querying the servertracing
: allows us to monitor the performance of our queriesdebug
: Enables development helpers
Ideally, we only need to enable playground, tracing
and debug
in development.
For a full list of possible parameters, see the apollo-server documentation.
We then pass this server as a middleware to a KoaJS server. This allows the apollo-server integrate with KoaJS. Apollo has a host of integrations for express, AWS lambda and more. See the full list of Apollo server integrations.
Finally, we start our server on port 8080
.
Step 4. Run the server
To run the server we simply add a start script to our package.json
"start": "tsc && node dist/server.js"
In a terminal, run
npm start
Now we can query our GraphQL server
Step 5: Query your GraphQL server
In a browser, go to http://localhost:8080/graphql This will open the apollo graphQL playground. In the query editor, enter the following
query getUser($id: Int!){
user(id: $id){
age
email
hobbies
id
name
}
}
In the Query Variables tab, enter the following
{
"id": 1
}
Click on the play button at the middle of the screen and this should return the information of the user at index 1.
Open a new tab in the playground, enter the query below and click on send.
query getAllUsers{
allUsers{
age
email
hobbies
id
name
}
}
This should return the information of all your users
That’s it! We have successfully created a Graphql server and queried it to return some information.
Issue with defining a resolver for each field
One issue that you might have noticed is that for queries that return more than one field in a schema, we have to access the data store multiple times. To understand this, let us add a counter to our getUserByIndex function in datastore.ts
to become
function getUserByIndex(index: number): User {
console.count('getUserByIndex');
return USERS[index];
}
If we restart our server and run our queries again from the playground, we will notice the counts for our queries
User: getUserByIndex: 5
allUsers: getUserByIndex: 50
This means that the datastore was accessed 5 and 50 times respectively for both queries.
For our example, this is trivial because we just have an array of 10 users which we access from memory but in a production environment, this can lead to serious performance issues as you will need to query a datasource(probably a relational database) multiple times per request.
In my next post, I will explore how we can create less naive resolvers by using dataloaders to batch and cache requests to a datasource.
Next steps
We have seen how to integrate a basic GraphQL server with KoaJS http server in NodeJS. I have shown a fairly simple example and have not explored things like authentication, authorizations, GraphQL mutations, GraphQL context, error handling, and more. Here are some links that I feel will be useful to you in building out your app and learning more about GraphQL.
Some useful Apollo Javascript packages
Have any questions, want to share your thoughts or just say Hi? I’m always excited to connect! Follow me on Twitter or LinkedIn for more insights and discussions. If you’ve found this valuable, please consider sharing it on your social media. Your support through shares and follows means a lot to me! Also, you can get the complete source code for this post on github.