Quick Start: Node and TimescaleDB

Goal

This quick start guide is designed to get the Node.js developer up and running with TimescaleDB as their database. In this tutorial, you’ll learn how to:

Prerequisites

To complete this tutorial, you need a cursory knowledge of the Structured Query Language (SQL). The tutorial walks you through each SQL command, but it is helpful if you’ve seen SQL before.

To start, install TimescaleDB. Once your installation is complete, we can proceed to ingesting or creating sample data and finishing the tutorial.

Obviously, you need to install Node and the Node Package Manager (npm) as well.

Connect Node to TimescaleDB

TimescaleDB is based on PostgreSQL and we can use common PostgreSQL tools to connect your Node app to the database. This example uses a common Node.js Object Relational Mapper (ORM) called Sequelize.

Step 1: Create your Node app

Let’s initialize a new Node app. From your command line, type the following:

  1. npm init -y

This creates a package.json file in your directory, which contains all of the dependencies for your project:

  1. {
  2. "name": "node-sample",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "scripts": {
  7. "test": "echo \"Error: no test specified\" && exit 1"
  8. },
  9. "keywords": [],
  10. "author": "",
  11. "license": "ISC"
  12. }

Now, let’s install Express.js by running the following command:

  1. npm install express

Finally, let’s create a simple web page to display a greeting. Open your code editor, and add the following to a file called index.js:

  1. const express = require('express')
  2. const app = express()
  3. const port = 3000;
  4. app.use(express.json());
  5. app.get('/', (req, res) => res.send('Hello World!'))
  1. app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

You can test your simple application by running the following from your command line and using your browser to view http://localhost:3000:

  1. node index.js

You should get a “Hello, World” greeting.

Step 2: Configure the TimescaleDB database using Sequelize

Locate your TimescaleDB credentials in order to connect to your TimescaleDB instance.

You’ll need the following credentials:

  • password
  • username
  • host URL
  • port
  • database name

Now, let’s add Sequelize to our project by first installing it (and its command line interface) and the packages for PostgreSQL from the command line:

  1. npm install sequelize sequelize-cli pg pg-hstore

Now let’s go back to our index.js file and require Sequelize in our application. You’ll need your TimescaleDB credentials in order to build the connection URL as well. Once you have that information, add the following to index.js, below the other const statements:

  1. const Sequelize = require('sequelize')
  2. const sequelize = new Sequelize('postgres://user:[email protected]:5432/dbname',
  3. {
  4. dialect: 'postgres',
  5. protocol: 'postgres',
  6. dialectOptions: {
  7. ssl: {
  8. require: true,
  9. rejectUnauthorized: false
  10. }
  11. }
  12. })
warning

Note the settings in dialectOptions. These are critical in connecting to a TimescaleDB instance via SSL.

We can test this connection by adding the following to index.js after the app.get statement:

  1. sequelize.authenticate().then(() => {
  2. console.log('Connection has been established successfully.');
  3. }).catch(err => {
  4. console.error('Unable to connect to the database:', err);
  5. });

Once again, start the application on the command line:

  1. node index.js

And you should get the following results:

  1. Example app listening at http://localhost:3000
  2. Executing (default): SELECT 1+1 AS result
  3. Connection has been established successfully.

Create a relational table

Step 1: Add TimescaleDB to your Node configuration

Now that we have a successful connection to the defaultdb database, we can build out our first database and model.

warning

You can skip the first two steps if you’re going to use TimescaleDB cloud. The service creates a database with the extension already enabled.

Let’s initialize Sequelize and create the necessary configuration files for our project. From the command line, type the following:

  1. npx sequelize init

