Nicolas Lupien

@betaflag

GraphQL on Rack — Writing a modular Graphql Server in Ruby

In this article, I’m going to show the required pieces to put together a production-ready GraphQL Server from scratch in Ruby by reusing the open source GraphQL gem and Rack, a modular Ruby web server interface.

Why Rack?

In Ruby-land, Rack can be compared to Node.js’ excellent express web server. However, its approach is a little bit different. While Express is a full web server, Rack only gives you the building blocks to roll your own. Since most Ruby web servers are built on Rack, we can use them to power our GraphQL server in no time.

Rack applications

In Rack, like most web servers, you deal with web requests and responses. The request is the information incoming from the web browser (URL, User Agent, cookies, parameters) and the response is what the web server sends back to the browser (status, headers, HTML, JSON).

The simplest Rack application simply receives a request and returns a response.

app = Proc.new do |env| 
  ['200', {'Content-Type' => 'text/html'}, ['Hello World']]
end

The Proc defines a new block of code to execute when the server receives a request and wraps its information in the env parameter. The block returns an array with 3 items: the HTTP status code, the response headers and the response body.

Rack offers multiple ways to run your application. It offers a basic development server called rackup but because Rack is modular, you can replace the runtime by any production-ready Ruby web server implementing the Rack interface like Thin.

gem install thin
thin start

Yes, it’s that simple!

Building the server

Based on the concepts mentioned above, let’s build the skeleton of our server.

require 'rack'
class GraphQLServer
def initialize(schema:, context: {})
@schema = schema
@context = context
end
  def response(status: 200, response)
[
200,
{
'Content-Type' => 'application/json',
'Content-Length' => response.bytesize.to_s
},
[response]
]
end
  def call(env)
request = Rack::Request.new(env)
response(200, "")
end
end

The initialize method takes a GraphQL schema as a parameter and a context object that we will pass to the schema resolvers at runtime. Have a look at the GraphQL gem for more information on how to build your schema. I also provide an example toward the end of this article.

The call method is called for each request by Rack but it doesn’t do much for now.

I decided to extract the response in its own method to simplify the call method that we will work on below.

Extracting the payload

The GraphQL HTTP protocol can pass a payload using both GET and POST request types. When using GET, the payload is located in the request parameters and when using POST, it’s in the request body.

We add a conditional to extract the payload according to the request type like this:

def call(env)
request = Rack::Request.new(env)
  payload = if request.get?
request.params
elsif request.post?
body = request.body.read
JSON.parse(body)
end


response(200, "")
end

Processing a GraphQL request

In the GraphQL HTTP protocol, a request consists of 3 objects: the query, its variables and the operation name. Since we already parsed the payload and the schema (from the constructor), we just have to pass these parameters to the schema.execute method.

def call(env)
request = Rack::Request.new(env)
  payload = if request.get?
request.params
elsif request.post?
body = request.body.read
JSON.parse(body)
end
  result = @schema.execute(
payload['query'],
variables: payload['variables'],
operation_name: payload['operationName'],
context:
@context,
).to_json


response(200, result)
end

That’s it! You now have a spec-compliant GraphQL server in Ruby.

Using your new GraphQL Server

You can start using your Rack server with any schema built with the GraphQL gem and serve it with your favourite Ruby web server.

require 'graphql'
type_def = <<-GRAPHQL
type Query {
hello: String
}
GRAPHQL
resolver = {
"Query" => {
"hello" => Proc.new { "world" }
}
}
schema = GraphQL::Schema.from_definition(
type_def,
default_resolve: resolver
)
run GraphQLServer.new(schema: schema)

I haven’t covered the run directive yet but it simply allows Rack to use your server for all incoming requests.

What’s next?

This is a very simple and naive implementation but it works with any spec-compliant GraphQL client like Apollo. However, if you want more flexibility and some error handling, you can use my open source graphql-server gem which this article is based on.

If you like this kind of stuff, follow me on Medium and on Twitter @betaflag.

More by Nicolas Lupien

Topics of interest

More Related Stories