Using TOML Config Files in Your Node.js Applications

toml files
In this article, we’ll learn how to use TOML, a rising star ⭐in the configuration file world, with Node.js. After reading this article, you will understand the TOML syntax including how to use it to model JavaScript objects in your Node applications.

After recently finishing my tutorial on Using INI Files with Node.js, I stumbled across the TOML configuration file format when conducting some focused learning on Rust. It turns out that Cargo, Rust’s package manager, uses TOML for its Cargo.toml configuration file, which serves in a similar capacity to the package.json file used by npm with Node. I excitedly delved into TOML and to learn more, and emerged quite impressed with the versatility and simplicity of this TOML format. Let’s get started and learn about TOML using the same examples I used in my INI tutorial. We’ll also juxtapose TOML with the INI syntax along the way.

Article contents

What is TOML?

TOML is a syntax used for configuration files that aims to be simple to read and minimal. TOML is an acronym for “Tom’s Obvious, Minimal Language” and was created, not surprisingly, by a guy named “Tom”—specifically Tom Preston-Werner. Let’s jump right in and consider a sample TOML configuration file for retrieving weather ☀ data:

# Weather configuration data
degreeType = "F"

cities = [
"San Diego, CA",
"Chicago, IL",
"Tokyo, Japan"
]

[fields]
temperature = "Temp"
windspeed = "Wind"
skytext = "Text"

Comments begin with a pound sign (#) as shown in line 1. This differs from INI files that use semicolons for comments.

In line 2, we declare a property and its associated value (a key/value pair). Please note that all string values must be quoted in double quotes as shown, unlike INI files that do not require strings to be quoted.

We declare a multiline array beginning in line 4. The array can also be constructed on one line, but I prefer multiline arrays so that individual array elements can be more easily commented out temporarily without impacting the rest of the array in the configuration.

In line 10, we declare a “table” called fields. In TOML parlance, tables (also known as hash tables or dictionaries) are collections of key/value pairs.

There are definite benefits with the TOML file format over other formats! It is simple and clean and offers advantages over JSON/JS configuration file syntax in some contexts. Let’s get started and write some Node.js code that utilizes TOML configuration files.

Initial Setup

To get started, we’ll install the popular toml package created by Michelle Tilley (aka BinaryMuse).

First, create a default package.json file if you don’t already have one:

$ npm init -y

Next, install the TOML package and save it as a dependency in the package.json file:

$ npm install --save toml

Creating and reading our first TOML file

We’re planning to retrieve data from a weather service, and we’ll start by creating a configuration file we can use to specify if we want the temperature in degrees Fahrenheit or degrees Celsius. Let’s call it config.toml:

# Weather configuration data
degreeType = "F"

Our very basic configuration file includes a comment and a property for degreeType.

Let’s read and parse the TOML configuration file and retrieve the value of degreeType:

const fs = require('fs');
const toml = require('toml');

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));
const degreeType = config.degreeType;

console.log(degreeType);

We use the require function in lines 1-2 to import the fs and toml modules and make them available to our program.

In line 4, we read our TOML file and parse it into an object named config.

Voilà! We now have a config object containing the information we stored in our configuration file. Retrieving the degreeType in line 5 is trivial.

Let’s get the current weather!

weather

Let’s make it more interesting and use our INI configuration to retrieve the current weather. Here we go!

First, we’ll get the weather-js module to help us:

$ npm install --save weather-js

Next, we’ll create a file called weather.js to make our own weather module to use for the next exercises:

const weather = require('weather-js');

const getCurrent = (city, degreeType) =>
  new Promise((resolve, reject) => {
    weather.find({ search: city, degreeType }, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result[0].current);
      }
    });
  });
module.exports = getCurrent;

Notice that we create a Promise to retrieve the weather and return a JavaScript object containing the current weather data for a given city.

Using our same TOML config file, let’s test our function and ensure it works:

const fs = require('fs');
const toml = require('toml');
const weather = require('./weather');

const city = 'San Diego, CA';

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));
const degreeType = config.degreeType;

console.log(degreeType);

weather(city, degreeType)
  .then(result => console.log(result))
  .catch(error => console.log(error));

We hardcode the city to retrieve (San Diego) in line 5 and then invoke our newly created weather function in line 12. The following JavaScript object is returned:

{ temperature: '67',
  skycode: '30',
  skytext: 'Partly Sunny',
  date: '2018-01-12',
  observationtime: '14:20:00',
  observationpoint: 'San Diego, CA',
  feelslike: '67',
  humidity: '66',
  winddisplay: '6 mph Northwest',
  day: 'Friday',
  shortday: 'Fri',
  windspeed: '6 mph',
  imageUrl: 'http://blob.weather.microsoft.com/static/weather4/en-us/law/30.gif' }

Very good – this gives us a clear picture of the various properties we can display with our results.

As a bonus exercise, change the degreeType in your TOML configuration file to “C” and rerun the program. You should see the temperature in degrees Celsius this time around!

Retrieve TOML array data

Let’s expand our TOML file to include a section for cities:

# Weather configuration data
degreeType = "F"

