Wrapping an NPM package with GraphQL: a chess server

Let's have fun : create a GraphQL chess server !

In some projects, you'll need to include some NPM packages and run them as a service. Let's take an example and create a chess server using chess.js

We consider that you're familiar with code.store CLI, you have an account and you know how to create a new service. If not, follow our quick start guide.

Step 1: Create a new service

Check that you're authenticated :

cs whoami

If you receive following error message, that means you're not authenticated and you'll need to execute codestore auth:login command.

Seems that you're not logged in. Please execute  codestore login  command to sign-in again.

Let's create a new service :

cs service:create

 What is your service name?
 It should be the shortest meaningful name possible, for example:
        Meeting-rooms booking
 Service name: Chess Server

 Describe what functional problem are you solving with your service?
 It's optional and here is an example:
        My service manages meeting rooms and their booking by users
 What problem are you solving?: Let consumers play chess using GraphQL

 Describe how you solve it? It's optional too and should look something like:
        This service provides an API to create, update and delete rooms and
        another set of queries to manage bookings, cancellations, and search for available rooms.
 How you solve it?: Wrapping chess.js into a GraphQL endpoint

 What is the most relevant business domain of your service?
 Use up/down arrows to navigate and hit ENTER to select.
 Please select 'Other' as last option
 Business domain: Content_Management

 Now, the last thing, enter free-hashtags describing your service.
 Up to 5, comma-separated, no need to add #.
 Example:
        hospitality, booking, meeting-rooms, office
 Hashtags: games,chess,play,fun

You should get something like that :

✔ Created service with ID: chess-server
✔ Private and demo environment containers were built
✔ Deployed to private and demo environments
✔ Downloading service template 
Your service on private environment is available by this url: https://api.code.store/121066bcd3ae4a049a326c99639e26bc/graphql
Your service on demo environment is available by this url: https://api.code.store/858f86742a234d0b91be456a59089adb/graphql

Step 2: Chess game schema

As we created our service, the first thing we need to do is to modify the default GraphQL schema by adding a new Type: Game. Players will interact with our API to play chess, games will be stored in our database. Inside your service, you'll need to edit the /src/schema.graphql file.

By default, your schema looks like that :

# This file was generated by code.store CLI and it should not be deleted.
#
# Visit our documentation to learn more about GraphQL https://docs.code.store/getting-started/graphql-schemas as well as about working with code.store https://docs.code.store/getting-started/quick-start/quick-start-with-cli
#
# In case of questions, you can send us a message at our community chat https://spectrum.chat/code-store
#
type Query {
    helloWorld: String!
}

Let's remove the default helloWorld query and add a type where we'll store chess Game states, your schema should look like this :

type Game {
    id: ID!                                     #Game's unique identifier
    createdAt: String!                          #Game's creation date
    fen: String                                 #Game's FEN representation 
    ascii: String                               #ASCII art representation of the board
    turn: String!                               #Current turn of the game, may be B or W
}

