DEV Community

Cover image for A First Look at GraphQL Helix
ajcwebdev
ajcwebdev

Posted on • Edited on • Originally published at ajcwebdev.com

A First Look at GraphQL Helix

Outline

All of this project's code can be found in the First Look monorepo on my GitHub.

Introduction

GraphQL Helix is a framework and runtime agnostic collection of utility functions for building your own GraphQL HTTP server. Instead of providing a complete HTTP server or middleware plugin function, GraphQL Helix only provides a handful of functions for turning an HTTP request into a GraphQL execution result. You decide how to send back the response.

Motivations and API

Daniel Rearden listed the following reasons pushing him to create Helix, believing that these factors were absent from popular solutions like Apollo Server, express-graphql and Mercurius:

  • Wanted bleeding-edge GraphQL features like @defer, @stream and @live directives.
  • Wanted to not be tied down to a specific framework or runtime environment.
  • Wanted control over how server features like persisted queries were implemented.
  • Wanted something other than WebSockets (i.e. SSE) for subscriptions.

renderGraphiQL and shouldRenderGraphiQL

renderGraphiQL returns the HTML to render a GraphiQL instance. shouldRenderGraphiQL uses the method and headers in the request to determine whether a GraphiQL instance should be returned instead of processing an API request.

getGraphQLParameters

getGraphQLParameters extracts the GraphQL parameters from the request including the query, variables and operationName values.

processRequest

processRequest takes the schema, request, query, variables, operationName and a number of other optional parameters and returns one of three kinds of results, depending on how the server should respond:

  1. RESPONSE - regular JSON payload
  2. MULTIPART RESPONSE - multipart response (when @stream or @defer directives are used)
  3. PUSH - stream of events to push back down the client for a subscription

Serve GraphQL Helix Locally

mkdir ajcwebdev-graphql-helix cd ajcwebdev-graphql-helix yarn init -y yarn add express graphql-helix graphql touch index.js echo 'node_modules\n.DS_Store' > .gitignore 
Enter fullscreen mode Exit fullscreen mode

index.js

// index.js const express = require("express") const { getGraphQLParameters, processRequest, renderGraphiQL, shouldRenderGraphiQL, } = require("graphql-helix") const { GraphQLObjectType, GraphQLSchema, GraphQLString, } = require("graphql") const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: "Query", fields: () => ({ hello: { type: GraphQLString, resolve: () => "Hello from GraphQL Helix!", } }), }), }) const app = express() app.use(express.json()) app.use("/graphql", async (req, res) => { const request = { body: req.body, headers: req.headers, method: req.method, query: req.query, } if (shouldRenderGraphiQL(request)) { res.send(renderGraphiQL()) } else { const { operationName, query, variables } = getGraphQLParameters(request) const result = await processRequest({ operationName, query, variables, request, schema, }) if (result.type === "RESPONSE") { result.headers.forEach(( { name, value } ) => res.setHeader(name, value)) res.status(result.status) res.json(result.payload) } } }) const port = process.env.PORT || 4000 app.listen(port, () => { console.log(`GraphQL server is running on port ${port}.`) }) 
Enter fullscreen mode Exit fullscreen mode

Run test queries on GraphQL Helix Locally

Start the server with node index.js.

node index.js 
Enter fullscreen mode Exit fullscreen mode

Open localhost:4000/graphql and send a hello query.

query HELLO_QUERY { hello } 
Enter fullscreen mode Exit fullscreen mode

01-graphql-helix-localhost-4000

curl --request POST \ --url http://localhost:4000/graphql \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}' 
Enter fullscreen mode Exit fullscreen mode

GraphQL Helix Final Project Structure

├── .gitignore ├── index.js └── package.json 
Enter fullscreen mode Exit fullscreen mode

Deploy GraphQL Helix with Serverless Framework

The Serverless Framework is an open source framework for building applications on AWS Lambda. It provides a CLI for developing and deploying AWS Lambda functions, along with the AWS infrastructure resources they require.

mkdir graphql-helix-serverless cd graphql-helix-serverless yarn init -y yarn add express graphql-helix graphql serverless-http touch index.js serverless.yml echo 'node_modules\n.DS_Store\n.serverless' > .gitignore 
Enter fullscreen mode Exit fullscreen mode

index.js

The serverless-http package is a piece of middleware that handles the interface between Node applications and the specifics of API Gateway. It allows you to wrap your API for serverless use without needing an HTTP server, port, or socket.

