Create Your Own CSV Writer With Node.js

4/23/22

Writing CSV Files With Node.js

Today I am going to show you how to create your own csv writer with zero dependencies, other than the ones provided by Node.js out of the box. Our csv writer will be asynchronous by default, and will provide an easy to use Promise based API.

CSV Writer Public API

This is how I envision someone using our csv writer.

const CSV = require('./lib/csv');
const header = ['First', 'Second', 'Third'];

const csv = new CSV('./data.csv', header);

const data = [
  ['Hello', 'Hola', 'Bonjour'],
  ['Lake', 'River', 'Canyon'],
  ['Cheeseburger', 'Fries', 'Coke'],
];

csv
  .create(data)
  .then(() => {
    console.log('hooray!');
  })
  .catch((err) => {
    console.log(err);
  });

All that's required is that the user provides an array of strings that represents the header of the CSV, and the data formatted as a two dimensional array.

Creating A CSV Writer Class

To get started, let's create a folder called lib, and a file within it named csv.js. From within csv.js let's setup a class with a basic constructor.

const writeFile = require('fs').writeFile;

module.exports = class CSV {
  constructor(path, header, delimiter = ',') {
    this.path = path;
    this.header = header.join(this.delimiter) + '\n';
    this.delimiter = delimiter;
    this.mode = '';
  }
}

Notice how we've declared three parameters. Let's breakdown what each one does.

  • path: A string that represents where to write the CSV to on the file system.
  • header: An array of strings to be be used as the header of the CSV.
  • delimiter (optional): What character we will separate each rows values with. By default it is a comma.

Now that we have that setup, let's define our first method on the CSV class.

The Create Method

This method will be responsible for converting our two dimensional array into one large string. Remember, we want users to be eventually be able to call it like this:

csv
  .create(data)
  .then(() => {
    console.log('hooray!');
  })
  .catch((err) => {
    console.log(err);
  });

Let's pretend that I am from the future, and I tell you that when finished, our csv writer will overwrite data if the user chooses a file that already exists. To prevent this from happening, we should give the user the option to append to a file. I envision it being used like this:

csv
  .create(data, 'a')
  .then(() => {
    console.log('hooray!');
  })
  .catch((err) => {
    console.log(err);
  });

In this case a stands for append. We will use this with this.mode, which you can see below.

const writeFile = require('fs').writeFile;

module.exports = class CSV {
  constructor(path, header, delimiter = ',') {
    this.path = path;
    this.header = header.join(this.delimiter) + '\n';
    this.delimiter = delimiter;
    this.mode = '';
  }

  create(data, mode = 'w') {
    this.mode = mode;
  }
};

Notice how if you don't call create with a second argument, it will default to w mode, which means it will overwrite the file chosen (if it already exists). This will make more sense later on.

Stringifying Our Two Dimensional Array

The next step is for us to convert the two dimensional array into one long string. We can do so by adding the following to create:

const writeFile = require('fs').writeFile;

module.exports = class CSV {
  constructor(path, header, delimiter = ',') {
    this.path = path;
    this.header = header.join(this.delimiter) + '\n';
    this.delimiter = delimiter;
    this.mode = '';
  }

  create(data, mode = 'w') {
    this.mode = mode;

    const stringified =
      this.mode === 'a'
        ? data.join('\n') + '\n'
        : this.header + data.join('\n') + '\n';

    return this.write(stringified);
  }
};

Notice how we check to see if the user wants to use the append feature. If so, we don't write the header into our final string. However, if the user has not specified they would like to use the append mode, we make sure to include the header in our string.

You'll also notice I am calling another method, write, which we haven't defined yet. Let's implement that now.

The Write Method

In order to write csv data to the file system, we are going to create a write method on the class. This method will return a Promise that wraps the Node.js file system method writeFile.

const writeFile = require('fs').writeFile;

module.exports = class CSV {
  constructor(path, header, delimiter = ',') {
    this.path = path;
    this.header = header.join(this.delimiter) + '\n';
    this.delimiter = delimiter;
    this.mode = '';
  }

  create(data, mode = 'w') {
    this.mode = mode;

    const stringified =
      this.mode === 'a'
        ? data.join('\n') + '\n'
        : this.header + data.join('\n') + '\n';

    return this.write(stringified);
  }

  write(str) {
    return new Promise((resolve, reject) => {
      writeFile(this.path, str, { flag: this.mode }, (err) => {
        if (err) {
          reject(err);
        }
        resolve();
      });
    });
  }
};

Seeing as we added return this.write(stringified); at the end of our create method, create is now returning a Promise. When writeFile has finished, it will resolve the Promise, and the user can go along their merry way.

Running Our Node.js Code

Congratulations, we are done! Now all we need to do is run the code that we defined at the beginning of the tutorial.

const CSV = require('./lib/csv');
const header = ['First', 'Second', 'Third'];

const csv = new CSV('./data.csv', header);

const data = [
  ['Hello', 'Hola', 'Bonjour'],
  ['Lake', 'River', 'Canyon'],
  ['Cheeseburger', 'Fries', 'Coke'],
];

csv
  .create(data)
  .then(() => {
    console.log('hooray!');
  })
  .catch((err) => {
    console.log(err);
  });

This should result in a csv file being written to the system, and will look like this:

First,Second,Third
Hello,Hola,Bonjour
Lake,River,Canyon
Cheeseburger,Fries,Coke

Summary

That's about it for writing files to csv with Node.js! If you have any problems understanding any of this, feel free to shoot me a message, and I'll make sure it's crystal clear.

P.S. Here is a full working version on Github.

Want To Level Up Your JavaScript Game?

Book a private session with me, and you will be writing slick JavaScript code in no time.