This creates a config/config.json file in your project. You need to modify it with the connection details we tested earlier. For the remainder of this application, we’ll use a database called node_test. Here’s a full example file. Again, note the dialectOptions.

  1. {
  2. "development": {
  3. "username": "[tsdbadmin]",
  4. "password": "[your_password]",
  5. "database": "node_test",
  6. "host": "[your_host]",
  7. "port": "[your_port]",
  8. "dialect": "postgres",
  9. "protocol": "postgres",
  10. "dialectOptions": {
  11. "ssl": {
  12. "require": true,
  13. "rejectUnauthorized": false
  14. }
  15. }
  16. },
  17. "test": {
  18. "username": "tsdbadmin",
  19. "password": "your_password",
  20. "database": "node_test",
  21. "host": "your_host",
  22. "port": "your_port",
  23. "dialect": "postgres",
  24. "protocol": "postgres",
  25. "dialectOptions": {
  26. "ssl": {
  27. "require": true,
  28. "rejectUnauthorized": false
  29. }
  30. }
  31. },
  32. "production": {
  33. "username": "tsdbadmin",
  34. "password": "your_password",
  35. "database": "node_test",
  36. "host": "your_host",
  37. "port": "your_port",
  38. "dialect": "postgres",
  39. "protocol": "postgres",
  40. "dialectOptions": {
  41. "ssl": {
  42. "require": true,
  43. "rejectUnauthorized": false
  44. }
  45. }
  46. }

Now you’re ready to create the node_test database. From the command line, type the following:

  1. npx sequelize db:create

You should get this result:

  1. Loaded configuration file "config/config.json".
  2. Using environment "development".
  3. Database node_test created.

Step 2: Add the TimescaleDB extension to the database

TimescaleDB is delivered as a PostgreSQL extension. Some instances and versions of TimescaleDB already have the extension installed. Let’s make sure the extension is installed if it’s not.

To start, create a database migration by running the following command:

  1. npx sequelize migration:generate --name add_tsdb_extension

There is a file that has the name add_tsdb_extension appended to it in your migrations folder. Modify that file to look like this:

  1. 'use strict';
  2. module.exports = {
  3. up: (queryInterface, Sequelize) => {
  4. return queryInterface.sequelize.query("CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;");
  5. },
  6. down: (queryInterface, Sequelize) => {
  7. return queryInterface.sequelize.query("DROP EXTENSION timescaledb;");
  8. }
  9. };

Now run the migration command from the command-line:

  1. npx sequelize db:migrate

You should get the following result:

  1. Sequelize CLI [Node: 12.16.2, CLI: 5.5.1, ORM: 5.21.11]
  2. Loaded configuration file "config/config.json".
  3. Using environment "development".
  4. == 20200601214455-add_tsdb_extension: migrating =======
  5. == 20200601214455-add_tsdb_extension: migrated (0.414s)

You can test and see if the TimescaleDB extension is installed by connecting to your database using psql and running the \dx command. You should get a result like this:

  1. List of installed extensions
  2. Name | Version | Schema | Description
  3. -------------+---------+------------+-------------------------------------------------------------------
  4. plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
  5. timescaledb | 1.7.1 | public | Enables scalable inserts and complex queries for time-series data
  6. (2 rows)

Step 3: Create a table

Now let’s create a table and model called page_loads for our database using the Sequelize command line tool:

  1. npx sequelize model:generate --name page_loads --attributes userAgent:string,time:date

You should get a result similar to this:

  1. Sequelize CLI [Node: 12.16.2, CLI: 5.5.1, ORM: 5.21.11]
  2. New model was created at [some path here] .
  3. New migration was created at [some path here] .

Now, edit the migration file to make sure it sets up a composite primary key:

  1. 'use strict';
  2. module.exports = {
  3. up: async (queryInterface, Sequelize) => {
  4. await queryInterface.createTable('page_loads', {
  5. userAgent: {
  6. primaryKey: true,
  7. type: Sequelize.STRING
  8. },
  9. time: {
  10. primaryKey: true,
  11. type: Sequelize.DATE
  12. }
  13. });
  14. },
  15. down: async (queryInterface, Sequelize) => {
  16. await queryInterface.dropTable('page_loads');
  17. }
  18. };

And finally, let’s migrate our change and ensure that it is reflected in the database itself:

  1. npx sequelize db:migrate

You should get a result that looks like this:

  1. Sequelize CLI [Node: 12.16.2, CLI: 5.5.1, ORM: 5.21.11]
  2. Loaded configuration file "config/config.json".
  3. Using environment "development".
  4. == 20200528195725-create-page-loads: migrating =======
  5. == 20200528195725-create-page-loads: migrated (0.443s)

Step 4: Create a model for the table

With the node_test database created and a page_loads table configured with a proper schema, we are ready to create the PageLoads model in our code. A model is an abstraction on the data stored in the table.

Above our app.use statement, add the following to index.js:

  1. let PageLoads = sequelize.define('page_loads', {
  2. userAgent: {type: Sequelize.STRING, primaryKey: true },
  3. time: {type: Sequelize.DATE, primaryKey: true }
  4. }, { timestamps: false });

You can now instantiate a PageLoads object and save it to the database.

Generate hypertable

In TimescaleDB, the primary point of interaction with your data is a hypertable, the abstraction of a single continuous table across all space and time intervals, such that one can query it via standard SQL.

Virtually all user interactions with TimescaleDB are with hypertables. Creating tables and indexes, altering tables, inserting data, selecting data, etc. can (and should) all be executed on the hypertable.

A hypertable is defined by a standard schema with column names and types, with at least one column specifying a time value.

tip

The TimescaleDB documentation on schema management and indexing explains this in further detail.

Let’s create this migration to modify the page_loads table and create a hypertable by first running the following command:

  1. npx sequelize migration:generate --name add_hypertable

You should get a result that looks like this:

  1. Sequelize CLI [Node: 12.16.2, CLI: 5.5.1, ORM: 5.21.11]
  2. migrations folder at [some_path] already exists.
  3. New migration was created at [some_path]/]20200601202912-add_hypertable.js .

