Reading and Writing YAML to a File in Node.js/JavaScript

Introduction

In last few years YAML, which stands for YAML Ain't Markup Language, has become very popular for use in storing data in a serialized manner, typically configuration files. Since YAML essentially is a data format, the YAML specification, is fairly brief. Thus, the only functionality required of YAML libraries is the ability to parse or generate YAML-formatted files.

In this article we're going to see how you can use YAML with a Node.js application. We will start by seeing how data is stored in a YAML file, followed by loading that data into a JavaScript object. Lastly, we will learn how to store a JavaScript object in a YAML file.

There are a few popular Node libraries used for parsing and generating YAML: yaml and js-yaml. js-yaml is the more popular of the two libraries, so that's what we'll focus on for this article.

Before we move further, there are a few prerequisites for this tutorial. You should have a basic understanding of JavaScript's syntax, specifically for the Node.js runtime. You'll also want to have Node.js and NPM installed on your system to follow along. Other than that, the tutorial is quite simple and easy to follow for beginners.

Installation

As with any Node package, the installations is pretty simple using NPM:

$ npm install js-yaml

Just be sure to use the correct flags for saving the dependency in your package.json file. For example, if js-yaml is only used for development purposes, then use --save-dev, otherwise use --save if it's used in the production environment of your app.

You can verify that it's correctly installed by opening the REPL from the same directory and importing the package with the following:

$ node
> const yaml = require('js-yaml')
>

The js-yaml library also supports CLI usage, which allows you to inspect YAML files from the command line. You can enable this by installing the package globally:

$ npm install -g js-yaml

Reading YAML Files in Node.js

We'll start out by reading a YAML-formatted file and parsing it in to a JS object. For the sake of this tutorial, let's say we have a file, data.yaml, which has the following contents:

--- # Article data
  title: "Reading and Writing YAML to a File in Node.js/JavaScript"
  url path: "/reading-and-writing-yaml-to-a-file-in-node-js-javascript"
  domain: "stackabuse.com"
  port: 443
  is-https: true
  meta:
    published-at: "Nov. 1st, 2019"
    author:
      name: "Scott Robinson"
      contact: "[email protected]"
    tags:
      - javascript
      - node.js
      - web development

To read and parse this file, we'll be using the .safeLoad() method:

// read.js
const fs = require('fs');
const yaml = require('js-yaml');

try {
    let fileContents = fs.readFileSync('./data.yaml', 'utf8');
    let data = yaml.safeLoad(fileContents);

    console.log(data);
} catch (e) {
    console.log(e);
}

Running this code will output the following:

$ node read.js
{ title: 'Reading and Writing YAML to a File in Node.js/JavaScript',
  'url path': '/reading-and-writing-yaml-to-a-file-in-node-js-javascript',
  domain: 'stackabuse.com',
  port: 443,
  'is-https': true,
  meta:
   { 'published-at': 'Nov. 1st, 2019',
     author: { name: 'Scott Robinson', contact: '[email protected]' },
     tags: [ 'javascript', 'node.js', 'web development' ] } }

You can see that the data in the YAML file is now converted to JS literals and objects in the same structure as the file.

The .safeLoad() method is recommended for parsing YAML content since it is safe for untrusted data. One limitation worth noting is that this method does not support multi-document sources. If you're familiar with YAML, you'll know that YAML can contain multiple "documents" within a single file, which are separated with the --- syntax. For example:

--- # Programming language
  language: "JavaScript"
  created-at: "December 4, 1995"
  domain: "stackabuse.com"
  creator: "Brendan Eich"
--- # Website
  domain: "wikipedia.org"
  created-at: "January 15, 2001"
  num-languages: 304
  num-articles: 51360771
  creator:
    - Jimmy Wales
    - Larry Sanger

Loading this file with .safeLoad() will throw an exception. Instead, you should use the .safeLoadAll() method, like so:

// read-all.js
const fs = require('fs');
const yaml = require('js-yaml');

try {
    let fileContents = fs.readFileSync('./data-multi.yaml', 'utf8');
    let data = yaml.safeLoadAll(fileContents);

    console.log(data);
} catch (e) {
    console.log(e);
}

This results in an array of parsed YAML documents:

