Home Manual Reference Source Test

Express Response Times Example

In this example we'll create a server which has an index page that prints out "hello world", and a page http://localhost:3000/times which prints out the last ten response times that InfluxDB gave us.

You can see annotated the source code on Github here.

The end result should look something like this:

➜  ~ curl -s localhost:3000
Hello world!
➜  ~ curl -s localhost:3000/times | jq
[
  {
    "time": "2016-10-09T19:13:26.815Z",
    "duration": 205,
    "host": "ares.peet.io",
    "path": "/"
  }
]

Get started by installing and importing everything we need! This tutorial assumes you're running Node 6.

npm install influx express

Now create a new file app.js and start writing:

const Influx = require("../../");
const express = require("express");
const http = require("http");
const os = require("os");

const app = express();

Create a new Influx client. We tell it to use the express_response_db database by default, and give it some information about the schema we're writing. It can use this to be smarter about what data formats it writes and do some basic validation for us.

const influx = new Influx.InfluxDB({
  host: "localhost",
  database: "express_response_db",
  schema: [
    {
      measurement: "response_times",
      fields: {
        path: Influx.FieldType.STRING,
        duration: Influx.FieldType.INTEGER,
      },
      tags: ["host"],
    },
  ],
});

Things we used:

Now, we have a working Influx client! We'll make sure the database exists and boot the app.

influx
  .getDatabaseNames()
  .then((names) => {
    if (!names.includes("express_response_db")) {
      return influx.createDatabase("express_response_db");
    }
  })
  .then(() => {
    http.createServer(app).listen(3000, function () {
      console.log("Listening on port 3000");
    });
  })
  .catch((err) => {
    console.error(`Error creating Influx database!`);
  });

Things we used:

Finally, we'll define the middleware and routes we'll use. We have a generic middleware that records the time between when requests comes in, and the time we respond to them. We also have another route called /times which prints out the last ten timings we recorded.

app.use((req, res, next) => {
  const start = Date.now();

  res.on("finish", () => {
    const duration = Date.now() - start;
    console.log(`Request to ${req.path} took ${duration}ms`);

    influx
      .writePoints([
        {
          measurement: "response_times",
          tags: { host: os.hostname() },
          fields: { duration, path: req.path },
        },
      ])
      .catch((err) => {
        console.error(`Error saving data to InfluxDB! ${err.stack}`);
      });
  });
  return next();
});

app.get("/", function (req, res) {
  setTimeout(() => res.end("Hello world!"), Math.random() * 500);
});

app.get("/times", function (req, res) {
  influx
    .query(
      `
    select * from response_times
    where host = ${Influx.escape.stringLit(os.hostname())}
    order by time desc
    limit 10
  `
    )
    .then((result) => {
      res.json(result);
    })
    .catch((err) => {
      res.status(500).send(err.stack);
    });
});

Things we used:

That's it! Go ahead and boot your app using node app.js and try it out! You can see the complete annotated the source code on Github here.

A Moment for Times

InfluxDB is a time series database, so it would make sense that the concept of time is moderately important when dealing with it.

By default, Influx will store all dates you give to it as a nanosecond-precision timestamp, whereas in JavaScript, most of the time we're dealing with millisecond precision timestamps, which we get from Date.now() or myDate.getTime(). This presents a bit of a problem for us JavaScripters, since nanosecond-precision timestamps are stored as 64-bit unsigned integers that JavaScript simply cannot represent accurately.

➜  node-influx git:(master) node
> 1475985480231035677
1475985480231035600

This module tries to make dates as easy as possible for you to deal with, and out of the box everything should "just work".

There are three places that dates can get passed around:

  • Dates coming from Influx queries, like select * from my_series
  • Dates being interpolated into Influx queries
  • Dates being used when writing points on the line protocol, via .writePoints() or .writeMeasurement()

To deal with this, we introduce a new type called NanoDate. These dates behave just like the normal Date type, but have two additional methods: .getNanoTime() and .getNanoISOString(). They behave just like the normal .getTime() and getISOString methods, but they both return nanosecond-precision strings instead of millisecond-precision numbers and timestamps.

expect(myNanoDate.getTime()).to.equal(1475985480231);
expect(myNanoDate.getNanoTime()).to.equal("1475985480231035677");
expect(myNanoDate.toISOString()).to.equal("2016-10-09T03:58:00.231Z");
expect(myNanoDate.toNanoISOString()).to.equal("2016-10-09T03:58:00.231035677Z");

All times returned from Influx queries are parsed to INanoDates. For example, you can do something like the following:

influx.query("select * from perf").then((results) => {
  results.forEach((row) =>
    console.log(`Used ${row.cpu} at ${row.time.toNanoISOString()}`)
  );
});