And there should now be a file in your migrations folder that we can modify to look like the following:

  1. 'use strict';
  2. module.exports = {
  3. up: (queryInterface, Sequelize) => {
  4. return queryInterface.sequelize.query("SELECT create_hypertable('page_loads', 'time');");
  5. },
  6. down: (queryInterface, Sequelize) => {
  7. }
  8. };

Now run the migration command from the command-line:

  1. npx sequelize db:migrate

You should get the following result:

  1. Sequelize CLI [Node: 12.16.2, CLI: 5.5.1, ORM: 5.21.11]
  2. Loaded configuration file "config/config.json".
  3. Using environment "development".
  4. == 20200601202912-add_hypertable: migrating =======
  5. == 20200601202912-add_hypertable: migrated (0.426s)

Insert rows into TimescaleDB

Now you have a working connection to your database, a table configured with the proper schema, and a hypertable created to more efficiently query data by time. Let’s add data to the table.

In the index.js file, modify the / route like so to first get the user-agent from the request object (req) and the current timestamp. Then, call the create method on our model (PageLoads), supplying the user agent and timestamp parameters. The create call executes an INSERT on the database:

  1. app.get('/', async (req, res) => {
  2. // get the user agent and current time
  3. const userAgent = req.get('user-agent');
  4. const time = new Date().getTime();
  5. try {
  6. // insert the record
  7. await PageLoads.create({
  8. userAgent, time
  9. });
  10. // send response
  11. res.send('Inserted!');
  12. } catch (e) {
  13. console.log('Error inserting data', e)
  14. }
  15. })

Execute a query

Each time the page is reloaded, we also want to display all information currently in the table.

To do this, modify the / route in our index.js file to call the Sequelize findAll function and retrieve all data from the page_loads table via the PageLoads model, like so:

  1. app.get('/', async (req, res) => {
  2. // get the user agent and current time
  3. const userAgent = req.get('user-agent');
  4. const time = new Date().getTime();
  5. try {
  6. // insert the record
  7. await PageLoads.create({
  8. userAgent, time
  9. });
  10. // now display everything in the table
  11. const messages = await PageLoads.findAll();
  12. res.send(messages);
  13. } catch (e) {
  14. console.log('Error inserting data', e)
  15. }
  16. })

Now, when you reload the page, you should see all of the rows currently in the page_loads table.

Next steps

Now that you’re able to connect, read, and write to a TimescaleDB instance from your Node application, be sure to check out these advanced TimescaleDB tutorials: