Getting Started with YAML in Node.js using js-yaml

YAML

In this tutorial, we harness the power of YAML for use within Node.js. As described on the official YAML site, YAML (YAML Ain’t Markup Language) is a “human friendly data serialization standard for all programming languages”. YAML and JSON are closely related. In fact, all JSON syntax is valid YAML as of the YAML 1.2 spec, but not all YAML syntax is valid JSON. YAML is a superset of JSON.

Why Use YAML in Node.js?

In the world of Node, we spend a lot of time parsing JSON and using JSON as a syntax for configuration files often makes a lot of sense. For example, if we create a file called config.json:

{
  "domain": "www.mysite.com",
  "mongodb": {
    "host": "localhost",
    "port": 27017
  }
}

We can then utilize the simplicity and elegance of the Node module loading system to both load and parse our configuration file in one step:

const config = require('./config.json');

const domain = config.domain;
const host = config.mongodb.host;

console.log(`The domain is ${domain} and the MongoDB host is ${host}`);

How awesome is that? Why would we ever want to consider using YAML instead of JSON? Many times JSON will meet our needs, but there are some contexts where creating our configuration files in YAML can add value. YAML provides the following benefits over JSON:

  • Cleaner syntax that is often more readable than JSON
  • Supports comments to provide additional hints and instructions to those editing the configuration files
  • Includes a feature called anchors that enables content to be duplicated across a document.
  • Includes a feature called merging which behaves a bit like inheritance in object-oriented programming.

We will create an email configuration system later in this tutorial to demonstrate the power of anchors and merging in YAML. You won’t be able to do this in pure JSON!

First Steps with YAML in Node.js

That’s enough background. Let’s get started writing some YAML code in Node.js! We will first create a simple YAML configuration file called test.yml with the following contents:

fruit:
- apple
- orange
- banana

Next, we’ll install the awesome js-yaml npm package to help us do the heavy lifting for the YAML deserialization of our YAML configuration file. At the time of this writing, the js-yaml package had almost 9 million monthly downloads. It’s obvious that there are a lot of Node.js developers using YAML—and specifically using the js-yaml package. Let’s install this package:

$ npm install --save js-yaml

Next, let’s create some code to read our YAML configuration file and display the results:

const yaml = require('js-yaml');
const fs = require('fs');

try {
    const config = yaml.safeLoad(fs.readFileSync('test.yml', 'utf8'));
    const indentedJson = JSON.stringify(config, null, 4);
    console.log(indentedJson);
} catch (e) {
    console.log(e);
}

Notice that we use the additional JSON.stringify parameters to enable us to “pretty print” the JSON object. We insert 4 spaces at each hierarchy level for readability purposes.  Here is the console output:

{
    "fruit": [
        "apple",
        "orange",
        "banana"
    ]
}

Since our YAML configuration file has been converted to an object, we can also iterate through the elements in our object as follows:

const yaml = require('js-yaml');
const fs = require('fs');

try {
    const config = yaml.safeLoad(fs.readFileSync('test.yml', 'utf8'));
    for (const fruit of config.fruit) {
        console.log(fruit);
    }
} catch (e) {
    console.log(e);
}

Working with More Complex YAML syntax

The YAML Wikipedia page provides an excellent summary of the YAML syntax as well as a fantastic example we will utilize now to demonstrate additional YAML syntax features. Create a new YAML configuration file called test2.yml with the following contents from Wikipedia:

receipt:     Oz-Ware Purchase Invoice
date:        2012-08-06
customer:
    first_name:   Dorothy
    family_name:  Gale

items:
    - part_no:   A4786
      descrip:   Water Bucket (Filled)
      price:     1.47
      quantity:  4

    - part_no:   E1628
      descrip:   High Heeled "Ruby" Slippers
      size:      8
      price:     133.7
      quantity:  1

bill-to:  &id001
    street: |
            123 Tornado Alley
            Suite 16
    city:   East Centerville
    state:  KS

ship-to:  *id001

specialDelivery:  >
    Follow the Yellow Brick
    Road to the Emerald City.
    Pay no attention to the
    man behind the curtain.

This example steps it up several notches demonstrating several YAML features. I want to call your attention to the anchor feature that is used to set the ship-to address to be the same as the bill-to address. The bill-to object is anchored with an ampersand (&id001) and then referenced later with an asterisk (*id001). This powerful feature enables us to avoid typing duplicate information. This YAML Cheat Sheet provides a concise summary of many other YAML features demonstated in the YAML configuration above.

Important node: YAML utilizes spaces (and not tabs) to define the hierarchy of data. When entering data in your editor, make sure that you are using spaces and not tabs. I have my editor (Visual Studio Code) configured to insert 4 spaces every time I hit the tab key.

Let’s go ahead and use js-yaml to parse the contents of our YAML file into a JSON object:

const yaml = require('js-yaml');
const fs = require('fs');

try {
    const config = yaml.safeLoad(fs.readFileSync('test2.yml', 'utf8'));
    const indentedJson = JSON.stringify(config, null, 4);
    console.log(indentedJson);
} catch (e) {
    console.log(e);
}

We see the following results when invoking our Node.js program from the terminal:

