---
title: Ice cream with GraphQL subscriptions | Blog | De Voorhoede
description: >-
  Get notified whenever ice cream is dispensed in your favourite flavour using
  real-time GraphQL subscriptions. Read about it in our blog.
language: English
url: 'https://www.voorhoede.nl/en/blog/ice-cream-with-graphql-subscriptions/'
---

Blog

# Ice cream with GraphQL subscriptions

By [Thadee](/en/team/thadee.md)

31 March 2020

* [Getting started](#getting-started)
* [GraphQL schema and resolvers](#graphql-schema-and-resolvers)
* [Run the server](#run-the-server)
* [GraphQL playground](#graphql-playground)
* [Real-time ice cream](#real-time-ice-cream)
* [Event filtering](#event-filtering)

Get notified whenever ice cream is dispensed in your favourite flavour using real-time GraphQL subscriptions

GraphQL is often praised for being a step forward from data retrieval by making calls to REST APIs from your application’s front-end. Sharing a common schema language puts front-end and middle/back-end developers on the same page. As a front-end developer, you can easily fetch just the data you need with a single call, instead of having to call a bunch of endpoints in series to satisfy your query. Much of the data stitching can now be offloaded to an intermediary graphQL server that will call the underlying data provider services, such as REST APIs, databases and microservices, so you don’t have to.

With the introduction of [graphQL subscriptions](https://github.com/graphql/graphql-spec/blob/master/rfcs/Subscriptions.md) in 2017, you can now use graphQL schema language to subscribe to to event streams and provide real-time updates to the client. Using the [Apollo server package](https://github.com/apollographql/apollo-server), you can quickly set up a production-ready graphQL server that supports subscriptions over a websocket. Combine this with a regular express server and witness the sensational power of graphQL subscriptions.

Today we’re building a graphQL api for an ice cream shop. Whenever an ice cream is dispensed, we want to be notified. Also, we want to be able to pass a parameter which specifies the ice flavour we want to be notified about.

## Getting started

Apollo graphQL’s `apollo-server` delivers you a production-ready graphQL server without too much hassle. Apollo server can wrap a web server that you already have, like Express.

```
npm install apollo-server-express express graphql
```

Include the required packages in your main JS file

```
const express = require('express');
const { createServer } = require('http'); 
const { ApolloServer, gql } = require('apollo-server-express');
```

## GraphQL schema and resolvers

Let’s start by writing up our **types**. So far, we need the default required **Query** type and a basic **IceCream** type to perform basic graphQL queries. As we’d like to retrieve all ice cream flavours, as well as a single ice cream by flavour, we’ll add the **iceCream** and **iceCreams** queries.

```
const typeDefs = gql`
    type IceCream {
    id: Int!
    flavour: String!
    description: String!
    }

    type Query {
    iceCream(flavour: String!): IceCream
    iceCreams: [IceCream]
    }
`
```

To gather the data that our client request, which is described by the schema, we’ll write **resolvers**. Resolvers are handler functions for graphQL, which run in response to an incoming request, fetch data from somewhere, and return it to the client in the desired format. Since we don’t have a real back-end to talk to, we’ll have a stub contain our ice cream.

```
// The excellent flavour descriptions courtesy of https://phrasegenerator.com/wine, with modifications.
    const stub = [
      {
        id: 0,
        flavour: 'vanilla',
        description: 'A flippant pepper bouquet and alcoholic garlic essences are blended in'
      },{
        id: 1,
        flavour: 'strawberry',
        description: 'Blends indigestible parsnip flavors with a sandy cool ranch flavor'
      },{
        id: 2,
        flavour: 'pear',
        description: 'A soporiphic coconut finish and enticing Bar-B-Q midtones are intertwined'
      }
    ]
    
    const resolvers = {
      Query: {
        iceCreams: () => {
          return Promise.resolve(stub)
        },
        iceCream: (_, { flavour }) => {
          return Promise.resolve(stub.find(({ flavour:stubFlavour }) => flavour === stubFlavour))
        }
      }
    };
```

Querying for **iceCreams** will return the entire dataset, which contains three items. If you query for **iceCream**, you’re expected to pass the flavour you need as a string. The **flavour** argument is then used to look up an ice cream of which the **flavour** property (destructured to **stubFlavour** here) is equal to the **flavour** argument value.

## Run the server

Create an instance of `ApolloServer` and pass it the **typeDefs** and **resolvers** that were created earlier as an options object.

```
const server = new ApolloServer({
  typeDefs,
  resolvers
})
```

Next, make a regular express app and call the Apollo server method **applyMiddleware** to hook the Apollo server in to it.

```
const app = express()

server.applyMiddleware({ app })
```

Pass the express app to node’s **http.createServer** and call **listen** on it to start the server. The server will listen on http\://localhost:3000

```
const httpServer = createServer(app)

const port = 3000

httpServer.listen(port, () => {
  console.log(`Server ready at http://localhost:${port}${server.graphqlPath}`)
})
```

We’re using an express app here because we want to add some endpoints of our own at a later stage.

## GraphQL playground

As soon as you have your server running, you can access a graphQL playground at its default url http\://localhost:3000/graphql

Lets a query for a vanilla ice cream

```
query {
  iceCream(flavour: "vanilla") {
    flavour
    description
  }
}
```

This returns ice cream data with the properties we asked for

```
{
  "data": {
    "iceCream": {
      "description": "A flippant pepper bouquet and alcoholic garlic essences are blended in",
      "flavour": "vanilla"
    }
  }
}
```

You can also retrieve a list of all available ice cream with the `iceCreams` query

```
query {
  iceCreams {
    flavour
    description
  }
}
```

## Real-time ice cream

So far, we’re able to get ice cream data if we ask for it explicitly, which is very nice. However, we wanted real time updates each time ice cream is dispensed, so what’s up with that?

To be able to handle subscriptions, we need a component that is able to subscribe to and publish events. The `graphql-subscriptions` package provides this functionality

```
npm install graphql-subscriptions
```

```
const express = require('express');
const { createServer } = require('http'); 
const { ApolloServer, gql, withFilter } = require('apollo-server-express');
const { PubSub } = require('graphql-subscriptions');

const pubsub = new PubSub();
```

Let’s change the **typeDefs** variable and add a **Subscription** definition for an **iceCreamDispensed** event

```
const typeDefs = gql`
type IceCream {
  id: Int!
  flavour: String!
  description: String!
  owner: String!
}
type Query {
  iceCream(flavour: String!): IceCream
  iceCreams: [IceCream]
}
type Subscription {
  iceCreamDispensed(flavour: String): IceCream
}
`
```

We’ll also need to add a **resolver** to handle subscriptions to the **iceCreamDispensed** event.

```
// Event name to listen to
const ICE_CREAM_DISPENSED = 'ICE_CREAM_DISPENSED';

const resolvers = {
  Subscription: {
    iceCreamDispensed: {
      subscribe: () => pubsub.asyncIterator([ ICE_CREAM_DISPENSED ]),
    }
  },
  // ... remaining part of resolvers stays the same 
  Query: {
```

And finally, we’ll attach the capability to accept websocket connections to our http server. call **installSubscriptionHandlers** passing the plain **httpServer**

```
server.applyMiddleware({ app })

const httpServer = createServer(app)

server.installSubscriptionHandlers(httpServer)

const port = 3000

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

With this defined and the server running, we can use the **subscription** type in our graphQL playground

```
subscription {
  iceCreamDispensed {
    flavour
    description
  }
}
```

Run this and you’ll be subscribed to the event, but because `ICE_CREAM_DISPENSED` is not being fired anywhere, you won’t see any data coming in.

We’ll leverage the express app that our graphQL server is mounted on to expose an endpoint that can be POSTed to to trigger the dispensing of a random ice cream flavour.

Inside the **/dispatch** handler, a random ice cream flavour is selected and picked from the stub data. Then we call **pubsub.publish** with an event name of `ICE_CREAM_DISPENSED` and our randomly selected ice cream. The subscription resolver that is listening for this event, will react by emitting the data to the subscribers.

```
const app = express()

app.post('/dispatch', (req, res) => {
  const flavours = ['vanilla','pear','strawberry']
  const flavour = flavours[Math.round(Math.random() * (flavours.length - 1))]
  const ice = stub.find(item => item.flavour === flavour)
  pubsub.publish(ICE_CREAM_DISPENSED, { iceCreamDispensed: ice } )
  return res.status(202).send('accepted')
})

server.applyMiddleware({ app })
```

Re-start the server and send an http request to the **/dispatch** endpoint

```
curl -X POST http://localhost:3000/dispatch
```

When you are subscribed, you should see data coming in on the right in the playground.

```
{
  "data": {
    "iceCreamDispensed": {
      "flavour": "strawberry",
      "description": "Blends indigestible parsnip flavors with a sandy cool ranch flavor"
    }
  }
}
```

## Event filtering

Nice, but not quite what we wanted. Now we’re subscribed to all flavours. But what if we were only interested in events pertaining to a particular flavour? Our **iceCreamDispensed** resolver is not yet doing anything with the **flavour** argument that we pass to it. But it should. How? We can use the `withFilter` function provided by the `apollo-server-express` package.

`withFilter` accepts a function returning an **AsyncIterator** and a filter function, which should return a boolean or a **Promise** of a boolean. The filter function decides whether or not the event will be emitted to a subscriber, and has access to the arguments that were passed with the subscription.

First, import `withFilter`

```
const express = require('express');
const { createServer } = require('http'); 
const { ApolloServer, gql, withFilter } = require('apollo-server-express');
const { PubSub } = require('graphql-subscriptions');
```

Change the `iceCreamDispensed` resolver to use `withFilter`

```
const resolvers = {
  Subscription: {
    iceCreamDispensed: {
      subscribe: withFilter(
        () => pubsub.asyncIterator([ ICE_CREAM_DISPENSED ]),
        ({ iceCreamDispensed: { flavour:payloadFlavour } }, { flavour:requestFlavour }) => {
          if (!requestFlavour) {
            return true
          }
          return payloadFlavour === requestFlavour
        }
      )
    },
  },
  Query: {
  // ...same as before
  
```

Pass a **flavour** argument to your subscription in the playground

```
subscription {
  iceCreamDispensed (flavour: "vanilla") {
    flavour
    description
  }
}
```

Now, you’ll only receive events about **vanilla** ice cream over your subscription.

[Here is the finished code](https://codesandbox.io/s/vigilant-glitter-n38hp)

```
const express = require('express');
const { createServer } = require('http'); 
const { ApolloServer, gql, withFilter } = require('apollo-server-express');
const { PubSub } = require('graphql-subscriptions');

const pubsub = new PubSub();

const typeDefs = gql`
type IceCream {
  id: Int!
  flavour: String!
  description: String!
}
type Query {
  iceCream(flavour: String!): IceCream
  iceCreams: [IceCream]
}
type Subscription {
  iceCreamDispensed(flavour: String): IceCream
}
`

const stub = [
  {
    id: 0,
    flavour: 'vanilla',
    description: 'A flippant pepper bouquet and alcoholic garlic essences are blended in'
  },{
    id: 1,
    flavour: 'strawberry',
    description: 'Blends indigestible parsnip flavors with a sandy cool ranch flavor'
  },{
    id: 2,
    flavour: 'pear',
    description: 'A soporiphic coconut finish and enticing Bar-B-Q midtones are intertwined'
  }
]

const ICE_CREAM_DISPENSED = 'ICE_CREAM_DISPENSED';

const resolvers = {
  Subscription: {
    iceCreamDispensed: {
      subscribe: 
      withFilter(
        () => pubsub.asyncIterator([ ICE_CREAM_DISPENSED ]),
        ({ iceCreamDispensed: { flavour:payloadFlavour } }, { flavour:requestFlavour }) => {
          if (!requestFlavour) {
            return true
          }
          return payloadFlavour === requestFlavour
        }
      )
    }
  },
  Query: {
    iceCreams: () => {
      return Promise.resolve(stub)
    },
    iceCream: (_, { flavour }) => {
      return Promise.resolve(stub.find(({ flavour:stubFlavour }) => flavour === stubFlavour))
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers
})

const app = express()

app.post('/dispatch', (req, res) => {
  const flavours = ['vanilla','pear','strawberry']
  const flavour = flavours[Math.round(Math.random() * (flavours.length - 1))]
  const ice = stub.find(item => item.flavour === flavour)
  pubsub.publish(ICE_CREAM_DISPENSED, { iceCreamDispensed: ice } )
  return res.status(202).send('accepted')
})

server.applyMiddleware({ app })

const httpServer = createServer(app)

server.installSubscriptionHandlers(httpServer)

const port = 3000

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

[← All blog posts](/en/blog.md)

## Also in love with the web?

For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?

[Join our team](/en/jobs.md)

[Return to top](#top)
