Node.js - Google Cloud Pub/Sub

Google Cloud Pub/Sub is a service for event-driven computing system. It allows you to publish thousands of messages per second that will be consumed by subscribers. It's one of the alternatives to Amazon's SQS (Simple Queue Service).

Pub/Sub ensures at-least-once delivery for each messages. Each received message must be acknowledged after successfully processed, otherwise the server will sent the same messages multiple times even it has been processed.

The advantage of using Pub/Sub is easy integration with other Google products. It's quite simple as it does not have shards or partitions. It's also has global scope by default, which means messages can be published and consumed anywhere. In addition, libraries are also provided for popular programming languges, makes it easy for developers to integrate Pub/Sub on applications.

In this tutorial, I'm going to show you the basic usage examples of Google Cloud Pub/Sub in Node.js

Preparation

1. Create or select a Google Cloud project

A Google Cloud project is required to use this service. Open Google Cloud console, then create a new project or select existing project

2. Enable billing for the project

Like other cloud platforms, Google requires you to enable billing for your project. If you haven't set up billing, open billing page.

3. Enable Google Pub/Sub API

To use an API, you must enable it first. Open this page to enable Pub/Sub API.

4. Set up service account for authentication

As for authentication, you need to create a new service account. Create a new one on the service account management page and download the credentials, or you can use your already created service account.

In your .env file, you have to add a new variable

GOOGLE_APPLICATION_CREDENTIALS=/path/to/the/credentials

The .env file should be loaded of course, so you need to use a module for reading .env such as dotenv.

Dependencies

This tutorial uses @google-cloud/pubsub. Add the following dependencies to your package.json and run npm install

  "@google-cloud/pubsub": "~0.19.0"
  "dotenv": "~4.0.0"

1. Topic

Every messages must be sent within a topic. It means you need to have at least one Pub/Sub topic in your account.

To create a topic, open Pub/Sub console and click on "Create Topic". You'll need to enter a name for the topic.

You can also create a topic programatically, as examplified below.

  require('dotenv').config();
  const PubSub = require(`@google-cloud/pubsub`);
  
  const pubsub = new PubSub();
  
  pubsub
    .createTopic('topic1')
    .catch(err => {
      console.error('ERROR:', err);
    });

2. Publisher

A publisher sends a message to a topic. In order to publish a message, the publisher sends a request to the Cloud Pub/Sub server containing the message. A unique identifier will be given to each successfully published message. Below is the code example for sending message to a topic

  require('dotenv').config();
  
  const PubSub = require(`@google-cloud/pubsub`);
  
  const pubsub = new PubSub();
  
  // In this example, the message is current time
  const data = new Date().toString();
  const dataBuffer = Buffer.from(data);
  const topicName = 'topic1';
  
  pubsub
    .topic(topicName)
    .publisher()
    .publish(dataBuffer)
    .then(messageId => {
      console.log(`Message ${messageId} published.`);
    })
    .catch(err => {
      console.error('ERROR:', err);
    });

3. Subscription

A subscription is required to retrieve messages on a topic. Therefore you must create at least a subscription on the topic. A subscription can only read messages on a topic, while a topic can have multiple subscriptions.

In order to create a subscription, on the Pub/Sub console, choose your topic, then click on "Create Subscription". In addition to entering the name for the subscription you want to create, you also have to choose delivery type. There are two delivery types: pull and push.

Pull

In pull subscription, the subcriber sends a request to the Cloud Pub/Sub server in order to retrieve messages

  1. The subscriber sends a request to pull messages
  2. The Cloud Pub/Sub server sends a response containing messages if any
  3. The subscriber acks each messages

Here is the example.

  require('dotenv').config();
  
  const PubSub = require(`@google-cloud/pubsub`);
  
  const pubsub = new PubSub();
  
  const subscriptionName = 'sub1';
  const timeout = 60;
  
  const subscription = pubsub.subscription(subscriptionName);
  
  let messageCount = 0;
  
  /**
   * Handler for received message.
   * @param {Object} message
   */
  const messageHandler = message => {
    console.log(`Received message ${message.id}:`);
    console.log(`Data: ${message.data}`);
    console.log(`tAttributes: ${message.attributes}`);
    messageCount += 1;
  
    // Ack the messae
    message.ack();
  };
  
  // Listen for new messages until timeout is hit
  subscription.on(`message`, messageHandler);
  setTimeout(() => {
    subscription.removeListener('message', messageHandler);
    console.log(`${messageCount} message(s) received.`);
  }, timeout * 1000);
  

Push

In push subscription, data will be sent by Cloud Pub/Sub server to the specified endpoint. When you create a new subscription using push method, you need to specify an endpoint where the messages will be sent.

  1. The Cloud Pub/Sub server sends a request containg messages
  2. The subscriber receives the request and acks each messages

In case you get INVALID_ARGUMENT error when creating a new subscriber, do the following:

  1. Verify your site on Google Search Console.
  2. Add your domain on Google Cloud Console Domain Verification.

Now you should be able to create a new subscriber. If you need to create another subscriber whose endpoint is under different domain, you need to do the same.

A message sent to an endpoint is on the message property of req.body. The data is in base64 form, so you have to decode it first. Sendng a response with a status code of 204 implicitly acknowledges the message. Here is the code example.

  router.post('/google-cloud-pubsub-subscriber', (req, res) => {
    const message = req.body ? req.body.message : null;
  
    if (message) {
      const buffer = Buffer.from(message.data, 'base64');
      const data = buffer ? buffer.toString() : null;

      console.log(`Received message ${message.messageId}:`);
      console.log(`Data: ${data}`);
    }
  
    return res.send(204);
  });

After creating both publisher and subscriber, it's time to test if it works as we expect. Run the subscriber and the publisher. If successful, you should see the messages printed on your console.</p