Open Sourcing GraphQL Guru Server

August 05, 2017 0 Comments

Open Sourcing GraphQL Guru Server

 

 

From the first time I read How to Build a GraphQL Server by Jonas Helfer I instantly fell in love with apollo-server.

What I liked most about the apollo-server was how it simplified the creation of a graphql server and it used the Schema Definition Language to declaratively express the schema. Within minutes I had a server up and running. Even better it came with GraphiQL, making creating and testing queries and mutations a breeze. Immediately I started using apollo-server for my projects. I was in love and the honeymoon lasted a while.

As the server grew with multiple files for schema and resolvers, the amount of boilerplate involved became increasingly frustrating. This was no fault of apollo-server as its concern was creating the graphql endpoint which it does very well. But adding new modules in the project became a nightmare. Dread the thought of renaming a module or changing the file structure. Too many places to import and refactor. An error in the process would crash the server and finding the cause was near impossible. At times it was easier to delete the module and imports and start again. After repeating this process on a couple of projects I swore never again. I was happy with apollo-server and how I structured my project but not with the boilerplate involved. So I set about automating the process into a development experience I would like to work with.

If you have never heard of graphql, take a look at graphql.org by Facebook for documentation and tutorials. Samer Buna has written Rest APIs are REST-in-Peace APIs Long Live GraphQL, which gives a good explanation of what graphql is. Therefore, I will only give a quick rundown.

Feel free to skip to the next section.

GraphQL is a language for APIs to query data. It is not to be confused with SQL and it does not query the database. GraphQL has two parts: request and server.

The request is a string which consists of queries that contains the names of the functions to be executed on the server and a list of the data fields to be returned. No special client is required to send requests. Requests can be sent to the sever over HTTP.

The server has a schema which describes the type of data and fields that can be requested. A single endpoint receives those requests and passes them onto a resolver function. Resolvers can be thought of as controllers from MVC. The resolver function is where you do such things as database operations or perform some computation to return.

The apollo-server endpoint — abstracted behind a single function which takes no arguments.

GraphQL Guru is an open source Node.js Graphql framework I have been working on lately. It is an abstraction around apollo-server to address the pain points mentioned above. Potentially, anything that you can do in apollo-server, you can do in Guru.

Main objectives of Guru are:

  • Scaffolding a full server
  • Dynamic imports
  • Structureless modules
  • Multiple pluggable database clients
  • Resolver caching
  • Cacheable persistent queries
  • Schema and query validation
  • IDE and admin tools
  • Do just enough and step out the way

Most frameworks I have tried are like learning a new language. They introduce new terminology and a host of methods that have to be remembered along with their prescribed way of doing things. As a developer this is something I never liked. If it is javascript, I expect it to work like javascript. After taking the time to learn GraphQL and apollo-server the last thing anyone wants is to learn another framework. Therefore, I made an early decision to limit introducing anything new to learn. If you know Graphql and Node.js, then you know how to use Guru.

create-graphql-guru — creates a fully working express server
GraphQL Guru quick demo

The easiest way to get started is via the npm create-graphql-guru CLI tool. Inspired by create-react-app and ruby-on-rails, create-graphql-guru is a command line tool that provides project scaffolding and generators for schemas, resolvers and database clients.

The command guru express my-server creates a fully working express server with no configuration required. Below is an outline of the structure produced by the CLI.

my-server
├── bin
│ └── www
│ └── index.js
├── node_modules
├── public
├── server
│ ├── core
│ │ ├── database
│ │ ├── middleware
│ │ │ └── index-middleware.js
│ │ ├── routes
│ │ │ └── index-routes.js
│ │ └── database.js
│ ├── history
│ │ ├── persisted-history.json
│ │ └── query-history.json
│ ├── modules
│ │ └── resolverQuery-hello.js
│ │ └── schemaQuery-hello.graphql
│ │ └── schemaType-hello.graphql
│ │ └── access.js
│ ├── queries
│ ├── views
│ │ └── layout
│ │ └── index.hbs
│ ├── environment.js
│ └── index.js
├── LICENSE
├── package.json
├── README.md
└── yarn.lock

The command npm run dev starts the server in development mode.

Guru Express server started in development mode.
- IDE address = http://localhost:8000
 - GraphQL endpoint:     = http://localhost:8000/graphql

The index.js file inside of the server directory contains the graphql endpoint. Well, not exactly. The apollo-server endpoint is abstracted behind a single function which takes no arguments.

A typical apollo-server looks like the following and grows whenever a new connector, resolver or schema is added.

import express from 'express';
import bodyParser from 'body-parser';
import { apolloServer } from 'apollo-server';
import { makeExecutableSchema} from 'apollo-server';
import { graphqlExpress } from 'apollo-server-express';
import connectors from './connectors';
import resolvers from './resolvers';
import schema from './schema';
const PORT = 3000;
const app = express();
const logger = { log: (e) => console.log(e) }
app.use('/graphql', bodyParser.json(), graphqlExpress(req => ({
schema: makeExecutableSchema({
typeDefs: schema,
resolvers,
logger
}),
context: {
connectors,
req
}
})));
app.listen(PORT);

