Writing Files with Node.js and JSDOM

Danny Lee
8 min readMay 9, 2020

--

Last week was full of ups and downs, I managed to snag a technical interview (post-cultural interview) through Angel.co with a small 5-person startup that recently received $1.1 million in funding (Mid-march 2020). Also, I heard back from a junior developer farming company that I passed my initial interview and first-level technical assessment. So, its definitely had its ups and downs, what were the downs you ask?

Well, the technical interview was quite humbling, and I didn’t “pass” on to the next level, but it was a great experience. I’ve only had a handful of technical interviews so far, but the interviews I have had with people who have founded, co-founded or been CTO of a startup, are vastly better experiences than the ones with other developers. For one, they care about their companies deeply and are not just looking for options that are “safe” or that they will be able to justify to their bosses. Also, they have been much more willing to take time and share what they think will help my development as a programmer.

It wasn’t the kind of interview I’ve gotten with mid or senior level developers, many of whom might be great programmers, but lack training as an interviewer. The couple of experiences I‘ve had interviewing with developers have basically been wastes of both of our time. With a varied background like mine (telco cable splicer, real estate broker, handyman, construction pm) with junior developer experience, there’s no way an employee could justify hiring me over the many CS major graduates out there in the same junior developer stage. The only people who could even dare to take a chance have to be “in-charge”.

Source: https://media.giphy.com/media/lcjWzvc9po5Og6eV4V/giphy.gif

That being said, I’m still confident and hopeful for my future. I know I have a goal — which is to master software development. And it’s only been 8 months, give me another 28 months at this and I know I will be a solid developer. There’s a lot more I have to share about hiring, starting and growing a business, and goals — but, let me share that another time, and for now let me get back to writing about the interview this week.

It was different in that it felt more thoughtful. Thoughtful in the sense that the issues in the example code were real world issues. And that the interviewer wasn’t just interested in stumping me or in making me feel like I was being challenged to some kind of duel. But, it was definitely a test to see how intimately I knew Javascript (the language I said I knew best).

So, how did I do? Well, not perfectly. I stumbled my way through 2 of the 5 questions that were prepared in the 30 minutes allotted to me. They were not very difficult problems, but they were laden with the kind of errors that would keep me debugging for hours. Things like the wrong type of return value from one function, used as the input to another and causing unusual behavior in another part of the program. The kind of error that comes from not knowing javascript completely and not knowing the “edge and corner cases”.

There’s tons of these kind of weird parts of javascript and anomalies, there’s also tons of sites and books that document and explore them. If this was the only thing missing in my repertoire I might have done okay. But, I was tripped up by little things like what an empty Array.find() returns. FYI, it returns undefined , I thought it returned an empty array. This kind of limited understanding can throw code into a tailspin if you just don’t know the specifics and rely on google or console.log() to identify what everything is.

At one point I thought there was a method called to_i in javascript (its actually Ruby) and Number.parseInt() completely slipped my mind. These are things that I relied on google to resolve — these little slips of mind, the cloudy, vague areas of my knowledge which come and go depending on the day of the week, it seems. The best piece of advice I came away with was: Master a language, whether JS or Ruby, and know it completely and thoroughly.

I remember having this intention early on in my software development journey, but somehow it got muddled as I became more exposed to different technologies. I started wandering down tangential paths and ended up learning about software and services more related to devops, or trying to understand project management strategies like agile and waterfall, and most recently, algorithms and data structures. All useful and important, but perhaps, a bit premature to my complete understanding and mastery of javascript and/or ruby. I hope I don’t forget this important learning! 😊

Source: https://media.giphy.com/media/mrBEVU9zQIsZa/giphy.gif

Intro to the Problem of the Week:

This week I have another interesting problem that might not be common to come across, but was amusing to solve. If you have alternative solutions to making this work I’d love to hear them!

The issue: I’ve been working on drawing a heart shape using the Canvas API, within a square shaped canvas. I want to pull out the coordinates of every pixel within the heart shape and save them in a database so I can randomly pluck one coordinate and recolor it.

My solution: I wrote a function that iterated over each pixel in the entire square shaped canvas (currently 225px square or 50,625 pixels) and checked to see if pixel was colored or not. If it was colored, an id equal to the for loop counter was assigned to a newly created Object and the following key and value pairs were added and the object was placed in the array position equal to id :

[
{
“id”:1,
”x”:100,
”y”:100,
”message_to”:””,
”message_from”:””,
”message_body”:””
},
{
next object....
}
]

