moleculer-http-client

HTTP client mixin that allows Moleculer services to communicate with remote REST APIs

Usage no npm install needed!

<script type="module">
  import moleculerHttpClient from 'https://cdn.skypack.dev/moleculer-http-client';
</script>

README

Moleculer logo

Build Status Coverage Status GitHub license npm Downloads

moleculer-http-client

A tiny wrapper around got HTTP client that allows Moleculer services to communicate with REST APIs. Why got? Because reasons.

Features

  • Make HTTP requests from Actions and Events
  • Stream data
  • Cache data
  • Customizable log messages and errors

Install

npm install moleculer-http-client --save

Service Settings

Use httpClient field to configure your got client.

module.exports = {
  name: "http",
  
  /**
   * Moleculer settings
   */
  settings: {
    // HTTP client settings
    httpClient: {
      // Boolean value indicating whether request should be logged or not
      logging: true,

      // Log request function
      logOutgoingRequest: logOutgoingRequest,

      // Log response function
      logIncomingResponse: logIncomingResponse,

      // Format the Response
      responseFormatter: "body", // one of "body", "headers", "status", "raw"

      // Format the Errors
      errorFormatter: errorFormatter,

      // Got Client options
      defaultOptions: {
        // Put here any Got available option that can be used to extend Got client
      }
    }
  },
};

Service Actions

get

HTTP GET action

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options

Returns

Type: Promise, Stream

post

HTTP POST action

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options
streamPayload Stream optional Stream payload

Note: When streaming use ctx.meta to pass url and opt and ctx.params to pass stream data

Returns

Type: Promise

put

HTTP PUT action

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options
streamPayload Stream optional Stream payload

Note: When streaming use ctx.meta to pass url and opt and ctx.params to pass stream data

Returns

Type: Promise

patch

HTTP PATCH action

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options
streamPayload Stream optional Stream payload

Note: When streaming use ctx.meta to pass url and opt and ctx.params to pass stream data

Returns

Type: Promise

delete

HTTP DELETE action

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options
streamPayload Stream optional Stream payload

Note: When streaming use ctx.meta to pass url and opt and ctx.params to pass stream data

Returns

Type: Promise

Service Methods

_get

HTTP GET method

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options

Returns

Type: Promise, Stream

_post

HTTP POST method

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options
streamPayload Stream optional Stream payload

Returns

Type: Promise

_put

HTTP PUT method

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options
streamPayload Stream optional Stream payload

Returns

Type: Promise

_patch

HTTP PATCH method

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options
streamPayload Stream optional Stream payload

Returns

Type: Promise

_delete

HTTP DELETE method

Parameters

Property Type Default Description
url String required URL
opt Object optional Request options

Returns

Type: Promise

Usage

Actions

Action Example

const { ServiceBroker } = require("moleculer");
const HTTPClientService = require("moleculer-http-client");

// Create broker
let broker = new ServiceBroker();

// Create a service
broker.createService({
  name: "http",

  // Load HTTP Client Service  
  mixins: [HTTPClientService]
});

// Start the broker
broker.start().then(() => {
  broker
    // Make a HTTP GET request
    .call("http.get", {
      url: "https://httpbin.org/json",
      opt: { responseType: "json" }
    })
    .then(res => broker.logger.info(res))
    .catch(error => broker.logger.error(error));
});

Result

INFO  http-client/HTTP: => HTTP GET to "https://httpbin.org/json"
INFO  http-client/HTTP: <= HTTP GET to "https://httpbin.org/json" returned with status code 200
INFO  http-client/BROKER: { slideshow: { author: 'Yours Truly', date: 'date of publication', slides: [ [Object], [Object] ], title: 'Sample Slide Show' } }

Events

Event Example

const { ServiceBroker } = require("moleculer");
const HTTPClientService = require("moleculer-http-client");

// Create broker
let broker = new ServiceBroker();

// Create a service
broker.createService({
  name: "http",

  // Load HTTP Client Service
  mixins: [HTTPClientService],

  events: {
    // Register an event listener
    async "some.Event"(request) {
      this.logger.info("Caught an Event");
      // Use service method to make a request
      const res = await this._get(request.url, request.opt);
      this.logger.info("Printing Payload");
      // Print the response data
      this.logger.info(res.body);
    }
  }
});

// Start the broker
broker.start().then(() => {
  broker
    // Emit some event
    .emit("some.Event", {
      url: "https://httpbin.org/json",
      opt: { responseType: "json" }
    });
});

Result