Adding a new type will generate a table and TypeOrm entity for you. What we put inside our Game type is open, I decided here to store a unique identifier of a game, a timestamp of when the game was created, a FEN representation string ( Forsyth–Edwards Notation, which is a kind of compressed representation of a chess game), an ASCII textual visualization of a board and current turn (black of white). We could do less (just an ID is ok) or more (adding the game's state, history, comments, ...)

Let's test it immediately. Go inside your service folder and execute the following command :

cs generate

generate command validates your schema and, if necessary, generates database models, TypeOrm entities, and migrations. In our case, it will throw an error, because there are no queries anymore in our schema (we removed the default helloWorld query).

cs generate
Compiling your code
Query root type must be provided.
Preparing the service code for upload
Reverting extra migrations
Uploading service to the generator
Saving generated code 

Error: Error: Query root type must be provided.

So we need to add a query to create a valid schema. One of the simplest ones would a query to load a game from the database providing its ID as parameter and returning a Game object :

type Game {
    id: ID!             #Game's unique identifier
    createdAt: String!  #Game's creation date
    fen: String         #Game's FEN representation 
    ascii: String       #ASCII art representation of the board
    turn: String!       #Current turn of the game, may be B or W
}

type Query {
    load(gameId: ID!): Game
}

You can notice here that there is no ! mark after the Game, which means our query may return null (in case of the wrong ID for example). Save your schema.graphql file, return to your favorite CLI, and run cs generate again. Yeehaa! You should get something like that :

cs generate
Compiling your code
Validating schema
✔ Preparing the service code for upload
↓ Migrations were not reverted: role "username" does not exist [SKIPPED]
✔ Uploading service to the generator
✔ Generated code has been saved 

As we started to work with stored objects, we need to setup a local database. You can just install postgre, if you don't know check our quick tutorial.

So now we have a schema with one query: load and one type: Game. We've setup a local database. We can try to run our chess service locally. You need to go to the root directory of your service and execute cs dev command:

cs dev
2020-09-11T10:02:54.416Z [NPM] Installing dependencies
2020-09-11T10:02:58.832Z [TypeScript] Compiling typescript code
2020-09-11T10:03:08.122Z [GraphQL] Validating schema
2020-09-11T10:03:08.146Z [GraphQL] Validating queries and mutations
 ›   Error: Error: helloWorld queries are not defined in schema

Oops! We've removed helloWorld query from our schema and replaced it with load query, but we forgot resolvers. Basically, for each query (mutation or query) there is one TypeScript file in /src/resolvers/queries folder. By default, when you create a new service, we create a helloWorld query resolver, so you'll find helloWorld.ts file there. Our loader will try to match available resolvers to the ones in the schema, and that's why we got the previous error.

Let's simply rename helloWorld.ts to load.ts

mv helloWorld.ts load.ts

Step 3: Include an NPM package

So now we need to add chess.js NPM package to our service, execute the following command in the root directory of your service:

npm install chess.js

To control that everything is ok, check the package.json file, it should look like that:

{
  "name": "chess-server",
  "version": "1.0.0",
  "author": "code.store",
  "scripts": {},
  "dependencies": {
    "chess.js": "^0.11.0",
    "codestore-utils": "^1.3.4",
    "pg": "^8.2.1",
    "typeorm": "^0.2.24"
  }
}

Step 4: Create a game and store it in a database with a mutation

So it's time to write our first mutation and use chess.js at the same time! First, let's edit the schema.graphql file in /src folder. As you remember we've added a Game type and a query load. Now time to add a mutation. Mutations are specific kind of queries that can modify the service's database state:

type Game {
    id: ID!                     #Game's unique identifier
    createdAt: String!          #Game's creation date
    fen: String                 #Game's FEN representation 
    ascii: String               #ASCII art representation of the board
    turn: String!               #Current turn of the game, may be B or W
}

type Query {
    load(gameId: ID!): Game     #Loads an existing game by it's ID and returns a Game object
}

type Mutation {
    createGame: Game!           #Creates a new game and returns a Game object
}

We need now create a mutation TypeScript file named createGame.ts in /src/resolvers/mutations

import { logger, Resolver } from 'codestore-utils';
import Chess from 'chess.js'
import Game from '../../data/entities/Game';

const resolver: Resolver = async (parent, args, context, info) => {
  logger.log('This is a createGame mutation!', 'createGame');
  const chess = new Chess.Chess()
  let game = new Game()
 
  game.createdAt = Date.now().toString()
  game.ascii = chess.ascii()
  game.fen = chess.fen()
  game.turn = chess.turn()
  
  const repository = context.db.connection.getRepository(Game)
  await repository.save(game);   
  
  return game
}

export default resolver;

Let's analyze what's inside. First, we need to include chess.js package, the one we installed trough npm install command:

import Chess from 'chess.js'

Then, we also need to import the Game TypeOrm entity, so we can access the storage object:

import Game from '../../data/entities/Game';

Then, we simply create a chess object and a new Game entity and fill Game's entity fields. Then we create a TypeOrm repository connection, save the Game object, and return it.

When a TypeOrm object has an ID field, it will be automatically created and filled in the returned object.

Let's test our service locally, by running from the service root folder cs dev command. You should see something like that :

cs dev
2020-09-18T12:44:19.286Z [NPM] Installing dependencies
2020-09-18T12:44:23.395Z [TypeScript] Compiling typescript code
2020-09-18T12:44:23.398Z /Users/maximetopolov/CLI-Demo/chess-server/src/rest is not available
2020-09-18T12:44:32.886Z [GraphQL] Validating schema
2020-09-18T12:44:32.914Z [GraphQL] Validating queries and mutations
2020-09-18T12:44:36.159Z [INFO] Starting development server
2020-09-18T12:44:36.159Z [Bootstrap] Start bootstrapping the application
2020-09-18T12:44:36.160Z [Database] Connecting to database
2020-09-18T12:44:36.309Z [Database] Successfully connected
2020-09-18T12:44:36.454Z [Database] Migrations ran
2020-09-18T12:44:36.456Z [GqlLoader] Loaded queries: load
2020-09-18T12:44:36.456Z [GqlLoader] Loaded mutations: createGame
2020-09-18T12:44:36.472Z [RestLoader] Loading rest handlers
2020-09-18T12:44:36.472Z [RestLoader] No REST handlers available
2020-09-18T12:44:36.478Z [Bootstrap] Graphql is available on: http://localhost:3000/graphql

You can play with your service by loading in your browser the API endpoint : http://localhost:3000/graphql

Last updated