With Guru server it is reduced to 2 lines.

import graphQLExpress from "@graphql-guru/express-server";

graphQLExpress();
Possible to move around or rename module files without the need to refactor

At the core of Guru are a number of loader functions. The loader functions search the projects for resolvers, schemas, models and various other files to dynamically import and for generating code.

Project structure is important. For the loader to know where to look for the files it must be placed inside the correct directory. Most directories have an index file, which is the file to be imported. The module directory is an exception to this rule, as there are no index files.

The module directory is where schemas, resolvers, validations and models live. These files can be deeply nested inside the module directory. The loader recursively searches the module directory and imports the files. This is possible due to the naming convention of the files.

Module file names begin with a predefined (camelcase) prefix and has an optional suffix separated with a dash. Modules can be split across multiple files and are merged based on their function or class name. This makes it possible to move around or rename module files without the need to refactor.

Yay! no more imports :)

Below is a list of module files type:

  • resolverMutaion.js
  • resolverQuery.js
  • schemaMutaion.graphql
  • schemaQuery.graphql
  • schemaType.graphql
  • model.js
  • validation.js

For a working server there must at least be a resolverQuery.js, schemaQuery.graphql and schemaType.graphql files.

Schema is the single source of truth

Unlike some of the other graphql servers, I have chosen a schema first approach, because a graphql schema is a rich source of information. What this means is the schema is the single source of truth, which Guru builds upon. The schema is used to generate code for relationships, connectors and a JSON representation of your schema along with validation. As with apollo-server Guru schema uses the Schema Definition Language, which among other things provides readability.

Schema relationships are as simple as defining them as an alias on a schema field

Each schema has a corresponding resolver with a class of the same name which contains the schema methods. Schema relationships are as simple as defining them as an alias on a schema field and adding a resolve method in the resolver class. As the resolver class and the schema object type have the same name, this is used to create the relationships between schemas and then passed to the respective resolver resolve method.

schemaType.graphql

type Hello {
id: String
firstName: String
lastName: String
}

schemaQuery.graphql

greet(name): Hello

resolverQuery.js

class Hello {
resolve () {}
  greet ({ args }) {
return Hello ${args.name}!;
}
}
export default new Hello();
Having inheritable database methods enables rapid development
GraphQL Guru pluggable database diagram

I wrote an article a few months ago called ENRG stack which stands for Express, Node, React and GraphQL. The idea behind the article was graphql servers allow for a database agnostic stack. I have transferred this idea into Guru. Guru not only works with any database but with multiple databases at the same time, as graphql intended.

Resolvers as mentioned above are plain javascript classes and therefore can be extended. Guru provides generic database methods for MongoDB, RethinkDB and Redis which resolvers can inherit.

With exception of Redis the available methods are

Queries

  • resolve
  • findAll
  • findById
  • findManyById

Mutations

Having inheritable database methods enables rapid development of your server until you are ready to overwrite them in the resolver with custom logic. It also allows built in caching so the same data is not retrieved multiple times. Of course you can always write and share your own generic modules for other database clients.

Update: Support for PostgreSQL, MySQL, SQLite and MSSQL has been add via sequelize (graphql-guru-sequelize). Local JSON database support via lowdb (graphql-guru-jsondb).

Few more features to GraphiQL, which I missed from Postman

Guru IDE is built around the awesome GraphiQL. If you have never used GraphiQL before it is a frontend interface for querying graphql servers. Before I was using GraphiQL I was writing graphql requests with Postman which is not suitable for graphql. What I wanted was a cross between Post and GraphiQL. So I decided to add a few more features to GraphiQL, which I missed from Postman.

  • Save query history
  • Persist queries on the server
  • Organise queries into collections
  • Batch queries
  • Query only by the persisted ID

The last point also allowed me to add middleware on the server to transform the persisted IDs into graphql queries and enable whitelisting of queries.

There’s still a lot to do on the IDE. Eventually I see Guru IDE becoming a standalone frontend for Guru.

Guru has drastically decreased the time to build a graphql server but I feel it can still be a lot more. My plan is for Guru to remove many of the decisions that I make when building a server as well as the boilerplate that accompanies these decisions.

Building Guru has been a fun journey and by far I had the most fun building the code generators and the CLI. Removing all that boilerplate was pure joy.

Graphql Guru is still in the early stages. Please give it a try and leave feedback in the comments. If you would like to collaborate or contribute that would be awesome.

Thanks for reading.

Click the ❤️ below to support my work.

I’m Otis Virginie, A Full Stack (React, Redux, Node, GraphQL) JavaScript Developer.


Tag cloud