INFO  http-client/HTTP: Caught an Event
INFO  http-client/HTTP: => HTTP GET to "https://httpbin.org/json"
INFO  http-client/HTTP: <= HTTP GET to "https://httpbin.org/json" returned with status code 200
INFO  http-client/HTTP: Printing Payload
INFO  http-client/HTTP: { slideshow: { author: 'Yours Truly', date: 'date of publication', slides: [ [Object], [Object] ], title: 'Sample Slide Show' } }

Stream

GET Stream

const { ServiceBroker } = require("moleculer");
const HTTPClientService = require("moleculer-http-client");
const fs = require("fs");

// Create broker
let broker = new ServiceBroker({
  nodeID: "http-client"
});

// Create a service
broker.createService({
  name: "http",

  // Load HTTP Client Service
  mixins: [HTTPClientService]
});

// Start the broker
broker.start().then(() => {
  broker
    // Make a HTTP GET request
    .call("http.get", {
      url: "https://sindresorhus.com/",
      opt: { isStream: true }
    })
    .then(res => {
      const filePath = "./examples/stream-get/file.md";
      res.pipe(fs.createWriteStream(filePath, { encoding: "utf8" }));

      res.on("response", response => {
        broker.logger.info(response.statusCode);
      });
    })
    .catch(error => broker.logger.error(error));
});

POST Stream

const { ServiceBroker } = require("moleculer");
const HTTPClientService = require("moleculer-http-client");
const ApiGateway = require("moleculer-web");
const fs = require("fs");

// Create broker
let broker = new ServiceBroker({
  nodeID: "http-client"
});

// Create a HTTP Client Service
broker.createService({
  name: "http",

  // Load HTTP Client Service
  mixins: [HTTPClientService]
});

// Create HTTP Server Services
broker.createService({
  name: "api",
  mixins: [ApiGateway],
  settings: {
    port: 4000,
    routes: [
      {
        path: "/stream",
        bodyParsers: { json: false, urlencoded: false },
        aliases: { "POST /": "stream:api.postStream" }
      }
    ]
  },

  actions: {
    postStream(ctx) {
      return new Promise((resolve, reject) => {
        const stream = fs.createWriteStream(
          "./examples/stream-post/stored-data.md"
        );

        stream.on("close", async () => {
          resolve({ fileName: `file.md`, method: "POST" });
        });

        // Return error to the user
        stream.on("error", err => {
          reject(err);
        });

        // Pipe the data
        ctx.params.pipe(stream);
      });
    }
  }
});

// Start the broker
broker.start().then(() => {
  const streamFile = "./examples/stream-post/data-to-stream.md";
  const stream = fs.createReadStream(streamFile, { encoding: "utf8" });

  // Pass stream as ctx.params
  // Pass URL and options in ctx.meta
  const req = broker.call("http.post", stream, {
    meta: { url: "http://localhost:4000/stream", isStream: true }
  });

  req.then(res => {
    broker.logger.info(res.statusCode);
  });
});

Cache

Moleculer Cache

If you are using actions to make HTTP requests then you can use Moleculer's cache to cache responses.

Please note that when using Moleculer's cache you will be ignoring Cache-Control header field. If you care about Cache-Control then you should use Got's cache.

Example of Moleculer Cache

const { ServiceBroker } = require("moleculer");
const HTTPClientService = require("moleculer-http-client");

// Create broker
let broker = new ServiceBroker({
  nodeID: "http-client",
  // Enable Moleculer Cache
  cacher: "Memory"
});

// Create a service
broker.createService({
  name: "http",

  // Load HTTP Client Service
  mixins: [HTTPClientService],

  actions: {
    get: {
      // Enable cache for GET action
      // More info: https://moleculer.services/docs/0.13/caching.html
      cache: true
    }
  }
});

// Start the broker
broker.start().then(() => {
  broker
    // Make a HTTP GET request
    .call("http.get", {
      url: "https://httpbin.org/json",
      opt: { responseType: "json" }
    })
    .then(res => broker.logger.info(res.body))
    .then(() =>
      broker.call("http.get", {
        url: "https://httpbin.org/json",
        opt: { responseType: "json" }
      })
    )
    .then(res => broker.logger.info(res.body))
    .catch(error => broker.logger.error(error));
});

Result

            INFO  http-client/HTTP: => HTTP GET to "https://httpbin.org/json"
            INFO  http-client/HTTP: <= HTTP GET to "https://httpbin.org/json" returned with status code 200
