Creating GraphQL API with Koa and TypeORM

Stanislav Govorov
Oct 17, 2017 · 11 min read

Hi! Today we’ll create a working GraphQL API prototype for a todo-list application from scratch. When I started to play with GraphQL I realized that it was a bit tricky for me to find step-by-step GraphQL+TypeORM guide. Though specs are fairly well described, every piece of the stack seems to be quite simple to understand, and concepts are consistent and sound, I realized I still don’t have the whole picture in my head. After diving into specs, googling a lot of articles and collecting information piece by piece I decided to create such a guide. We’ll walk through all necessary steps from the very beginning to working example.

Let’s get started!

Creating app stub

Create an empty directory for our future app and cd into it. All file paths in this article are relative to this project root directory.

First of all we need to install dependencies:

$ yarn add \
nodemon \
typescript \
ts-node \
@types/node \
koa \
koa-bodyparser \
koa-router \
graphql \
apollo-server-koa \
pg \
tsconfig-paths \
typeorm \
reflect-metadata
  • nodemon will watch for file changes and restart server automatically
  • ts-node allows us to write typescript code and run it with nodejs
  • we also need tsconfig-paths to make “paths” mapping from tsconfig.json work as expected
  • koa is a web framework our app will be based on
  • apollo-server-koa is a GraphQL library for koa
  • pg is a PostgreSQL driver for node
  • typeorm is an ORM we’re going to use.
  • reflect-metadata is its dependency

Configure typescript compiler:

tsconfig.json:{
"compileOnSave": false,
"compilerOptions": {
"noImplicitAny": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution" : "node",
"lib": [
"es2017"
],
"types": [
"node"
],
"sourceMap": true,
"baseUrl": "./src"
},
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}

I use paths mapping here to get rid of weird relative paths in import declarations, e.g import '../../../entities/card' will become import 'entities/card' .

experimentalDecorators flag enables decorators support and emitDecoratorMetadata flag is responsive for providing decorator functions with necessary type information. Both of them are essential for typeorm.

You can read more about these and other compiler options hreflect-metadataere.

Application sources will live inside ./src directory.

Nodemon configuration:

nodemon.json:{
"watch": ["./src"],
"ext": "ts",
"exec": "ts-node --inspect -r tsconfig-paths/register ./src/server.ts"
}

Here we asked nodemon to watch files under ./src directory and restart server after every change. --inspect flag is necessary for debugging purposes as we’ll use Chrome DevTools.

Nodemon is very powerful, and I encourage you to read the docs.

Finally, add this command to scripts section in package.json:

"start" : "nodemon",

Given this alias, we’re now able to run our app via yarn start command.

I use yarn in this post, but npm should also work fine. However, it’s significantly slower and is lack of some handy features.

It’s time to create a server, let’s create a stub first to check whether our stack is working as expected:

./src/server.ts

Let’s check whether it’s working (make sure you have dependencies installed) :

$ yarn install && yarn start
Expected console output
$ http localhost:3000
Server responded 200 OK

Seems to be working. Nothing special so far.

Utilizing database

Now it’s time to make our app more dynamic.

The main unit in out future app is a Card. It’s a single todo item which has a title, description and done flag.

Let’s describe it in terms of typeorm API and create an Entity class:

@Entity and other words beginning with @ are decorators. It’s a ES Stage 2 Draft proposal feature you’re most likely already have heard about. You should definetly master them if you’re not familiar with decorators already. Here are the docs.

To recap, inside our class we just define props and mark them as different kind of columns using typeorm decorators.

Optional parameter I passed to @Entity('cards') is a table name. Typeorm will create table named card if you omit it. It’s up to you to decide whether to use default naming strategy or not, but using plural forms of entities is a good naming convention, and many frameworks support it out of the box.

Now we need to tell our app how to connect to a database.

There are a lot of examples how to create a connection right in the same place where Koa or express is starting. Most of these examples are about wrapping webserver initialization code into createConnection callback, and all in one place. But let’s keep our code dry and neat and separate db initialization logic from app start. This will be especially useful as our app will grow and more and more initializers will have been created. We’ll create a separate initializer for it:

I deliberately placed debugger call here to encourage you to explore createConnection callback arguments and discover typeorm stuff.

