1

I am trying to implement a background service so that there is less load on the API call. Background task will run to upload files to S3 and send email using nodemailer.

Rifa Achrinza
  • 1,555
  • 9
  • 19
Mohammad Quadri
  • 375
  • 4
  • 12

1 Answers1

4

Following the loopback 4's design patterns you can create a service provider and inject it in your service or controller. Here is a very basic example with Bull.js:

import Bull, { Queue, Job } from "bull";
import {Provider, service} from "@loopback/core";
import {get} from "@loopback/rest";

export function audioProcessor(job: Job) {
  console.log(
    `Processing audio file: ${job.data.filename}`,
    `Audio bitrate: ${job.data.bitrate}`
  );
}

export class AudioQueueProvider implements Provider<Queue> {
  async value() {
    const queue = new Bull("AudioQueue", {
      redis: { port: 6379, host: "127.0.0.1" }
    });

    queue.process(audioProcessor);
    
    return queue;
  }
}

export class AudioController {
  constructor(
    @service(AudioQueueProvider) public queue: Queue;
  ) {}

  @get('/process-audio')
  async addToQueue(): Promise<string> {
    await this.queue.add(
      {
        filename: 'process_me.wav',
        bitrate: 320,
      }
    );
    return 'Audio file added to the AudioQueue for processing';
  }
}

The RabbitMQ implementation should be similar (not tested):

import { Provider, service } from "@loopback/core";
import {get} from "@loopback/rest";
import amqp from "amqplib/callback_api";

export function audioProcessor(msg: any) {
  console.log(
    `Processing audio file: ${msg.content.filename}`,
    `Audio bitrate: ${msg.content.bitrate}`
  );
}

export class AudioQueueProvider implements Provider<any> {
  async value() {
    const CONN_URL = "amqp://localhost";
    let ch = null;
    const channelName = 'AudioQueue';
    
    amqp.connect(CONN_URL, function (err, conn) {
      conn.createChannel(function (err, channel) {
        ch = channel;
      });
    });

    ch.assertQueue(channelName, {
      durable: false
    });

    ch.consume(channelName, audioProcessor, {
      noAck: true
    });
    
    return ch;
  }
}

export class AudioController {
  constructor(
    @service(AudioQueueProvider) public channel: any;
  ) {}

  @get('/process-audio')
  async addToQueue(): Promise<string> {
    await this.channel.sendToQueue(
      'AudioQueue',
      {
        filename: 'process_me.wav',
        bitrate: 320,
      }
    );
    return 'Audio file added to the AudioQueue for processing';
  }
}

Svetljo
  • 303
  • 1
  • 8
  • `@service(AudioQueueProvider) public queue: Queue` didn't work for me. I had to bind the AudioQueueProvider in application constructor and inject in the controller `@inject(QUEUE_SERVICE_BINDING_KEY) public queue: Queue` – Mohammad Quadri Sep 15 '20 at 14:50