$ node read-all.js
[ { language: 'JavaScript',
    'created-at': 'December 4, 1995',
    domain: 'stackabuse.com',
    creator: 'Brendan Eich' },
  { domain: 'wikipedia.org',
    'created-at': 'January 15, 2001',
    'num-languages': 304,
    'num-articles': 51360771,
    creator: [ 'Jimmy Wales', 'Larry Sanger' ] } ]

Another method worth mentioning is the .load() method, which is very similar to .safeLoad(), except that it supports all YAML schema types. The extra supported types are specific to JavaScript (!!js/undefined, !!js/regexp, and !!js/function) and you must absolutely trust the data in these YAML files since it can load untrusted code.

For example, a function can be defined in YAML like the following:

'toString': !<tag:yaml.org,2002:js/function> function() {console.log('Malicious code execuited!');}

That tag tells our YAML library to parse it as a function, which can then be executed later. As pointed out in the documentation, one common method that is executed on JS objects is toString, which we can exploit like this:

// unsafe.js
const yaml = require('js-yaml');

let yamlStr = "'toString': !<tag:yaml.org,2002:js/function> function() {console.log('Malicious code execuited!');}";
let loadedYaml = yaml.load(yamlStr) + '';
console.log(loadedYaml);

And running this code shows that the console.log method is indeed executed:

$ node unsafe.js 
Malicious code execuited!
undefined

Writing YAML to Files in Node.js

Now that you know how to read YAML files with Node.js, let's see how we can write JavaScript objects/data to a YAML file.

For this example, we'll be using the following JS object, which you may recognize from the previous examples:

let data = {
    title: 'Reading and Writing YAML to a File in Node.js/JavaScript',
    'url path': '/reading-and-writing-yaml-to-a-file-in-node-js-javascript',
    domain: 'stackabuse.com',
    port: 443,
    'is-https': true,
    meta: {
        'published-at': 'Nov. 1st, 2019',
        author: {
            name: 'Scott Robinson',
            contact: '[email protected]'
        },
        tags: [
            'javascript', 'node.js', 'web development'
        ]
    }
};

In order to serialize this object and save it to a YAML-formatted file, we'll use the .safeDump() method, which again uses js-yaml's DEFAULT_SAFE_SCHEMA:

// write.js
const fs = require('fs');
const yaml = require('js-yaml');

let data = { /* Same as above */};

let yamlStr = yaml.safeDump(data);
fs.writeFileSync('data-out.yaml', yamlStr, 'utf8');

Executing this code will write out a YAML file that looks like this:

title: Reading and Writing YAML to a File in Node.js/JavaScript
url path: /reading-and-writing-yaml-to-a-file-in-node-js-javascript
domain: stackabuse.com
port: 443
is-https: true
meta:
  published-at: 'Nov. 1st, 2019'
  author:
    name: Scott Robinson
    contact: [email protected]
  tags:
    - javascript
    - node.js
    - web development

This output is almost identical to the original YAML we read earlier in the article, except that the document separator (---) is not included.

Data Types

It's important to keep in mind that not all JavaScript data types can be directly serialized to YAML, and vice versa. In some cases, the closest data type will be used when possible if it's not directly supported. For example, a YAML !!seq type will be parsed as a JavaScript array.

According to the js-yaml documentation, the following data types are supported:

YAML Type Example JS Type
!!null '' null
!!bool true bool
!!int 3 number
!!float 3.1415 number
!!binary c3RhY2thYnVzZS5jb20= buffer
!!timestamp '2013-08-15' date
!!omap [ ... ] array of key-value pairs
!!pairs [ ... ] array or array pairs
!!set { ... } array of objects with given keys and null values
!!str '...' string
!!seq [ ... ] array
!!map { ... } object

As mentioned earlier, other JS-specific types can also be supported, but only if you aren't using the "safe" methods.

Conclusion

YAML is an increasingly popular format used to structure data for applications, typically as configuration files, but also as a replacement for anything that JSON is used for. Due to its high flexibility and easy to read syntax, it is quickly replacing JSON in many projects, although both still have their place.

In this article we showed how you can use the js-yaml library to parse YAML files to JavaScript objects in Node.js and how you can serialize JavaScript objects to a YAML file. We also showed what JS types are used for various YAML data types. For more details on this library, check out the official documentation.