Today's Hype Scores!

How we used the Nodejs Twilio library, NestJS, and Postgres to build an image sharing microservice: Part 2 of 3

This is a 3-part tutorial where we go over how to build a simple MMS image sharing service using Twilio, NestJS, and Postgres. In this second post of the 3-part tutorial series we go over how to scaffold your NestJS Microservice controllers and services

Intro

(If you read this in the first post feel free to skip to the next section. If you missed the first post, you should head back there to get started by following this link)

Hey everyone - in this series of posts we're going to show you how we set up a simple MMS image sharing service. This is similar to the service that we use at HypeRider to drive our daily newsletter

For those of you unfamiliar, we at HypeRider run a sentiment driven stock newsletter. We started by listening to Reddit and Twitter and reporting what people were saying about stocks. But since then, our newsletter has grown to support other things like NFTs, Cryptocurrency, and now our new "Meme Corner".

The founding apes and I wanted an easy way that we could take all the memes that we share between each other every day, and bring those to our users. We currently use this service to power our "Meme Corner" but we know that this can be expanded to other types of content as well.

We decided to use Twilio - a messaging API provider - to help us build this and we're going to show you how as well in this 3-part tutorial

Technical Summary

  • In this post we will review how to take your NestJS Microservice from last time and scaffold the supporting services, interfaces, and controllers
  • Also as a reminder - this is a toy project and by no means should this be deployed as is to a "production" environment
  • In general in this series we may assume you have some basic knowledge of some of the tools mentioned here
  • If you have questions or need more guidance - we still want to help you! Reach out here - bwalsh@hyperider.io if you want more help!

Pre-requisites

For all of the tutorials we will require:

Installing the Nodejs Twilio library and Create a NestJS Controller and Service for Twilio

Install NodeJS Twilio Library

As with all packages we can use npm (see the pre-reqs if you don't have it installed) to install the NodeJS Twilio library. You can run the below command in your projects main directory to install it.

npm i twilio

Use the Nest CLI to create a service for Twilio

NestJS provides us with a top notch CLI in order to speed up our development process. Rather than tediously creating files and adding them to other files, we can simply run the generate command (aliased as g in these examples) to create the parts of the project we want.

nest g s twilio

Add code to your service

For those that are familiar, NestJS shares many development patterns with Angular. One large pattern is that it relies heavily on dependency injection and singleton services. These services help us separate our business logic from our controller logic.

This will make it easier to separate concerns if we ever need to change this code later.

For this example - we will keep things relatively simple and put our logic to write to the database in an async function here called create

import { Inject, Injectable } from '@nestjs/common';
import { Image } from './image.interface';
import { UpdateTwilioDto } from './dto/update-twilio.dto';
import { MASSIVE_CONNECTION } from '../massive/constants';

@Injectable()
export class TwilioService {

  constructor(@Inject(MASSIVE_CONNECTION) private readonly db){}

  async create(image: Image) {
    return this.db.image.save(image)
  }
}

Use the Nest CLI to create a controller

Similar to the service above, we can add our controller to our project.

nest g c twilio

Create an Image interface

We can also create an image interface that will help ensure that we are using the correct types.

nest g interface image

You can then fill in the generated file with this

export interface Image {
    id?: string,
    url: string,
}

We'll reference this custom Image type later in our controller.

Add code to your controller

Finally we can add the code for our webhook to our controller

Controllers are where majority of our routing logic is created. We generally want to keep our controllers clean and simple and keep things as much as possible to the routing and response logic. In this tutorial we do break that pattern to keep things simple, but we can review how to refactor this more later.

In our case, we are only going to create a POST endpoint that Twilio can use as a webhook

import {
  Controller,
  Post,
  Header,
  Req,
  Request,
  HttpCode,
} from '@nestjs/common';
import { TwilioService } from './twilio.service';
import { Image } from './image.interface';
import { twiml } from 'twilio';

@Controller('twilio')
export class TwilioController {
  constructor(private readonly twilioService: TwilioService) {}

  @Post('hook')
  @Header('Content-Type', 'text/xml')
  @HttpCode(200)
  async hook(@Req() request: Request): Promise<string> {
    const resp = new twiml.MessagingResponse();

    const url = request.body['MediaUrl0']
    
    if (url) {
      const image: Image = {
        url
      }
      await this.twilioService.create(image)
      const message = resp.message('Nice, you uploaded an image!');
      message.media(
        'https://staticdelivery.nexusmods.com/mods/4095/images/thumbnails/219/219-1644075313-762393458.jpeg',
      );
    } else {
      const message = resp.message(
        'Woops looks like you forgot your image 🐸',
      );
      message.media(
        'https://www.dictionary.com/e/wp-content/uploads/2018/03/This-is-Fine-300x300.jpg',
      );
    }

    return resp.toString();
  }
}

What does this all mean

Hopefully at this point you're still tracking along with us. Let's review a couple of things of note in our controller:

First, we use dependency injection in our constructor to inject the TwilioService from our previous steps. This gives us access to our Postgres database and will allow us to write our image url to it.

Second, we use the decorators that NestJS gives us to define our route as /twilio/hook as well as defining the Content-Type the endpoint accepts and overriding NestJS default 200 response status code. You can read more about decorators here.

  @Post('hook')
  @Header('Content-Type', 'text/xml')
  @HttpCode(200)

And lastly, we actually implement our function that will extract the image url if one exists and respond back to the user that texted our phone number.

If you have more questions about the details of how the controller works, or have opinions on how I should restructure this code feel free to contact me at bwalsh@hyperider.io! I always love to get feedback from readers!

Test your setup

If you've done everything correctly between the first tutorial and this one we should be able to start our server successfully. We can run the below command to start a local development server that will hot-reload if you make any changes to your files

npm run start:dev

Review

So in this tutorial we were able to successfully create and start our NestJS microservice. We added a service, an interface, and a controller. We then added the code necessary to run all of those parts and came away with something that can now process our requests from Twilio and write them to our Postgres database.

In the next post we'll explore configuring Twilio and running our service locally in a way that we can finally test all of this end-to-end and we can see the fruits of our labor.

If you enjoyed this post feel free to subscribe to our HypeRider newsletter There you will see the results of our full-blown HypePipe in our Meme Corner as well as our usual selection of top sentiment driven stocks.

If you need to review something from part 1 of this tutorial, you can follow this link

Stay tuned for part 3 in this series where we will configure Twilio and use ngrok to run our project locally and start storing our images we send via text message!