Request ->  INFO  http-client/BROKER: { slideshow: { author: 'Yours Truly', date: 'date of publication', slides: [ [Object], [Object] ], title: 'Sample Slide Show' } }
Cache   ->  INFO  http-client/BROKER: { slideshow: { author: 'Yours Truly', date: 'date of publication', slides: [ [Object], [Object] ], title: 'Sample Slide Show' } }

Got's Cache

If you are using methods or you care about Cache-Control header option then you should use Got's cache.

Example of Got cache

const { ServiceBroker } = require("moleculer");
const HTTPClientService = require("moleculer-http-client");

// Using JS Map as cache
const cacheMap = new Map();

// Create broker
let broker = new ServiceBroker({
  nodeID: "http-client"
});

// Create a service
broker.createService({
  name: "http",

  // Load HTTP Client Service
  mixins: [HTTPClientService],

  settings: {
    httpClient: {
      defaultOptions: {
        // Set Got's built-in cache
        // More info: https://www.npmjs.com/package/got#cache-1
        cache: cacheMap
      }
    }
  }
});

// Start the broker
broker.start().then(() => {
  broker
    // Make a HTTP GET request
    .call("http.get", {
      url: "https://httpbin.org/cache/150",
      opt: { responseType: "json" }
    })
    .then(res => broker.logger.info(res.isFromCache))
    .then(() =>
      broker.call("http.get", {
        url: "https://httpbin.org/cache/150",
        opt: { responseType: "json" }
      })
    )
    .then(res => broker.logger.info(res.isFromCache))
    .catch(error => broker.logger.error(error));
});

Result

INFO  http-client/HTTP: => HTTP GET to "https://httpbin.org/cache/150"
INFO  http-client/HTTP: <= HTTP GET to "https://httpbin.org/cache/150" returned with status code 200
INFO  http-client/BROKER: false
INFO  http-client/HTTP: => HTTP GET to "https://httpbin.org/cache/150"
INFO  http-client/HTTP: **CACHED** HTTP GET to "https://httpbin.org/cache/150" returned with status code 200
INFO  http-client/BROKER: true

Got Instance

If you need to do some fancy request (e.g., HEAD, TRACE, OPTIONS) you can directly call the got client available at _client.

const { ServiceBroker } = require("moleculer");
const HTTPClientService = require("moleculer-http-client");

// Create broker
let broker = new ServiceBroker({
  nodeID: "http-client"
});

// Create a service
broker.createService({
  name: "http",

  // Load HTTP Client Service
  mixins: [HTTPClientService],

  actions: {
    async fancyRequest(ctx) {
      try {
        // Direct call to Got Client
        // Can be any Got supported HTTP Method
        return await this._client(ctx.params.url, ctx.params.opt);
      } catch (error) {
        throw error;
      }
    }
  }
});

// Start the broker
broker.start().then(() => {
  broker
    // Make a fancy request
    .call("http.fancyRequest", {
      url: "https://httpbin.org/json",
      opt: { method: "GET", responseType: "json" }
    })
    .then(res => broker.logger.info(res.body))
    .catch(error => broker.logger.error(error));
});
INFO  http-client/HTTP: => HTTP GET to "https://httpbin.org/json"
INFO  http-client/HTTP: <= HTTP GET to "https://httpbin.org/json" returned with status code 200
INFO  http-client/BROKER: { slideshow: { author: 'Yours Truly', date: 'date of publication', slides: [ [Object], [Object] ], title: 'Sample Slide Show' } }

Customization

Log Messages

    const service = broker.createService({
      name: "http",

      mixins: [MoleculerHTTP],

      settings: {
        httpClient: {
          // Input is Got's options object. More info: https://www.npmjs.com/package/got#options
          logOutgoingRequest: options => {
            console.log(`-----> Request ${options.href}`);
          },

          // Input is Got's response object: More info: https://www.npmjs.com/package/got#response
          logIncomingResponse: response => {
            console.log(`<----- Response Status Code ${response.statusCode}`);
          }
        }
      }
    });

Errors

    const service = broker.createService({
      name: "http",

      mixins: [MoleculerHTTP],

      settings: {
        httpClient: {
          // Custom error handler function
          // Input error is Got's error. More info: https://www.npmjs.com/package/got#errors
          errorFormatter: error => {
            return new Error("Custom Error");
          }
        }
      }
    });

Test

$ npm test

Contribution

Please send pull requests improving the usage and fixing bugs, improving documentation and providing better examples.

License

The project is available under the MIT license.

Contact

Copyright (c) 2016-2020 MoleculerJS

@moleculerjs @MoleculerJS