Notice async word here. It’s another ES feature supported by typescript. It’s nothing but special syntax to work with promises in a more comfort, declarative way. There’s a beatiful explanation what async/await is: https://gist.github.com/Navix/ec0a684481cb30d38817d8f72b084ef1

and animated version:

Now we have created initializer. It’s time to add it to server initialization code:

or more declarative, utilizing async/await (especially useful with multiple initializers):

Notice that initialize/database is not a relative path and resolved to ./src/initializers/database. It’s possible due to paths option in our tsconfig.json.

Now make sure you have postgres up and running and look at the console output (nodemon did all the job for us and we have the most recent version of our server running). If everything is ok you should see “Database connection established”:

TypeORM initialization

If you did not see any SQL, make sure you have logging option configured properly in initializer options.

Oh, what is it?CREATE TABLE query. But I don’t remember any code about table creation… Looks like typeorm has created a table for us. Let’s check our database structure:

db_dev=# \d cards                                                                                                           
Таблица "public.cards"
Столбец | Тип | Модификаторы
-------------+-----------------------------+-------------------------------------
id | uuid | NOT NULL DEFAULT uuid_generate_v4()
created_at | timestamp without time zone | NOT NULL DEFAULT now()
updated_at | timestamp without time zone | NOT NULL DEFAULT now()
title | text | NOT NULL
description | text |
done | boolean | NOT NULL
Индексы:
"cards_pkey" PRIMARY KEY, btree (id)

Magic.

Notice we have synchronize: true option in connection initializer. According to documentation, this feature is responsive for keeping our entity classes and database schema in sync. This could be quite handy for development, as you don’t need to create tables manually. However, it’s a contraversial feature, you can disable it and create table using migrations, or even using psql directly:

# CREATE EXTENSION IF NOT EXISTS “uuid-ossp”;
# CREATE TABLE "cards" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
"title" text NOT NULL,
"description" text,
"done" boolean NOT NULL,
PRIMARY KEY("id")
)

Ok. So far we have a storage and a configured connection. Let’s add some dummy code to make sure that saving to the database is working:

Go back to a terminal and notice we have INSERT query there:

Don’t forget to remove that code from initializer, otherwise a new record will be created at every restart.

Routing

Our server is still completely useless. Now it’s time to define API routes.

We’ll keep app routes in a separate config: ./src/routes.ts , thus ./src/server.ts should now look like this:

And ./src/routes.ts:

Our router config is based on koa-router. Its DSL allows us to define application routes in a declarative way. In our case a config is quite simple, but koa-router has a plenty of features like nesting, named parameters and many more.

What we did here is just declared that GET and POST requests to /graphql will be sent to special GraphQL handler middleware — Apollo server (don’t worry, I’ll describe it a bit later):

routes.get(apiEntrypointPath, graphQlOpts);
routes.post(apiEntrypointPath, koaBody(), graphQlOpts);

Take a look at next entrypoint:

routes.get('/graphiql', graphiqlKoa({ endpointURL: apiEntrypointPath }));

GraphiQL, according to its github page, is an in-browser IDE for exploring GraphQL. Once we have it attached to our server, we have full-pledged in-browser client where we can play with our API: explore schema, perform queries and anything like that with features like autocompletion, typeahead, error highlighting, query history and many more:

Utilizing GraphQL

Our app is broken now, as our router tries to import unexisting modules:

We will use Apollo Server in our app to deal with all this GraphQL magic. First of all, I encourage you to read official GraphQL guide in order to be familiar with its basic concepts.

What we need to do next is to setup all that GraphQL stuff: schemas, types and so on.

Keeping things dry, we’ll place related files in ./src/graphql directory.

First, let’s create a simple schema to play with apollo basics. We need to describe all possible query types and then compose them to a schema.

As we have Card entity in our app and we want cards be accessible via API, we need to describe cards as a Type , that in our case is just a set of available fields:

What’s really great that having these type definition GraphQL will take care about such painful problems as input parameters type checks and fields validation, throwing errors with human-readable messages for us.

Next we need to tell GraphQL what kind of queries are possible by describing a special Query type. For instance let’s create two queries — for one card and for collection of cards:

We’ve declared here that cards query returs an array ([]hint) of Card entities, and card expects id string parameter, which is required (!), and returns an instance of Card . Type system specification is pretty human-readable and fairly well described in official documentation. Great advantage of graphql-tools is that you use the GraphQL schema language as it is — graphql-first philosophy. No weird DSLs and no mess with vague function calls. All types and schema definitions are just plain strings — in exactly the same form as in official GraphQL docs.

