My experience building a Node.js Native Module using an LLM
8 min read
I use LLM-assisted code generation at work for small, tedious tasks. Mostly around implementing a feature in a library written in language X which already exists in an equivalent library written in language Y. It works fine and saves me time. Admittedly I had my reservations and didn't trust it for larger projects.
I've been working a lot with image metadata for a side project and wanted a tool to read and write EXIF data in various types of image files. I decided that building a Node.js library, one good enough for publishing to npm, would make for a good experiment.
Going in I specifically wanted the library to be a native module written in C++ with appropriate JavaScript bindings. I don't quite know C++ and while I've contributed to such projects I hadn't created one from scratch before. I was also familiar with the exiv2 binary, which is the de-facto tool for EXIF manipulation, and assumed it came with a library. I also knew that I wanted it to work on Linux (what I use) and macOS (what most photographers use).
I also went into this project not researching what alternative libraries existed and with the intent of letting the LLM write all of the code.
The First 40 Minutes
For my setup I had the Zed editor installed and had it configured to work with Claude. I really only opened code files to audit what was being produced and otherwise relied on the conversational feature to produce code ("agentic" as the kids call it). I already had the exiv2 binary installed and I made sure to install the library package as well. I also added a straight-out-of-camera JPEG to the project.
My first prompt was something like this:
Create an npm package that relies on exiv2 and that exposes a function to read EXIF data with the file path as the input string and returning a JSON object with the EXIF keys and values.
The LLM immediately started its code-writing monologue. It noticed that the exiv2 binary was already installed. It then started writing code to shell out to the exiv2 binary. It's convenient that these tools have a stop button for situations like this.
I stopped the process and modified my prompt to specify that I wanted a native C++ module built and started again.
A few minutes later and the process was complete. I had a library that would compile when running npm install, pulled in the exiv2 library, had a method for reading tags, and would return an object with the EXIF data. It noticed the test fixture image and used that for the tests. So far so good.
It wasn't perfect, though. While the JavaScript function accepted a callback, skimming the C++ code made me suspicious that the operation was blocking. I asked the LLM if the function was blocking and it said yes. I asked the LLM to rewrite the code to not be blocking and it rewrote the code and told me it was no longer blocking. Could it be trusted? I then asked it to prove it wasn't blocking and it wrote a bunch of JavaScript tests to ensure the JavaScript event loop could still perform other operations while the data was parsed.
At this point 40 minutes had passed and the goal of reading data was complete.
Another Two Hours
Of course, I also wanted to write metadata. I also wanted the JavaScript API to support both promises and callbacks. The naming scheme of EXIF data is CamelCased with dot separators (an example is Exif.Photo.LensModel) and the metadata object is completely flat. I wanted a second set of functions which would accept a hierarchal object representation for reading and writing values for convenience (tags.Exif.Photo.LensModel instead of tags['Exif.Photo.LensModel']). I also wanted better error handling.
I added a fake JPEG file to the repo and told it to test with that for errors. I also added a RAW file from a Sony camera. I described the metadata hierarchy I envisioned and had it create that. I had it create the additional functions. I kept prodding it to add tests. There were edge cases I would think of that the LLM wouldn't.
For example, if a metadata object used for writing properties was missing fields, or objects were empty, I wouldn't want it to delete the existing fields. If a value was specifically null I wanted that to be a marker to delete the data. If a high level property was null then I would want all of the descendent properties to be removed as well. Here's an example:
const exif = {
Iptc: null, // delete all Iptc.* fields
Xmp: {
dc: {
title: 'new title', // set a new Xmp.dc.title
creator: null, // remove the Xmp.dc.creator
rights: undefined, // leave Xmp.dc.rights unmodified
},
// leave, for example, Xmp.xmpMM.* unmodified, since it's not listed
},
};
ex.setImageTags('./DSC1234.ARW', exif, cb);
I thought of all the edge cases that I could and worked with the LLM to create tests. I found bugs that were hard to diagnose and where the test suite would pass. For example the test suite would read tags, write the file, then read tags again, and would pass the first time it ran. The second time would obviously fail as the image file was modified.
I found more issues as well. Most fields were strings but some were confusingly numbers, such as a fields representing lighting conditions. I asked the LLM to figure it out and it looked through exiv2 docs and realized these fields were enums. I then told the LLM to return the string versions of the enums for reading, and for writing to either accept the numeric or string versions. It then called out to more methods in the C++ exiv2 library to perform the enum translation. It also wrote out some weird logic to hard-code some of these enum values in the native module code but not all of them.
At one point I selfishly asked my colleague Bryan English to take a look. He said that the C++ code should probably be using the NAPI interface instead of Nan. I literally copied and pasted his reply to the LLM and it modified the native module to use what appears to be NAPI.
And now nearly three hours had passed. If I were to have built this from scratch it probably would have taken me between one and two weeks. It seems to work, and it seems to be something that would benefit folks other than myself.
Questions, Questions
What should be done with the project? I don't understand how it works. Would it make sense to make the repository public and publish it to npm? Who would fix the bugs? You know exactly as much as I do about how the project works. In other words, I'm no better off maintaining the library than anyone else in the world. Do I own the library? It is legally ambiguous if LLM-generated code can by copyrighten:
It concludes that the outputs of generative AI can be protected by copyright only where a human author has determined sufficient expressive elements. This can include situations where a human-authored work is perceptible in an AI output, or a human makes creative arrangements or modifications of the output, but not the mere provision of prompts.
Do I want to be responsible for bug reports with this software? If a vulnerability is discovered do I want to have to fix it? The tests all pass. But am I confident enough to use it in a hobby project? What about a commercial project?
What would you do?
Prior Art
Is there a better library out there? I finally searched on npm. It turns out there already is an exiv2 package on npm which is also a node native module, though it was last published 9 years ago. There's also a more recent fork named @11ways/exiv2 which was last modified a month ago.
How similar is the project I vibe-coded with the two existing projects? Well, all three projects have these two methods with the same signatures:
const ex = require('whatever-exiv2-library');
ex.getImageTags('./photo.jpg', (err, tags) => {
console.log(`Taken: ${tags['Exif.Image.DateTime']}`);
});
ex.setImageTags('./photo.jpg', tags, (err) => {
// success
});
The main differences are that the two published libraries don't allow removing tags via the set method but instead have a deleteImageTags(tagList) method, are callback only, and don't have the hierarchal tags concept. It looks like they also only support the numerical data and don't do the string lookups from the enum values. The older library uses the older Nan APIs while the newer library uses NAPI.
Did the LLM look at these projects when coming up with the API design? While getImageTags() seems like an obvious name for a method that would get image tags, there are several other ways it could be named. For example, getTags(). I searched through the exiv2 C++ source code and the string getImageTags doesn't appear. Did the LLM rip off the existing library?
At least with this particular project the previous question of what to do has been answered. There's no need to publish my library since alternatives already exist.
Ethical Concerns
Who really wrote this code after all? I did, but so did you, and so did everyone else who ever published open source code. Many authors of programming books also contributed to this project (at the cost of a $1.5 billion dollar settlement).
For decades open source software engineers published code for the world to use. And for decades companies used it free of charge. But the difference is that now companies are trying to sell amalgamations of that code back to us. I, an open source software developer, write code and host it on GitHub. Anthropic crawls that code and trains Claude. Anthropic then charges me to use Claude. They get my code for free, I pay them for it.
Sure, companies were always selling us our software back to us in a way. Daniel Stenberg, the author of cURL, might own a car and all modern cars run cURL. But this LLM stuff is a bit more direct. We're not buying product features, we're buying literal code.
Do code-generating LLMs make the world a better place? Let me assure you that I didn't grow as a developer throughout this experience. I have no firmer of a grasp of C++ than before I started. The library that was generated is potentially better than what exists today but that would require human labor to validate. A pile of GPUs somewhere helped contribute to the boiling of an ocean.
If I think of my skill as a software engineer as a graph, with the X axis being time and the Y axis being skill, it's mostly a line that goes up to the right. Assuming I only write code using LLMs from now on I would expect that line to level off.
What happens with junior engineers who use these tools? Well, it might've taken a junior engineer two weeks to build this project with an LLM. I was able to build it so quickly most likely because I knew what the ideal API looked like and I knew the right words to use (exiv2, native module, non-blocking, prebuilds, etc). If a junior engineer went through this prompt session it likely would have used the worse implementation where the library shells out to the exiv2 binary. It might not have used the exiv2 library at all.

My biggest fear for the industry is that folks who start "software engineering" today and who only use an LLM won't ever grow that skill chart up and to the right. How would they know to ask if the implementation is blocking?
Looking Forward
I won't argue that the LLM space isn't interesting. I'll paraphrase but the Fight Club quote "Tyler Durden was selling their own fat right back to them" comes to mind. I'll do my best to not give personal money to the massive AI companies.
That said, "democratization" is always a race to the bottom. Massive AI companies are getting massive amounts of money from venture capitalists, but they're in competition, produce essentially the same thing, and compete on costs. The cost of all software, for better or worse, approaches zero. All paid software eventually has adequate open source alternatives.
I do look forward to the day when all this stuff runs for free locally on cheap commodity hardware. Some cron job will occasionally download a free model from some community server. Perhaps that day most of the "AI" companies will either be gone or still undergoing a pivoting death spiral, shedding engineers and promising profits to investors.
![]()
Thomas Hunter II is a Software Engineer with 18+ years building scalable, production-grade systems. Deep expertise in Node.js, observability, and developer tooling, with a strong track record of driving architectural decisions, mentoring engineers, and improving customer facing technical documentation. Author of 5 books (including O'Reilly), certified by the OpenJS Foundation, and frequent international conference speaker.