Wrapping NPM Packages as a Service Provider in AdonisJs

Wrapping NPM Packages as a Service Provider in AdonisJs

AdonisJs makes creating APIs fun, it makes dependency importing into a file easy with the use global method. It gives the developer a central point to configure their dependencies and bind it to the IOC container. To read about the IOC container click here. This article will show you how to use AdonisJs service providers to achieve all the benefits mentioned earlier, including configuring different dependencies serving a similar purpose to have a single interface and a central point to switch from one dependency to the other without altering the code. I will be demonstrating using the Twilio Node.js helper library, I will assume you are already working with AdonisJs but if you need to learn about adonisJs you can follow this link and have a basic understanding of object-oriented programming.

Getting Started

First, we install the Twilio helper library using npm, if you prefer you can use yarn. npm install twilio into the project.

Setting up Configuration

So we open up the config folder and create a file in it called sms.js. The configuration object will be created in such a way that it can contain various SMS service providers configuration details. The details will be fed from the environment file.

const Env = use('Env')

module.exports = {
  /*
  |--------------------------------------------------------------------------
  | Connection
  |--------------------------------------------------------------------------
  |
  | Connection to be used for sending SMS. 
  | A connection needs to have a corresponding object below.
  |
  */
  connection: Env.get('SMS_CONNECTION'),
  sms_sender: Env.get('SMS_SENDER'),
  /*
  |--------------------------------------------------------------------------
  | TWILIO
  |--------------------------------------------------------------------------
  |
  | Here we define the configuration for sending SMS via TWILIO.
  |
  */
  twilio: {
    driver: 'twilio',
    accountSid: Env.get('TWILIO_SMS_ACCOUNTS_ID'),
    auth_token: Env.get('TWILIO_SMS_AUTH_TOKEN')
  },
}

The connection determines which SMS service provider the system is using, sms_sender defines the number sending the SMS, twilio is the object containing the configuration details for the Twilio SMS service provider, any other service provider needed can be configured by adding the configuration object like the twilio object.

Setting up the Providers Folder

The providers folder will contain all providers and will live in the root of your app. In the providers folder we create a folder called Sms, here is where all the magic will happen.

|-- app
| |--config
| |-- providers
|   |-- sms
| |-- start

Setting the Source folder

In the newly created providers/sms folder, we need to have a folder where the code interacts with the npm package. the folder will be named src, which will contain an index.js file and other files for the different SMS service providers. In this demo, we will call it a Twilio.js file which will contain the class that interacts with the Twilio node.js helper function.

const Twilio = require('twilio')

class TwilioDriver {
  constructor (config, sender) {
    this.config = config
    this.messageBody = null
    this.sendTo = null
    this.sender = sender
  }

  to (to) {
    this.sendTo = to
    return this
  }

  body (body) {
    this.messageBody = body
    return this
  }

  async sendSms () {
    const client = Twilio (this.config.accountSid, this.config.auth_token)

    return await client.messages.create ({
      body: this.messageBody,
      from: this.sender,
      to: this.sendTo
    })
  }
}

module.exports = TwilioDriver

Next, we create an index.js file in the src folder, the file will require other files in the src folder, pass it into an object and export the object. The index.js file will look like this:

module.exports = {
  twilio: require('./Twilio')
}

Creating the Service Provider index file

This file takes in the config file and the index.js file from the src folder, it will reside in the Sms folder. It takes in the connection property of the config file and decides which SMS service provider to instantiate.

const Driver = require('./src')

class Sms {
  constructor (Config) {
    this.Config = Config
  }

  prepare () {
    /**
     * Read connection name using Config
     * provider
     */

    const SmsConfig = this.Config.get('sms')
    const name = SmsConfig.connection
    const sender = SmsConfig.sms_sender
    const config = SmsConfig[name]

    const SmsDriver = Driver[name]


    /**
     * Return the instance back
     */
    return new SmsDriver (config, sender)
  }
}

module.exports = Sms

BInding to the IOC container

Here we use service provider to bind all our previous work to the IOC container. The service provider is a class we extend, it comes with a register and a boot method but we only need to use the register method. In the register method, the configuration from the config file is passed into the Sms class located in the sms/index.js file. The first parameter of this.app.bind defines the name used to address the provider when importing it later for use.

const { ServiceProvider } = require('@adonisjs/fold')

class SmsProvider extends ServiceProvider {
  register () {
    this.app.bind('Sms', () => {
      const Config = this.app.use('Adonis/Src/Config')
      return new (require('.'))(Config).prepare()
    })
  }
}

module.exports = SmsProvider

this.app references the ioc object, so we can use this.app.bind or this.app.singleton and it will be the same with ioc.bind or ioc.singleton

Registering the Provider

To register the newly created provider, open the app.js file in the start folder in the root of the app.

  • require the path module
    const path = require('path')
    
  • Add the path to the new provider in the providers array
    const providers = [
    path.join(__dirname, '..', 'providers', 'sms/Provider'),
    ]
    

    Using the Provider

    At this point, we can import the provider into any part of the project using
    const Sms = use('Sms')
    

    Moving Further

    So if there is a need to use another SMS service provider nodeJs helper library, we add the configuration to the config/Sms.js file, we add the class to interact with the helper library in the providers/sms/src folder to handle it. The class will contain the to method, the body method and the sendSms method. So as not to break existing code. Finally, remember to define the new name of the service provider in the .env file and let it be the name of one of the properties in the config/Sms.js file holding a configuration object.

In conclusion, we have seen have to configure an NPM package as a service provider for AdonisJs, the principles remain the same and a Provider.js file is always necessary, the rest are defined based on the needs of the developer.