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.
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
['200', {'Content-Type'
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 thinthin start
Yes, it’s that simple!
Based on the concepts mentioned above, let’s build the skeleton of our server.
require 'rack'
class GraphQLServerdef initialize(schema:, context: {})@schema = schema@context = contextend
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, "")endend
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.
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.paramselsif request.post?body = request.body.readJSON.parse(body)end
response(200, "")end
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.paramselsif request.post?body = request.body.readJSON.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.
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 = <<-GRAPHQLtype 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.
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.