Go GraphQL: Adding Realtime GraphQL Subscriptions in Golang #7

In this post, youll learn how to add realtime functionality into your app with GraphQL subscriptions.



In this section, you’ll learn how you can bring realtime functionality into your app by implementing GraphQL subscriptions.

In this post we will learn:
  • How to implement GraphQL Subscriptions in golang server for realtime updates
  • Send realtime updates to subscribed clients when a new Human character is created
  • Send realtime updates to subscribed clients

So lets get started !
. . .

What are GraphQL subscriptions?

Subscriptions are a GraphQL feature that allows a server to send data to its clients when a specific event happens. Subscriptions are usually implemented with WebSockets.

In that setup, the server maintains a steady connection to its subscribed client. This also breaks the “Request-Response-Cycle” that was used for all previous interactions with the API.

Instead, the client initially opens up a long-lived connection to the server by sending a subscription query that specifies which event it is interested in.

Every time this particular event happens, the server uses the connection to push the event data to the subscribed client(s).
. . .

Adding Subscriptions Handler

So we need to made changes in our GraphQL server to accept the WebSocket connection, we will update the existing GraphQL server and add one more path to accept the WebSocket connection

Before we start we need to install the dependencies, run following command to add the dependencies to your project
glide get github.com/gorilla/websocket

we will be using the gorilla WebSocket package to implement the realtime functionality in our GraphQL application.

So we have our dependencies in place lets start will the implementation of the WebSocket handler

#1
func Handler(w http.ResponseWriter, r *http.Request) {
conn, err := Upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("failed to do websocket upgrade: %v", err)
return
}
connectionACK, err := json.Marshal(map[string]string{
"type": "connection_ack",
})
if err != nil {
log.Printf("failed to marshal ws connection ack: %v", err)
}
if err := conn.WriteMessage(websocket.TextMessage, connectionACK); err != nil {
log.Printf("failed to write to ws connection: %v", err)
return
}

#2
go func() {
for {
_, p, err := conn.ReadMessage()
if websocket.IsCloseError(err, websocket.CloseGoingAway) {
return
}
if err != nil {
log.Println("failed to read websocket message: %v", err)
return
}
var msg ConnectionACKMessage
if err := json.Unmarshal(p, &msg); err != nil {
log.Printf("failed to unmarshal: %v", err)
return
}
if msg.Type == "start" {
length := 0
Subscribers.Range(func(key, value interface{}) bool {
length++
return true
})
#3
var subscriber = Subscriber{
ID: length + 1,
Conn: conn,
RequestString: msg.Payload.Query,
OperationID: msg.OperationID,
}
#4
Subscribers.Store(subscriber.ID, &subscriber)
}
}
}()
}

Let's walk through the comments to understand the code
  1. The handler that accepts the incoming WebSocket connection request
  2. We have added the function in go-routine that listens for the incoming connections and processes it further
  3. If all ok, then we will create a new object with the incoming connection with appropriate values
  4. Then it is added to the list of active subscribers, which will be notified depending on the GraphQL subscription Query they pass to it.

This subscription handler is wired up with the GraphQL server, with the new route as follows:
// graphql subscriptions
http.HandleFunc("/subscriptions", subscriptions.Handler)

Now we have a route to accept the incoming WebSocket connections.
. . .

Adding GraphQL Subscription Query

Enough with the talking, more of the coding! Let’s implement the subscription that allows your clients to subscribe to newly created Human character.

Just like with queries and mutations, the first step to implement a subscription is to extend your GraphQL schema definition.

Subscription := graphql.NewObject(graphql.ObjectConfig{
Name: "Subscription",
Fields: graphql.Fields{
#1
"newHuman": &graphql.Field{
Type: graphql.NewList(types.HumanType),
#2
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
#3
return resolvers.HumanData, nil
},
},
},
})

Lets walk through the comments to understand the code
  1. We have added a newHuman subscription which will return the list of humans with new humans in it
  2. Resolver function to resolve the subscription
  3. Finally returns the updated list of Humans character.

Let's test the subscriptions are working with the following query, it will subscribe to and changes into the list oh Humans
subscription {
newHuman {
id
name
appearsIn
homePlanet
}
}

When you run the above query you will get the following output on your graphql editor:

Now we have the newPerson subscription reday.

We also need to make changes in our createHuman mutation to publish the changes to all the subscribes, we will be doing following changes to it as follow:

Mutation := graphql.NewObject(graphql.ObjectConfig{
Name: "Mutation",
Fields: graphql.Fields{
#1
"createHuman": &graphql.Field{
Type: types.HumanType,
Description: "Update existing human",
Args: graphql.FieldConfigArgument{
... args
},
#2
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
... more code

char := resolvers.CreatePerson(id, name, appearsin, homePlanet)
#3
HumanPublisher()
return char, nil
},
},
},
})

Let's walk through the comments to understand:
  1. We have added createHuman mutation that creates a new Human character
  2. We have resolver function to resolver the mutation
  3. HumanPulisher is a function that notifies all the subscribes and publish the changes to them. You can look at the HumanPublisher code here.
. . .

Testing Subscriptions

With all the code in place, it’s time to test your realtime API ⚡️ You can do so, by using two instances (i.e. windows) of the GraphQL Playground at once.
If you haven’t already, restart the server by first killing it with CTRL+C, then run go run main.go again.

Next, open two browser windows and navigate both to the endpoint of your GraphQL server: http://localhost:8080.

As you might guess, you’ll use one Playground to send a subscription query and thereby create a permanent WebSocket connection to the server. The second one will be used to send a post mutation that triggers the subscription.
In one Playground, send the following subscription:
subscription { newHuman { id name appearsIn homePlanet } }

In contrast to what happens when sending queries and mutations, you’ll not immediately see the result of the operation. Instead, there’s a loading spinner indicating that it’s waiting for an event to happen.


Time to trigger a subscription event. Open the second playground and end the following post mutation inside a GraphQL Playground.
mutation {
createHuman(
id: 1001
name: "Darth Vader"
appearsIn: [3,4,5]
homePlanet: "tatooni"
) {
id
name
appearsIn
homePlanet
}
}

Now observe the Playground where the subscription was running:


We have received the updated list of human characters as we created a new human character.

So, this brings us to the end of realtime graphql subscriptions into the golang server using the WebSockets. Happy hacking!!
. . .

Snehal Kumar

Jun 24 2019

Write your response...

On a mission to build Next-Gen Community Platform for Developers