The other part of this problem is that I want to get this into some sort of database. The first idea that came to mind was to just build a fetch and send it all to a Ruby on Rails backend, but do I really want to send 50,000 objects back and forth each time? Maybe it would be better to write the file to a JSON file and read it using a tool I used at the Flatiron School (the #1 rated bootcamp by coursereport.com) 😉 that tool is json-server available at npm.

How to write a file with Javascript

The first thing I tried was to use this snippet for Node.js to write a file in a javascript file written for the front end:

const fs = require('fs');

fs.writeFile("/tmp/test", "Hey there!", function(err) {
if(err) {
return console.log(err);
}
console.log("The file was saved!");
});

// Or
fs.writeFileSync('/tmp/test-sync', 'Hey there!');

I added it to my javascript file and tried to call it from my index.html, but got the following error in my Chrome console:

Uncaught ReferenceError: require is not defined at index.js:1

This is because require() does not exist in the browser/client-side JavaScript.” — jp richardson. He goes on to share different ways we can include Node.js modules in front-end applications. Too deep to go into here, and very deep when you start learning the specifics of each tool.

Another way that I did NOT choose to use is by creating a blob in a front-end javascript file and using a download button to create the file, like in this example application. Its a way to take data in the front-end, without interacting with a server and making it available for download to the user.

How I Did It — JSDOM

After receiving that error, and some research, I decided to just write the script as a Node.js file and since I only need to generate this list of coordinates once and save it to a JSON file, it would be sufficient. Afterwards, I would load that JSON file in the json-server and manipulate it as I needed.

The issue I ran into was my code was written to interact with a canvas and to work with a canvas I needed a document . I took it for granted that Node.js would generate that for you, but it’s not, granted that is.

(Side note: The code for the heart shape I used is from Eric Rowell’s excellent book on Canvas — HTML 5 Canvas Cookbook (amazon, google books), highly recommended for those looking for a deep dive into Canvas amazing depth and flexibility.)

The first two lines of nearly any script using canvas are:

var canvas = document.getElementById('canvas');var context = canvas.getContext('2d');

However, in my case there was no element with ID of ‘canvas’, because there was no document when I ran $ node script.js . I no longer got the error stating that require is not defined but now I had another problem. Node.js doesn’t create a document for you. After some more research, I came across a module/package that does just this for Node.js file. Its called JSDOM (github, npm, tutorial at testim.io) and allows you to create a window and document in your Node.js environment.

⚠️ Warning: this does have its dangers, which you can read about on the github Readme. These involve the potential for untrusted and malicious scripts having access to the Node.js environment, and ultimately, your machine.

In my case, I just needed a quick and dirty script to generate a list of coordinates one time. So, I added the code to require the jsdom module, did an NPM install and then created a document :

const fs = require('fs');const jsdom = require("jsdom")l;const { JSDOM } = jsdom;const { document } = (new JSDOM(`...`)).window;

And then, instead of using getElementById('canvas') I substituted createElement() :

var canvas = document.createElement('canvas');

So, now I have my document, I created my canvas, I’m setting my context and everything looks ready to go, right? Of course, no:

$ node index.js  

Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (/Users/dannylee/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (/Users/dannylee/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (/Users/dannylee/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:102:58)
at Object.<anonymous> (/Users/dannylee/git/mm/goodbye/index.js:10:22)
at Module._compile (internal/modules/cjs/loader.js:1121:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1160:10)
at Module.load (internal/modules/cjs/loader.js:976:32)
at Function.Module._load (internal/modules/cjs/loader.js:884:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:67:12)
at internal/main/run_main_module.js:17:47 undefined
/Users/dannylee/git/mm/goodbye/index.js:13
context.save();
^
TypeError: Cannot read property 'save' of null
at drawHeart (/Users/dannylee/git/mm/goodbye/index.js:13:11)
at Object.<anonymous> (/Users/dannylee/git/mm/goodbye/index.js:53:1)
at Module._compile (internal/modules/cjs/loader.js:1121:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1160:10)
at Module.load (internal/modules/cjs/loader.js:976:32)
at Function.Module._load (internal/modules/cjs/loader.js:884:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:67:12)
at internal/main/run_main_module.js:17:47
$

Apparently, even though I now have a document I still need to install the NPM package/module canvas (npm) to gain access to the createCanvas() method. I quickly open up the terminal and:

$ npm add canvas

Add the module as a require to my js file:

const { createCanvas } = require('canvas')

And voila! My coordinate objects output to console, and then are exported to a file. But wait! This doesn’t look right. Instead of JSON objects, my output file looks like this:

$ cat outputFile.js[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],

If you immediately shouted out JSON.stringify !!! You get a gold star!

Source: https://media.giphy.com/media/cOQSc9wAHifk1LlQBM/giphy.gif

One quick update to my code and we’re ready to fly!

jsonData[i] = JSON.stringify({ "id": i, "x": x, "y": y, "message_to":"", "message_from":"", "message_body":"" });

And that’s it. A quick and dirty script got me my JSON file and I learned a few new tricks along the way. Maybe this week hasn’t been so bad after all! I’ll leave you with a quote from Jonathan Livingston Seagull and an appropriate gif:

“Why, Jon, why?” his mother asked. “Why is it so hard to be like the rest of the flock, Jon? Why can’t you leave low flying to the pelicans, the albatross? Why don’t you eat? Son, you’re bone and feathers!” “I don’t mind being bone and feathers mom. I just want to know what I can do in the air and what I can’t, that’s all. I just want to know.”
Richard Bach, Jonathan Livingston Seagull

Source: https://media.giphy.com/media/TR0YyZChDIC64/giphy.gif

--

--

Danny Lee

a nyc based coding enthusiast, handyman, farmer at-heart, artist and future surfer