You are currently viewing GraphQL Server – Apollo, KoaJS and Typescript implementation.

GraphQL Server – Apollo, KoaJS and Typescript implementation.

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 definition
  • resolvers: Our resolver functions
  • playground: enables a browser console for querying the server
  • tracing: allows us to monitor the performance of our queries
  • debug: 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.

graphql playground

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

graphql playground

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. 

Introduction to GraphQL

Apollo platform documentation

Some useful Apollo Javascript packages

KoaJS Middlewares

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.

Leave a Reply