cities = [
"San Diego, CA",
"Chicago, IL",
"Tokyo, Japan"
]

We could also express our array on one line like this:

cities = [ "San Diego, CA", "Chicago, IL", "Tokyo, Japan" ]

As mentioned previously, I prefer multiline arrays so that individual array elements can be more easily commented out temporarily without impacting the rest of the array configuration.

Back to our regularly schedule program… 📺

We can now get the cities array from the TOML configuration:

const fs = require('fs');
const toml = require('toml');

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));

console.log(config.cities);

This program returns the following results:

[ 'San Diego, CA', 'Chicago, IL', 'Tokyo, Japan' ]

We’re on the right track! Let’s revise our program to utilize the cities array data and return the weather for each city:

const fs = require('fs');
const toml = require('toml');
const weather = require('./weather');

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));
const degreeType = config.degreeType;

for (const city of config.cities) {
  weather(city, degreeType)
    .then(current => {
      const temperature = current.temperature;
      console.log(`${city}\t${temperature} deg${degreeType}`);
    })
    .catch(error => console.log(error));
}

Bam! The program output returned looks something like this:

Tokyo, Japan    36 degF
Chicago, IL     21 degF
San Diego, CA   66 degF

Perfect. Let’s update our TOML configuration file to include a list of fields to return from the Weather service:

# Weather configuration data
degreeType = "F"

cities = [
"San Diego, CA",
"Chicago, IL",
"Tokyo, Japan"
]

fields = [
"temperature",
"windspeed",
"skytext"
]

We create a second multiline array for fields, and then we can modify our code to retrieve the field values for each city:

const fs = require('fs');
const toml = require('toml');
const weather = require('./weather');

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));
const degreeType = config.degreeType;

for (const city of config.cities) {
  weather(city, degreeType)
    .then(current => {
      current.temperature += ` deg${degreeType}`;
      const data = config.fields.map(f => current[f]).join('\t');
      console.log(`${city}\t${data}`);
    })
    .catch(error => console.log(error));
}

Excellent! Our new output now looks something like this:

Tokyo, Japan    36 degF 9 mph   Mostly Sunny
Chicago, IL     20 degF 22 mph  Light Snow
San Diego, CA   67 degF 8 mph   Partly Sunny

What if we don’t want to see the Chicago data temporarily? We can simply comment out the Chicago entry in the array while leaving the rest of the array intact:

# Weather configuration data
degreeType = "F"

cities = [
"San Diego, CA",
# "Chicago, IL",
"Tokyo, Japan"
]

fields = [
"temperature",
"windspeed",
"skytext"
]

Run the program again and you will no longer see the Chicago weather. That configuration file update was easy!

Retrieve fields and values from a table

We’d like to include custom heading titles for each of the weather fields returned from our cities. We’ll start by updating the TOML configuration file:

# Weather configuration data
degreeType = "F"

cities = [
"San Diego, CA",
"Chicago, IL",
"Tokyo, Japan"
]

[fields]
temperature = "Temp"
windspeed = "Wind"
skytext = "Text"

In line 10, we are now using a “table” called fields instead of an array. In TOML, tables (also known as hash tables or dictionaries) are collections of key/value pairs. They appear in square brackets on a line by themselves. You can tell them apart from arrays because arrays are assigned values using the equals sign.

The fields table is parsed by the toml npm package into the following JavaScript object:

{
  "temperature": "Temp",
  "windspeed": "Wind",
  "skytext": "Text"
}

We are then able to use, for example, “Temp” as the column heading for the temperature field returned, etc. Here’s how it’s done:

const fs = require('fs');
const toml = require('toml');
const weather = require('./weather');

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));
const degreeType = config.degreeType;

const fields = Object.keys(config.fields);

const fieldHeadings = fields.map(f => config.fields[f]).join('\t');
console.log(`City\t\t${fieldHeadings}`);

for (const city of config.cities) {
  weather(city, degreeType)
    .then(current => {
      current.temperature += ` deg${degreeType}`;
      const data = fields.map(f => current[f]).join('\t');
      console.log(`${city}\t${data}`);
    })
    .catch(error => console.log(error));
}

We use the fields table returned in line 8 to accomplish the magic. And…here are the results returned (including our custom heading names):

City            Temp    Wind    Text
San Diego, CA   67 degF 8 mph   Partly Sunny
Chicago, IL     20 degF 22 mph  Light Snow
Tokyo, Japan    36 degF 9 mph   Mostly Sunny

Inline tables

To complete our tutorial, let’s explore some other features of TOML configuration files starting with inline tables. We’ll use an array of baseball players from the days of yore to help us.

baseball

Inline tables can be used as a more compact syntax to include the table (key/value pair collection) on one line. Here’s an example of an inline table in context with a regular table:

# Inline table
player = { Name = "Joe Rudi", Position = "First Base", BattingAvg = 0.278 }

[player2]
Name = "Sal Bando"
Position = "Third Base"
BattingAvg = 0.230

Here’s the code to retrieve each table:

const fs = require('fs');
const toml = require('toml');

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));

console.log(config.player);
console.log(config.player2);