// index.js const serverless = require('serverless-http') const express = require("express") const { getGraphQLParameters, processRequest, renderGraphiQL, shouldRenderGraphiQL, } = require("graphql-helix") const { GraphQLObjectType, GraphQLSchema, GraphQLString, } = require("graphql") const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: "Query", fields: () => ({ hello: { type: GraphQLString, resolve: () => "Hello from GraphQL Helix on Serverless!", } }), }), }) const app = express() app.use(express.json()) app.use("/graphql", async (req, res) => { const request = { body: req.body, headers: req.headers, method: req.method, query: req.query, } if (shouldRenderGraphiQL(request)) { res.send(renderGraphiQL()) } else { const { operationName, query, variables } = getGraphQLParameters(request) const result = await processRequest({ operationName, query, variables, request, schema, }) if (result.type === "RESPONSE") { result.headers.forEach(( { name, value } ) => res.setHeader(name, value)) res.status(result.status) res.json(result.payload) } } }) const handler = serverless(app) module.exports.start = async (event, context) => { const result = await handler(event, context) return result } 
Enter fullscreen mode Exit fullscreen mode

serverless.yml

The resources and functions are defined in a file called serverless.yml which includes:

  • The provider for the Node runtime and AWS region
  • The handler and events for your functions.
# serverless.yml service: ajcwebdev-graphql-helix-express frameworkVersion: '2' provider: name: aws stage: dev runtime: nodejs14.x versionFunctions: false lambdaHashingVersion: 20201221 httpApi: cors: allowedOrigins: - '*' allowedMethods: - GET - POST - HEAD allowedHeaders: - Accept - Authorization - Content-Type functions: endpoint: handler: index.start events: - httpApi: path: '*' method: '*' 
Enter fullscreen mode Exit fullscreen mode

The handler is named index.start because it is formatted as <FILENAME>.<HANDLER>.

Upload to AWS with sls deploy

Once the project is defined in code it can be deployed with the sls deploy command. This command creates a CloudFormation stack defining any necessary resources such as API gateways or S3 buckets.

sls deploy --verbose 
Enter fullscreen mode Exit fullscreen mode
service: ajcwebdev-graphql-helix-express stage: dev region: us-east-1 stack: ajcwebdev-graphql-helix-express-dev resources: 10 api keys: None endpoints: ANY - https://cuml5hnx0b.execute-api.us-east-1.amazonaws.com functions: endpoint: ajcwebdev-graphql-helix-express-dev-endpoint layers: None 
Enter fullscreen mode Exit fullscreen mode

Run test queries on GraphQL Helix Serverless

Open cuml5hnx0b.execute-api.us-east-1.amazonaws.com/graphql and send a hello query.

query HELLO_QUERY { hello } 
Enter fullscreen mode Exit fullscreen mode

02-graphql-helix-serverless-framework

curl --request POST \ --url https://cuml5hnx0b.execute-api.us-east-1.amazonaws.com/graphql \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}' 
Enter fullscreen mode Exit fullscreen mode

GraphQL Helix Serverless Final Project Structure

├── .gitignore ├── index.js ├── package.json ├── serverless.yml └── yarn.lock 
Enter fullscreen mode Exit fullscreen mode

Deploy GraphQL Helix with Amplify

AWS Amplify is a set of tools and services to help frontend web and mobile developers build fullstack applications with AWS infrastructure. It includes a CLI for creating and deploying CloudFormation stacks along with a Console and Admin UI for managing frontend web apps, backend environments, CI/CD, and user data.

mkdir graphql-helix-amplify cd graphql-helix-amplify amplify init 
Enter fullscreen mode Exit fullscreen mode

The amplify init command creates a boilerplate project that is setup for generating CloudFormation templates.

? Enter a name for the project ajcwebdevhelix The following configuration will be applied: Project information | Name: ajcwebdevhelix | Environment: dev | Default editor: Visual Studio Code | App type: javascript | Javascript framework: none | Source Directory Path: src | Distribution Directory Path: dist | Build Command: npm run-script build | Start Command: npm run-script start ? Initialize the project with the above configuration? Yes Using default provider awscloudformation ? Select the authentication method you want to use: AWS profile For more information on AWS Profiles, see: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html ? Please choose the profile you want to use default 
Enter fullscreen mode Exit fullscreen mode

Create backend with amplify add api

amplify add api configures a Lambda handler and API gateway to serve the function.

amplify add api 
Enter fullscreen mode Exit fullscreen mode
? Please select from one of the below mentioned services: REST ? Provide a friendly name for your resource to be used as a label for this category in the project: helixresource ? Provide a path (e.g., /items): /graphql ? Choose a Lambda source: Create a new Lambda function ? Provide the AWS Lambda function name: helixfunction ? Choose the function runtime that you want to use: NodeJS ? Choose the function template that you want to use: Hello World ? Do you want to access other resources created in this project from your Lambda function? N ? Do you want to edit the local lambda function now? N ? Restrict API access: N ? Do you want to add another path? N 
Enter fullscreen mode Exit fullscreen mode
cd amplify/backend/function/helixfunction/src yarn add graphql-helix graphql express serverless-http cd ../../../../../ 
Enter fullscreen mode Exit fullscreen mode