$ node test-yaml2.js
{
    "receipt": "Oz-Ware Purchase Invoice",
    "date": "2012-08-06T00:00:00.000Z",
    "customer": {
        "first_name": "Dorothy",
        "family_name": "Gale"
    },
    "items": [
        {
            "part_no": "A4786",
            "descrip": "Water Bucket (Filled)",
            "price": 1.47,
            "quantity": 4
        },
        {
            "part_no": "E1628",
            "descrip": "High Heeled \"Ruby\" Slippers",
            "size": 8,
            "price": 133.7,
            "quantity": 1
        }
    ],
    "bill-to": {
        "street": "123 Tornado Alley\nSuite 16\n",
        "city": "East Centerville",
        "state": "KS"
    },
    "ship-to": {
        "street": "123 Tornado Alley\nSuite 16\n",
        "city": "East Centerville",
        "state": "KS"
    },
    "specialDelivery": "Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.\n"
}

The bill-to object and the ship-to object match as expected!

Real-world Example: Email Configuration system

Let’s jump into a real world example that utilizes YAML for building the data structures used for sending out email messages to various distribution lists within a company. This showcases additional YAML features including merging and demonstrates how YAML syntax can be useful over the standard JSON syntax. I created the following YAML file for our use:

# Transport for sending message
transport:
    service: Gmail
    auth:
        user: thisdavej@gmail.com
        pass: mypassword

management: &management
    - jgarcia1@unc.edu: Jean Garcia
    - agriffin2@timesonline.co.uk: Anthony Griffin
    - ebowman3@google.com.au: Earl Bowman

operations: &operations
    - nhall5@ezinearticles.com: Nicholas Hall
    - bwelch6@upenn.edu: Betty Welch
    - kburke7@bloglovin.com: Kimberly Burke
    - mross8@creativecommons.org: Maria Ross
    - lmorgan9@pcworld.com: Lonnie Morgan

marketing: &marketing
    - rstanley4@printfriendly.com: Ruby Stanley
    - jlyncha@dion.ne.jp: Joseph Lynch
    - emcdonaldb@ezinearticles.com: Edward Mcdonald
    - dgeorgec@aol.com: Deborah George

message_defaults: &message_defaults
    from: info@acme.com

management_message:
    <<: *message_defaults
    recipients:
        <<: *management

operations_message:
    <<: *message_defaults
    from: operations@acme.com
    recipients:
        <<: *operations

all_employees_message:
    <<: *message_defaults
    recipients:
        <<: *management
        <<: *operations
        <<: *marketing

There’s a lot going on in this YAML configuration! I will seek to hit some of the high points. We first define the transport information we will need to successfully send an email message through our provider. Next, we create distribution lists to list the recipients who will receive the email messages. Finally, we define messages associated with each distibution list. We use anchors, references, and merge formatting (for example, <<: *message_defaults) to create our data structures and avoid retyping information.

I have included some sample Node.js source code below that could be used to leverage this YAML configuration file and send out email messages. The beauty of configuration files (YAML or other) is that you can simply modify a configuration file rather than touching code to change your email distribution lists and other facets related to sending email messages. Here is the code:

const yaml = require('js-yaml');
const fs = require('fs');

let config = {};
try {
    config = yaml.safeLoad(fs.readFileSync('email.yml', 'utf8'));
} catch (e) {
    console.log(e);
}

// Our mock function that would do the heavy lifting of sending the email message.
// The mailOptions object is designed to be compatible with Nodemailer
function sendEmail(transport, mailOptions) {
    console.log(mailOptions);
}

// Shape the recipients into an array
function getRecipients(recipients) {
    const to = [];
    for (const email in recipients) {
        const name = recipients[email];
        const address = name ? `${name} <${email}>` : email;
        to.push(address);
    }
    return to;
}

function sendMessageCore(message, subject, body) {
    const mailOptions = {
        from: message.from,
        to: getRecipients(message.recipients),
        subject,
        html: body,
    };
    sendEmail(config.transport, mailOptions);
}

function sendMessageToManagement(subject, body) {
    const message = config.management_message;
    sendMessageCore(message, subject, body);
}

function sendMessageToOperations(subject, body) {
    const message = config.operations_message;
    sendMessageCore(message, subject, body);
}

function sendMessageToAllEmployees(subject, body) {
    const message = config.all_employees_message;
    sendMessageCore(message, subject, body);
}

sendMessageToManagement('Management Update', 'Here is the body of the message');

sendMessageToOperations('Operations Update', 'Here is the body of the message');

sendMessageToAllEmployees('All Employees Update', 'Here is the body of the message');

In this code sample, we ultimately arrive at some simple functions such as sendMessageToOperations that can be used to send email messages by supplying a subject and a body. All of the other details including email recipients are handled through the YAML configuration file we created. Powerful stuff!

Conclusion

YAML does provide some benefits over JSON for serializing and deserializing data, and thus it can be a good choice in some contexts as a format for Node.js configuration files. We learned how to parse YAML in Node.js using the js-yaml npm package, we built a real world example for sending email messages, and we had a good time doing it. 🙂

Follow @thisDaveJ on Twitter to stay up to date on the latest tutorials and tech articles.

Additional Articles

Beginner’s Guide to Installing Node.js on a Raspberry Pi
Using Winston, a versatile logging library for Node.js
Using Visual Studio Code with a Raspberry Pi (Raspbian)
Visual Studio Code Jumpstart for Node.js Developers

Share

One thought on “Getting Started with YAML in Node.js using js-yaml

Leave a Reply

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