Migrate your Redwood GraphQL API with GraphQL-Helix and Envelop

Note: For the official migration guide please see Using GraphQL Envelop+Helix in Redwood v0.35+. We ask that you direct any comments there. We would love your feedback! :grinning_face_with_smiling_eyes:

Back in January the Redwood team decided to modify the internals of Redwood to allow users to specify their own client instead of using Apollo Client. Within weeks @marceloalves created a new package for a React Query Provider and @Tobbe showed how you could Switch to another GraphQL Client with graphql-hooks.

But what if you wanted to use your GraphQL server of choice?

Over the last two months Dotan Simha from The Guild along with assistance from certified Redwood Whisperer @dthyresson have been working on similar modifications which will allow users to migrate away from Apollo Server to a different GraphQL server.

Hi, people of the Redwood! :slight_smile:

I created an initial PR for migrating from apollo-server-lambda to Envelop and GraphQL-Helix. The goal of this PR is to normalize the incoming HTTP requests and try to handle them in a generic way. Also, since the request is detached from the handler, we can use any GraphQL library for execution.

While graphql-helix provides the basic pipeline and the initial request normalization, envelop provides the connection to the GraphQL execution, and allow to enrich the entire GraphQL execution pipeline with custom code (custom context building, parser cache, validation cache, tracing, metrics collection and more).

Dotan Simha - Partial normalization of Lambda request (April 25, 2021)

The initial PR, Partial normalization of Lambda request for migration to Envelop, laid the foundation for using GraphQL-Helix and Envelop.

  • GraphQL Helix is a framework and runtime agnostic collection of utility functions for building your own GraphQL HTTP server.
  • Envelop is a lightweight library allowing developers to easily develop, share, collaborate and extend their GraphQL execution layer. Envelop is the missing GraphQL plugin system.

Earlier this week I released GraphQL Helix, a new JavaScript library that lets you take charge of your GraphQL server implementation.

There’s a couple of factors that pushed me to roll my own GraphQL server library:

  • I wanted to use bleeding-edge GraphQL features like @defer, @stream and @live directives.
  • I wanted to make sure I wasn’t tied down to a specific framework or runtime environment.
  • I wanted control over how server features like persisted queries were implemented.
  • I wanted to use something other than WebSocket (i.e. SSE) for subscriptions.

Unfortunately, popular solutions like Apollo Server, express-graphql and Mercurius fell short in one or more of these regards, so here we are.

Daniel Rearden - Building a GraphQL server with GraphQL Helix (November 5, 2020)

Create Redwood App

The code for this project can be found on my GitHub.

yarn create redwood-app redwood-envelop
cd redwood-envelop

Open schema.prisma in api/db and add the following schema.

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider      = "prisma-client-js"
  binaryTargets = "native"
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  body      String
  createdAt DateTime @default(now())
}

Provision a PostgreSQL database with Railway

First you need to create a Railway account and install the Railway CLI.

railway login
railway init
railway add

Add a PostgreSQL plugin to your Railway project and then set the DATABASE_URL inside your .env file.

echo DATABASE_URL=`railway variables get DATABASE_URL` > .env

Setup database with prisma migrate dev and generate scaffold

Running yarn rw prisma migrate dev generates the folders and files necessary to create a new migration. We will name our migration posts-table.

yarn rw prisma migrate dev --name posts-table
yarn rw g scaffold post
yarn rw dev

Open http://localhost:8910/posts to create a couple blog posts.

Configure project to use Envelop

Add useEnvelop=true to the [experimental] section in your redwood.toml config. This lets the dev-server know how to handle the response.

[web]
  port = 8910
  apiProxyPath = "/.redwood/functions"
[api]
  port = 8911
[browser]
  open = true
[experimental]
  esbuild = false
  useEnvelop = true

Add @redwoodjs/graphql-server to api dependencies

yarn workspace api add @redwoodjs/graphql-server

Define logger in /api/src/lib/logger.js and update import to graphql-server package

// api/src/lib/logger.js

import { createLogger } from '@redwoodjs/graphql-server/logger'

export const logger = createLogger({
  options: { level: 'info', prettyPrint: true },
})

Add graphql-server package to graphql.js function and loggerConfig to createGraphQLHandler

// api/src/functions/graphql.js

import {
  createGraphQLHandler,
  makeMergedSchema,
  makeServices,
} from '@redwoodjs/graphql-server'

