GraphQL subscriptions with Rails and React

Ricardo Trindade
3 min readApr 5, 2021

I’ve been using GraphQL for a year now at work and outside of it (check my other story where I built a simple API in Elixir using Animal Crossing the theme).

Source code for both backend and frontend applications can be found at https://github.com/RicardoTrindade/CableQL and https://github.com/RicardoTrindade/Subscriptions-React respectively.

The idea for this story came from the issue that we were facing with background jobs being scheduled by GraphQL mutations, and how could we let the user know that the job has finished. There are a couple of ways to do this but I thought to give the Subscriptions a try and learn something along the way.

GraphQL subscriptions allow clients to observe events and receive updates from the server whenever those events occur, I find it similar to Elixir’s LiveView. I had never used them before as I’ve only tried queries and mutations, which seem to be the most used features for this technology.

For this I built two simple applications, a Rails API application acting as the backend and a separate React app for the frontend.

The frontend application can invoke a mutation on the backend that will schedule a long running job (creating users in this case). Whenever a user is created, the backend sends an event via Subscriptions that gets caught by the frontend which then populates a list with the newly created ids.

The React application is built in Typescript and it’s using codegen, a library that reads a GraphQL schema and automatically generates the types for you in Typescript. It’s quite a powerful combo for developing frontend applications. Check out the generated types in api.ts. For handling queries/mutations/subscriptions we will be using Apollo which is the standard lib. For transport and websocket management we decided to use graphql-ruby-client and @rails/actioncable.

The backend will be using the GraphQL gem and Action Cable to handle the subscriptions. Both gems have a very good guides on how to set everything up and highly recommend reading them throughly. The mutation that triggers a background job (managed by Sidekiq), creates 10 users in the database (each separated by a 3 second sleep to mock some long running process). After each user is created we send a user_created event via subscriptions. (N.B that user_created matches the name declared on SubscriptionRoot)

sleep(3.seconds)
user = User.create(email: "email+#{i}@mail.com")
ApiSchema.subscriptions.trigger(:user_created, {}, { user: user })

The User type in GraphQL is really simple and almost matches the table schema in the database.

type User {
"""
email
"""
email: String!
"""
id
"""
id: Int!
}
type SubscriptionRoot {
userCreated: UserCreatedPayload!
}
type UserCreatedPayload {
user: User
}
# frozen_string_literal: trueclass Subscriptions::UserCreated < GraphQL::Schema::Subscriptionfield :user, Types::User, null: truedef subscribe(**args)
# super will return no_object since we don't have any user to return on the subscribe.
super
end
def update
# super will return the user, but you can perform any modifications if needed.
super
end
end
Simple diagram of the whole communication process

Some gotchas from the React application. The guides recommend that you split the HttpLink, one for queries and mutations and another for subscriptions as the transport method is different.

const cable = createConsumer('ws://localhost:3000/cable')const httpLink = new HttpLink({uri:'http://localhost:3000/graphql'})const link = split(hasSubscriptionOperation,new ActionCableLink({ cable }),httpLink);

And the bit below to handle incoming subscription data.

# Hook generated by codegen
const
{ data, loading, error } = useUserCreatedSubscription({
onSubscriptionData: ({ subscriptionData }: OnSubscriptionDataOptions<UserCreatedSubscription>) => {// Handle incoming data}}})
UI application displaying the parsed data from Subscription Data

Hope you enjoyed these demo applications showing how you can use Subscriptions on both backend and frontend. I’ll be making some improvements to the code for readability and maybe add some tests but the core functionality will remain the same. Let me know what you think of it and if you have any questions feel free to reach out to me.

--

--

Ricardo Trindade

Fullstack engineer at Marley Spoon. Keen interest in machine learning, Ruby and Kotlin