index.js

// amplify/backend/function/helixfunction/src/index.js const serverless = require('serverless-http') const express = require("express") const { getGraphQLParameters, processRequest, renderGraphiQL, shouldRenderGraphiQL, } = require("graphql-helix") const { GraphQLObjectType, GraphQLSchema, GraphQLString, } = require("graphql") const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: "Query", fields: () => ({ hello: { type: GraphQLString, resolve: () => "Hello from GraphQL Helix on Amplify!", } }), }), }) const app = express() app.use(express.json()) app.use("/graphql", async (req, res) => { const request = { body: req.body, headers: req.headers, method: req.method, query: req.query, } if (shouldRenderGraphiQL(request)) { res.send(renderGraphiQL()) } else { const { operationName, query, variables } = getGraphQLParameters(request) const result = await processRequest({ operationName, query, variables, request, schema, }) if (result.type === "RESPONSE") { result.headers.forEach(( { name, value } ) => res.setHeader(name, value)) res.status(result.status) res.json(result.payload) } } }) module.exports.handler = serverless(app) 
Enter fullscreen mode Exit fullscreen mode

Upload to AWS with amplify push

amplify push uploads the stack templates to an S3 bucket and calls the CloudFormation API to create or update resources in the cloud.

amplify push 
Enter fullscreen mode Exit fullscreen mode
✔ Successfully pulled backend environment dev from the cloud. Current Environment: dev ┌──────────┬───────────────┬───────────┬───────────────────┐ │ Category │ Resource name │ Operation │ Provider plugin │ ├──────────┼───────────────┼───────────┼───────────────────┤ │ Function │ helixfunction │ Create │ awscloudformation │ ├──────────┼───────────────┼───────────┼───────────────────┤ │ Api │ helixresource │ Create │ awscloudformation │ └──────────┴───────────────┴───────────┴───────────────────┘ ? Are you sure you want to continue? Yes REST API endpoint: https://acj63jadzb.execute-api.us-west-1.amazonaws.com/dev 
Enter fullscreen mode Exit fullscreen mode

Run test queries on GraphQL Helix Amplify

Open acj63jadzb.execute-api.us-west-1.amazonaws.com/dev/graphql and send a hello query.

query HELLO_QUERY { hello } 
Enter fullscreen mode Exit fullscreen mode

03-graphql-helix-amplify

curl --request POST \ --url https://acj63jadzb.execute-api.us-west-1.amazonaws.com/dev/graphql \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}' 
Enter fullscreen mode Exit fullscreen mode

GraphQL Helix Amplify Final Project Structure

├── .gitignore └── amplify └── backend ├── api │ └── helixresource │ ├── api-params.json │ ├── helixresource-cloudformation-template.json │ └── parameters.json └── function └── helixfunction ├── function-parameters.json ├── helixfunction-cloudformation-template.json └── src ├── event.json ├── index.js ├── package.json └── yarn.lock 
Enter fullscreen mode Exit fullscreen mode

Deploy GraphQL Helix with Docker and Fly

Fly is a platform for full stack applications and databases that need to run globally. Fly executes your code close to users and scales compute in cities where your app is busiest. You can run arbitrary Docker containers and host popular databases like Postgres.

mkdir graphql-helix-docker cd graphql-helix-docker npm init -y npm i express graphql-helix graphql touch index.js Dockerfile .dockerignore docker-compose.yml echo 'node_modules\n.DS_Store' > .gitignore 
Enter fullscreen mode Exit fullscreen mode

index.js

// index.js const express = require("express") const { getGraphQLParameters, processRequest, renderGraphiQL, shouldRenderGraphiQL, } = require("graphql-helix") const { GraphQLObjectType, GraphQLSchema, GraphQLString, } = require("graphql") const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: "Query", fields: () => ({ hello: { type: GraphQLString, resolve: () => "Hello from GraphQL Helix on Docker!", } }), }), }) const app = express() app.use(express.json()) app.use("/graphql", async (req, res) => { const request = { body: req.body, headers: req.headers, method: req.method, query: req.query, } if (shouldRenderGraphiQL(request)) { res.send(renderGraphiQL()) } else { const { operationName, query, variables } = getGraphQLParameters(request) const result = await processRequest({ operationName, query, variables, request, schema, }) if (result.type === "RESPONSE") { result.headers.forEach(( { name, value } ) => res.setHeader(name, value)) res.status(result.status) res.json(result.payload) } } }) const port = process.env.PORT || 8080 app.listen(port, () => { console.log(`GraphQL server is running on port ${port}.`) }) 
Enter fullscreen mode Exit fullscreen mode

Dockerfile

Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image.