The results returned are:

{ Name: 'Joe Rudi', Position: 'First Base', BattingAvg: 0.278 }
{ Name: 'Sal Bando', Position: 'Third Base', BattingAvg: 0.23 }

Array of tables

Let’s now examine how we can use a TOML data type called an array of tables. These can be expressed by using a table name in double brackets. Each table with the same double bracketed name included as an element in the same array. The tables are inserted in the order encountered. Here we go:

[[players]]
Name = "Reggie Jackson"
Position = "Right Field"
BattingAvg = 0.253

[[players]]
Name = "Joe Rudi"
Position = "First Base"
BattingAvg = 0.278

[[players]]
Name = "Sal Bando"
Position = "Third Base"
BattingAvg = 0.230

This TOML array of three tables translates into the following JavaScript object:

[
  {
    "Name": "Reggie Jackson",
    "Position": "Right Field",
    "BattingAvg": 0.253
  },
  {
    "Name": "Joe Rudi",
    "Position": "First Base",
    "BattingAvg": 0.278
  },
  {
    "Name": "Sal Bando",
    "Position": "Third Base",
    "BattingAvg": 0.23
  }
]

Let’s parse the TOML configuration and print the results to the console:

const fs = require('fs');
const toml = require('toml');
const cTable = require('console.table');

const config = toml.parse(fs.readFileSync('./config.toml', 'utf-8'));

console.table(config.players);

We install and use the handy console.table package to make it easier to print the results in a beautiful way:

Name            Position     BattingAvg
--------------  -----------  ----------
Reggie Jackson  Right Field  0.253
Joe Rudi        First Base   0.278
Sal Bando       Third Base   0.23

Additional data types

TOML supports several data types in a strongly typed fashion so let’s explore these quickly now using Reggie Jackson ⚾ to help us.

[player]
Name = "Reggie Jackson"
Position = "Right Field"
BattingAvg = 0.253
JerseyNumber = 9
AllStar = true
BirthDate = 1946-05-18T00:00:00Z
Summary = """
Jackson was nicknamed \"Mr. October\" for his clutch hitting in the postseason with the Athletics and the Yankees. He helped Oakland win five consecutive American League West divisional pennants."""

The player table demonstrates the use of several TOML data types and features.

Line 4 uses a float to represent the batting average. In this context, a zero must precede the number since the number is less than one. A value of “.253” cannot be parsed, but “0.253” can. The JavaScript object returned includes the float as a number instead of a string, as expected.

Integer values are used in line 5 since baseball jersey numbers will always be integers.

A Boolean value, demonstrated in line 6, is always expressed in lower case.

A local date-time value is included in line 7. TOML expects this to be specified in RFC 3339 format which is the format shown in the configuration file with a “T” separating the date and time components of the timestamp. TOML also supports dates without the time component, but the Node toml parser we are using was not supporting this syntax at the time of this writing.

Finally, beginning in line 8, we include a multi-line basic string value. This string value is surrounded by three quotation marks on each side and supports newlines. Notice that we also escape quotation marks in the string using a backslash character.

The resulting JavaScript object returned looks like this:

{
  "Name": "Reggie Jackson",
  "Position": "Right Field",
  "BattingAvg": 0.253,
  "JerseyNumber": 9,
  "AllStar": true,
  "BirthDate": "1946-05-18T00:00:00.000Z",
  "Summary": "Jackson was nicknamed \"Mr. October\" for his clutch hitting in the postseason with the Athletics and the Yankees. He helped Oakland win five consecutive American League West divisional pennants."
}

Very awesome! The TOML format supports a variety of data types beyond the INI file syntax. Don’t be surprised that the Summary field appears as one continuous line and causes a great deal of horizontal scrolling in the object above. TOML also includes options to force line breaks in multiline strings. You can read more about the details and nuances of the TOML file syntax here.

Bonus TOML tools 🔨

If you are using Visual Studio Code for developing your Node.js applications, I recommend the TOML Language Support extension for syntax highlighting of your TOML config files. Also visit the TOML wiki for a list of other languages, editors, and tools related to TOML.

Conclusion

TOML files provide a very simple and versatile format, and you are now equipped! Your call of action is to go find some contexts to use the TOML file format in your Node.js applications.

Additional articles

Using INI Files in Your Node.js Applications for Health and Profit
Getting Started with YAML in Node.js
Guide to Using Redis with Node.js
Making Interactive Node.js Console Apps That Listen for Keypress Events

Follow @thisDaveJ (Dave Johnson) on Twitter to stay up to date with the latest tutorials and tech articles.

Share

5 thoughts on “Using TOML Config Files in Your Node.js Applications

    1. Hi Marc, there is an npm package called json2toml that allows one to convert JSON objects to TOML. It may or may not handle all of the edge cases of the TOML spec, but it might work for your needs. You could, for example, use the toml npm package described in my article to read/parse user preferences from a TOML file, make changes to the resulting JSON object during the user session, convert the JSON object back to a TOML string using json2toml, and write the TOML string to a file using the built-in Node fs module.

Leave a Reply

Your email address will not be published. Required fields are marked *