Introduction
In the 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 into 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
article:
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.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
For example, a function can be defined in YAML like the following:
'toString': !<tag:yaml.org,2002:js/function> function() {console.log('Malicious code executed!');}
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 executed!');}";
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 executed!
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.