FROM node:14-alpine LABEL org.opencontainers.image.source https://github.com/ajcwebdev/graphql-helix-docker WORKDIR /usr/src/app COPY package*.json ./ RUN npm i COPY . ./ EXPOSE 8080 CMD [ "node", "index.js" ] 
Enter fullscreen mode Exit fullscreen mode

For a more in depth explanation of these commands, see my previous article, A First Look at Docker.

.dockerignore

Before the docker CLI sends the context to the docker daemon, it looks for a file named .dockerignore in the root directory of the context.

node_modules Dockerfile .dockerignore .git .gitignore npm-debug.log 
Enter fullscreen mode Exit fullscreen mode

If this file exists, the CLI modifies the context to exclude files and directories that match patterns in it. This helps avoid sending large or sensitive files and directories to the daemon.

docker-compose.yml

Compose is a tool for defining and running multi-container Docker applications. After configuring your application’s services with a YAML file, you can create and start all your services with a single command.

version: "3.9" services: web: build: . ports: - "49160:8080" 
Enter fullscreen mode Exit fullscreen mode

Run test queries on GraphQL Helix Docker

The docker compose up command aggregates the output of each container. It builds, (re)creates, starts, and attaches to containers for a service.

docker compose up 
Enter fullscreen mode Exit fullscreen mode
Attaching to web_1 web_1 | GraphQL server is running on port 8080. 
Enter fullscreen mode Exit fullscreen mode

To test your app, get the port of your app that Docker mapped:

docker ps 
Enter fullscreen mode Exit fullscreen mode

Docker mapped the 8080 port inside of the container to the port 49160 on your machine.

CONTAINER ID 50935f5f4ae6 IMAGE graphql-helix-docker_web COMMAND "docker-entrypoint.s…" CREATED 47 seconds ago STATUS Up 46 seconds PORTS 0.0.0.0:49160->8080/tcp, :::49160->8080/tcp NAMES graphql-helix-docker_web_1 
Enter fullscreen mode Exit fullscreen mode

Open localhost:49160/graphql and send a hello query.

query HELLO_QUERY { hello } 
Enter fullscreen mode Exit fullscreen mode

04-graphql-helix-docker

curl --request POST \ --url http://localhost:49160/graphql \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}' 
Enter fullscreen mode Exit fullscreen mode

Launch app on Fly with fly launch

Run fly launch in the directory with your source code to configure your app for deployment.

fly launch \ --name graphql-helix-docker \ --region sjc 
Enter fullscreen mode Exit fullscreen mode

This will create and configure a fly app by inspecting your source code and prompting you to deploy.

Creating app in /Users/ajcwebdev/graphql-helix-docker Scanning source code Detected Dockerfile app Automatically selected personal organization: Anthony Campolo Created app graphql-helix-docker in organization personal Wrote config file fly.toml Your app is ready. Deploy with `flyctl deploy` ? Would you like to deploy now? No 
Enter fullscreen mode Exit fullscreen mode

Open fly.toml and add the following PORT number under env.

[env] PORT = 8080 
Enter fullscreen mode Exit fullscreen mode

Deploy application with fly deploy

fly deploy 
Enter fullscreen mode Exit fullscreen mode
Image: registry.fly.io/graphql-helix-docker:deployment-1631689218 Image size: 124 MB ==> Creating release Release v2 created You can detach the terminal anytime without stopping the deployment Monitoring Deployment 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing] --> v0 deployed successfully 
Enter fullscreen mode Exit fullscreen mode

Check the application's status with fly status.

fly status 
Enter fullscreen mode Exit fullscreen mode
App Name = graphql-helix-docker Owner = personal Version = 0 Status = running Hostname = graphql-helix-docker.fly.dev Deployment Status ID = 47cb82b9-aaf1-5ee8-df1b-b4f10e389f16 Version = v0 Status = successful Description = Deployment completed successfully Instances = 1 desired, 1 placed, 1 healthy, 0 unhealthy Instances ID TASK VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED a8d02b87 app 0 sjc run running 1 total, 1 passing 0 4m28s ago 
Enter fullscreen mode Exit fullscreen mode

Run test queries on GraphQL Helix Docker Fly

Open graphql-helix-docker.fly.dev/graphql and send a hello query.

query HELLO_QUERY { hello } 
Enter fullscreen mode Exit fullscreen mode

05-graphql-helix-docker-fly

curl --request POST \ --url https://graphql-helix-docker.fly.dev/graphql \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}' 
Enter fullscreen mode Exit fullscreen mode

GraphQL Helix Docker Final Project Structure

├── .dockerignore ├── .gitignore ├── docker-compose.yml ├── Dockerfile ├── fly.toml ├── index.js └── package.json 
Enter fullscreen mode Exit fullscreen mode

Resources

Building a GraphQL server with GraphQL Helix provides a comprehensive description of GraphQL Helix's implementation. The examples folder in the graphql-helix repo also includes example applications such as:

Top comments (0)