DEV Community

loading...
Microsoft Azure

GraphQL on Azure: Part 6 - Subscriptions With SignalR

aaronpowell profile image Aaron Powell Originally published at aaron-powell.com on ・5 min read

In our exploration of how to run GraphQL on Azure, we’ve looked at the two most common aspects of a GraphQL server, queries and mutations, so we can get data and store data. Today, we’re going to look at the third piece of the puzzle, subscriptions.

What are GraphQL Subscriptions

In GraphQL, a Subscription is used as a way to provide real-time data to connected clients. Most commonly, this is implemented over a WebSocket connection, but I’m sure you could do it with long polling or Server Sent Events if you really wanted to (I’ve not gone looking for that!). This allows the GraphQL server to broadcast query responses out when an event happens that the client is subscribed to.

Let’s think about this in the context of the quiz game we’ve been doing. So far the game is modeled for single player, but if we wanted to add multiplayer, we could have the game wait for all players to join, and once they have, broadcast out a message via a subscription that the game is starting.

Defining Subscriptions

Like queries and mutations, subscriptions are defined as part of a GraphQL schema, and they can reuse the types that are available within our schema. Let’s make a really basic schema that contains a subscription:

type Query {
 hello: String!
}

type Subscription {
 getMessage: String!
}

schema {
 query: Query
 subscription: Subscription
}

Enter fullscreen mode Exit fullscreen mode

The subscription type that we’re defining can have as many different subscriptions that clients can subscribe via, and each might return different data, it’s completely up to the way your server wants to expose real-time information.

Implementing Subscriptions on Azure

For this implementation, we’re going to go back to TypeScript and use Apollo. Apollo have some really great docs on how to implement subscriptions in an Apollo Server, and that’ll be our starting point.

But before we can start pushing messages around, we need to work out what is going to be the messaging backbone of our server. We’re going to need some way in which the server and communicate with all connected clients, either from within a resolver, or from some external event that the server receives.

In Azure, when you want to do real-time communications, there’s no better service to use than SignalR Service. SignalR Service takes care of the protocol selection, connection management and scaling that you would require for a real-time application, so it’s ideal for our needs.

Creating the GraphQL server

In the previous posts, we’ve mostly talked about running GraphQL in a serverless model on Azure Functions, but for a server with subscriptions, we’re going to use Azure App Service, and we can’t expose a WebSocket connection from Azure Functions for the clients to connect to.

Apollo provides plenty of middleware options that we can chose from, so for this we’ll use the Express integration, apollo-server-express and follow the subscriptions setup guide.

Adding Subscriptions with SignalR

When it comes to implementing the integration with SignalR, Apollo uses the graphql-subscriptions PubSubEngine class to handle how the broadcasting of messages, and connections from clients.

So that means we’re going to need an implementation of that which uses SignalR, and thankfully there is one, @aaronpowell/graphql-signalr-subscriptions (yes, I did write it 😝).

We’ll start by adding that to our project:

npm install --save @aaronpowell/graphql-signalr-subscriptions

Enter fullscreen mode Exit fullscreen mode

You’ll need to create a SignalR Service resource and get the connection string for it (I use dotenv to inject it for local dev) so you can create PubSub engine. Create a new resolvers.ts file and create the SignalRPubSub instance in it.

import { SignalRPubSub } from "@aaronpowell/graphql-signalr-subscriptions";

export const signalrPubSub = new SignalRPubSub(
    process.env.SIGNALR_CONNECTION_STRING
);
Enter fullscreen mode Exit fullscreen mode

We export this so that we can import it in our index.ts and start the client when the server starts:

// setup ApolloServer
httpServer.listen({ port }, () => {
    console.log(
        `🚀 Server ready at http://localhost:${port}${server.graphqlPath}`
    );
    console.log(
        `🚀 Subscriptions ready at ws://localhost:${port}${server.subscriptionsPath}`
    );

    signalrPubSub
        .start()
        .then(() => console.log("🚀 SignalR up and running"))
        .catch((err: any) => console.error(err));
});
Enter fullscreen mode Exit fullscreen mode

It’s important to note that you must call start() on the instance of the PubSub engine, as this establishes the connection with SignalR, and until that happens you won’t be able to send messages.

Communicating with a Subscription

Let’s use the simple schema from above:

type Query {
    hello: String!
}

type Subscription {
    getMessage: String!
}

schema {
    query: Query
    subscription: Subscription
}
Enter fullscreen mode Exit fullscreen mode

In the hello query we'll broadcast a message, which the getMessage can subscribe to. Let's start with the hello resolver:

export const resolvers = {
    Query: {
        hello() {
            signalrPubSub.publish("MESSAGE", {
                getMessage: "Hello I'm a message"
            });
            return "Some message";
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

So our hello resolver is going to publish a message with the name MESSAGE and a payload of { getMessage: "..." } to clients. The name is important as it's what the subscription resolvers will be configured to listen for and the payload represents all the possible fields that someone could select in the subscription.

Now we'll add the resolver for the subscription:

export const resolvers = {
    Query: {
        hello() {
            signalrPubSub.publish("MESSAGE", {
                getMessage: "Hello I'm a message"
            });
            return "Some message";
        }
    },
    Subscription: {
        getMessage: {
            subscribe: () => signalrPubSub.asyncIterator(["MESSAGE"])
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

A resolver for a subscription is a little different to query/mutation/field resolvers as you need to provide a subscribe method, which is what Apollo will invoke to get back the names of the triggers to be listening on. We're only listening for MESSAGE here (but also only broadcasting it), but if you added another publish operation with a name of MESSAGE2, then getMessage subscribers wouldn't receive that. Alternatively, getMessage could be listening to a several trigger names, as it might represent an aggregate view of system events.

Conclusion

In this post we've been introduced to subscriptions in GraphQL and seen how we can use the Azure SignalR Service as the backend to provide this functionality.

You'll find the code for the SignalR implementation of subscriptions here and the full example here.

Discussion (0)

pic
Editor guide