import schemas from 'src/graphql/**/*.{js,ts}'
import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'
import services from 'src/services/**/*.{js,ts}'

export const handler = createGraphQLHandler({
  loggerConfig: {
    logger,
    options: {
      operationName: true,
      tracing: true
    }
  },
  schema: makeMergedSchema({
    schemas,
    services: makeServices({ services }),
  }),
  onException: () => {
    db.$disconnect()
  },
})

Apollo plugins are not currently supported and must be removed. However, there may be equivalent Envelop plugins. These can be added in the createGraphQLHandler configuration options in extraPlugins. extraPlugins accepts an array of plugins.

Currently used plugins

'@envelop/depth-limit'
'@envelop/disable-introspection'
'@envelop/filter-operation-type'
'@envelop/parser-cache'
'@envelop/validation-cache'
'@envelop/use-masked-errors'

Change the imports to use the new graphql-server package if your services raise any errors based on ApolloError such as UserInputError or ValidationError.

import { UserInputError } from '@redwoodjs/graphql-server'

If you have any other @redwoodjs/api imports in your project make sure to change them to @redwoodjs/graphql-server.

Restart development server and send a query

yarn rw dev

Add HomePage and PostsCell

yarn rw g page home /
yarn rw g cell posts
// web/src/components/PostsCell/PostsCell.js

export const QUERY = gql`
  query PostsQuery {
    posts {
      id
      title
      body
      createdAt
    }
  }
`

export const Loading = () => <div>Almost there...</div>
export const Empty = () => <div>WHERE'S THE POSTS?</div>
export const Failure = ({ error }) => <div>{error.message}</div>

export const Success = ({ posts }) => {
  return posts.map((post) => (
    <article key={post.id}>
      <header>
        <h2>{post.title}</h2>
      </header>
      <p>{post.body}</p>
      <div>{post.createdAt}</div>
    </article>
  ))
}
// web/src/pages/HomePage/HomePage.js

import PostsCell from 'src/components/PostsCell'

const HomePage = () => {
  return (
    <>
      <h1>Redwood+Envelop</h1>
      <PostsCell />
    </>
  )
}

export default HomePage

Setup Netlify Deploy

Generate the configuration file needed for deploying to Netlify with the following setup command.

yarn rw setup deploy netlify

Push Project to GitHub

Create a blank repository at repo.new and push the project to your GitHub.

git init
git add .
git commit -m "the guilded age of redwood"
git remote add origin https://github.com/ajcwebdev/redwood-envelop.git
git push -u origin main

Connect Repo to Netlify

Go to Netlify and connect the repo. Include the DATABASE_URL environment variable and add ?connection_limit=1 to the end of the connection string. You can also give your site a custom domain such as redwood-envelop.

Open redwood-envelop.netlify.app

Test the API with a query

query getPosts {
  posts {
    id
    title
    body
    createdAt
  }
}

Is Redwood still Redwood without Apollo?

RedwoodJS was originally architected around Apollo Client on the web side and Apollo Server on the api side. These two libraries were fundamental to the development of not only Redwood but the entire GraphQL ecosystem. Apollo itself was born from the ashes of the Meteor Development Group.

Meteor pursued a similar philosophy of fullstack JavaScript now employed by Redwood. By bringing the decoupled Apollo pieces of client and server together into a single fullstack application it felt like the original dream of Meteor was finally coming to fruition. But GraphQL is itself about decoupling the frontend from the backend so that one side is never too heavily tied to the other.

This has allowed Redwood to pursue other GraphQL clients and servers that continue to evolve and improve and engage with the open source developer community. The free market of repositories is alive and well, and we will see many more experiments in the coming future.

2 Likes

Great post @ajcwebdev! Love how you mixed a technical how-to with some history, humor and musings.

Thank you @Tobbe, that’s very kind and captures exactly the balance I always attempt to strike in my writing. Peter once used the term Redwood Bard which was an honorific I quite liked:

In Celtic cultures, a bard is a professional story teller, verse-maker, music composer, oral historian and genealogist, employed by a patron (such as a monarch or noble) to commemorate one or more of the patron’s ancestors and to praise the patron’s own activities.

The useMaskedErrors plugin is also installed.

This plugin prevents any non GraphQL or other expected errors from leaking potentially sensitive info. For example, let’s say you had a Prisma connection errors. The error message might have alluded to a connection string or part of the query you made (like model.findMany), but with masked errors, a generic Error with message is returned nice and safe.

1 Like

Thanks DT, just updated the post!