When writing data to Influx, all write methods accept INanoDates in all situations. This means if you select data from Influx and want to update a data point, you can pass that time right back into the write method. (Remember, points within series are unique by their time!) If you have a nanosecond timestamp from some external source, you can convert it to a INanoDate using toNanoDate.

import { toNanoDate } from "influx";

const myNanoDate = toNanoDate("1475985480231035600");
expect(myNanoDate.getTime()).to.equal(1475985480231);
expect(myNanoDate.getNanoTime()).to.equal("1475985480231035600");
expect(myNanoDate.toNanoISOString()).to.equal("2016-10-09T03:58:00.231035600Z");

Finally, if you want to embed a INanoDate into an Influx query, you should should use toNanoISOString to so do:

influx.query(
  `select * from perf where time > "${myNanoDate.toNanoISOString()}"`
);

Anything unclear? Have some remaining questions or found a bug? Feel free to open an issue, we respond quickly!

Browser Setup

For Node.js, influx can be installed and you can use it out of the box!

npm install --save influx@next

For browsers, this will also work provided you have a bundler which can polyfill Node modules, such as Browserify, Webpack, Jspm, or Rollup with rollup-plugin-node-resolve.

However, at the time of writing, request timeouts will not work by default on any of these platforms until they update to the latest Node API changes. If this is an important feature for you, we recommend that you instruct your bundler to use our patched version of stream-http as your http polyfill. You can install it via:

npm install --save node-influx/stream-http

You can tell Webpack to use this module by adding the following section in your webpack.config.js:

const http = path.resolve(__dirname, "node_modules/stream-http/index.js");

module.exports = {
  resolve: {
    alias: { http, https: http },
  },

  // the rest of your webpack config
};

You can tell Browserify to use this module by adding the following into your build config:

const http = require("stream-http");

browserify(myFiles, {
  builtins: Object.assign({}, require("browserify/lib/builtins"), {
    http,
    https: http,
  }),

  // the rest of your browserify config
});

Testing

influx is tested primarily with a suite of unit tests in additional to functional tests which require an InfluxDB instance to run. In order to check authentication related tests, the InfluxDB instance must have both HTTP and /ping authentication enabled and a root:root user must exist.

The recommended way of running Influx is use their official Docker image:

docker run -d --name influxdb \
  -p 8083:8083 -p 8086:8086 --expose 8090 --expose 8099 \
  -e INFLUXDB_HTTP_AUTH_ENABLED=true \
  -e INFLUXDB_HTTP_PING_AUTH_ENABLED=true \
  influxdb:1.8

docker exec influxdb influx -execute "CREATE USER root WITH PASSWORD 'root' WITH ALL PRIVILEGES"

Alternately, you can use a local installation of the package. If you would like to contribute and don't want to set up a full Influx testing environment, you can run solely unit tests and linting via npm-run-all test:unit test:lint, which do not require anything other than an npm install.

When running tests you can configure where Influx lives by setting an environment variable which is a valid host to pass into the IClusterConfig object.

➜  node-influx git:(master) export INFLUX_HOST='{"host":"127.0.0.1","port":12345}'
➜  node-influx git:(master) npm test

You can run npm run test:watch to watch files and automatically re-run tests when they change.

Contributing

We'd like to encourage you to contribute to the repository. You can do this by making an issue ticket or, even better, submitting a patch via a pull request.

We try to make it as easy as possible for you but there are a few things to consider when contributing. The following guidelines for contribution should be followed if you want to submit a pull request:

How to prepare

  • You need a GitHub account
  • Submit an issue ticket for your issue if there is not one yet. _ Describe the issue and include steps to reproduce if it's a bug. _ Ensure to mention the earliest version that you know is affected.
  • If you are able and want to fix this, fork the repository on GitHub

Make Changes

  • In your forked repository, create a topic branch for your upcoming patch. (e.g. feature--autoplay or bugfix--ios-crash) _ Usually this is based on the master branch. _ Create a branch based on master; git branch fix/master/my_contribution master then checkout the new branch with git checkout fix/master/my_contribution. Please avoid working directly on the master branch.
  • Make sure you follow the established coding style. You can run npm run test:lint to verify you're all set.
  • Make use of the .editorconfig-file if provided with the repository.
  • Make commits of logical units and describe them properly, documenting anything new that you add.
  • If possible, submit tests to your patch / new feature so it can be tested easily.
  • Assure nothing is broken by running all the tests via npm test.

Submit Changes

  • Push your changes to a topic branch in your fork of the repository.
  • Open a pull request to the this repository and choose the right original branch you want to patch.
  • If not done in commit messages (which you really should do) please reference and update your issue with the code changes. But please do not close the issue yourself.
  • We'll review your changes and respond soon, usually within a day!

Additional Resources