Ok, we have types and query schema, but we forgot about resolvers — magic pieces of code that will convert queries to real data. Let’s create them:

Resolver is a function that returns a promise which resolves to a real data:

async card(obj, { id }, context, info) {
const repository = getRepository(Card);
return await repository.findOne({ id });
}

Arguments passed to resolver contain all necessary information about a query, parent resolved data, execution state of a query, field names, path to the field from the root and more. Resolver function signature is described here.

Finally we just compose all of these stuff into a Schema :

makeExecutableSchema and GraphQLSchema are Apollo Server and graphql-tools concepts used to convert schema definition from text to internal data structures. Docs

Let’s test our new API. Open GraphiQL in browser and try the following query:

{
cards {
title
}
}

As we already have at least one record in our database (remember we tested entities creation?), you should’ve seen something like this:

Notice that though we have received all fields from the database, API returned only fields that we asked for. However, in real-world application it’s a no-way to receive all fields everytime and your resolvers should be smart enough to compose a right database query.

Try to send wrong fields and query names and look at error messages.

It even has “did you mean” feature out of the box!

"message": "Cannot query field \"cardss\" on type \"Query\". Did you mean \"cards\" or \"card\"?",

Our cards query has no filters and limits. I will not cover pagination and filtering patterns in this post, you can read a beatiful article about pagination on GraphQL official documentation.

Mutations

API we’ve just created is read-only. Let’s define some mutation queries. Mutation is a kind of GraphQL query that is used to modify data on a server.

For instance, here’s how we create a query that will toggle todo-card done flag:

  • define a simple mutation toggleCardMutation
  • create a root mutation and add new mutation query to it
  • add mutations to schema
  • add mutations to resolvers

Just like Query type, mutation type we’ve created is quite self-documented:

type Mutation {
toggleCard (
id: String!
): Card
}

Here we tell GraphQL that we expect toggleCard mutation query with one required parameter — card id, which is a string. And response will be a Card type instance. Hence every field manipulations are possible.

Just as like as resolver function, a mutation is a simple function that returns a Promise (because of it’s async nature):

async toggleCard(_, { id }) {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const done = !card.done;
const result = await repository.updateById(id, { done });
return {
...card,
done,
};
}

However, GraphQL is smart enough and if you don’t need any async logic in your resolver, synchronous functions should also work out of the box.

Now as our build is should be success, switch to GraphiQL and try to perform a query:

mutation {
toggleCard(id:"d9ab9bbb-0f0a-4636-bc07-9f72d5dbab03"){
title
created_at
done
}
}

Performing query a couple of times you should see done flag switching after every request:

Look at your console, you should see multiple UPDATE queries there:

Patching cards

Now let’s create something more complex: API method for modifying any arbitrary fields. The same way as we created toggleCard , we need to follow these steps to create CardPatch query type:

Notice that we allowed only a subset of fields to be patched. It’s a business logic restriction to prevent fields such id, created_at, updated_at from being updated. And again, we did it in a very human-readable and declarative way, utilizing GraphQL powerful type definition syntax.

Try to perform patch queries and make sure everything works:

updating card via GraphiQL
TypeORM performs UPDATEs

Create cards

And finally we need a method that will allows us to create new items and save them to the database. Type definitions are quite similar to updateCard , except one detail — we require at least title field to be not empty:

Check whether new method is working:

Creating new Card entry
TypeORM performs INSERT query

Notice that as we’ve whitelisted allowed fields, it’s not possible to send idand other special fields manually:

Only whitelisted fields are alowed

That said, sometimes you might want to generate ids on clientside. It’s called persistence ignorance pattern, and it allows you to create entity with all relations on a clientside.

Now as our prototype is complete, look how simple and self-descriptive our API schema is. It’s quite easy to dive into all this types and schemas, and understand what’s going on within minutes.

This approach scales pretty well. As your application grows, you can easily add authentication middlewares and create different schemas with different query types for different user roles.

That’s it! We’ve created working GraphQL API from scratch. Hope you find this post useful. Thanks for reading!

Complete example is available on github.

Stanislav Govorov

Written by

Software Engineer @ Mendix

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade