DEV Community

Patricio Salazar
Patricio Salazar

Posted on

How I Added CSV Importing In My React-Node.js Project

Touch Base was fine. It was a cool project. It worked. But, let's face it—was it usable? (Touch Base is a full stack React contact management app that I made).

I was thinking about this and realized something obvious. When a user starts using Touch Base they have to add contacts manually. Which might be fine if you have 5 contacts. If you have 1,000 contacts you want to add, this sucks… and you probably won't want to use this system. So of course, I knew I had to add the ability to import contacts.

Researching Options

My first Google search was “csv importers”, or something like that. I looked through some of the options available and found FlatFile. Their main heading read “The fastest way to collect, onboard and migrate data.” Perfect… except, it wasn't all that for me. Now, this is probably my fault (they seem like an amazing service) but the process of implementing their importer was taking more effort than I was willing to put in for this. This is the perfect time for a little sidebar context:

Lately, I've been really valuing scrappiness. I want to get things done, fast. This isn't about cutting corners, I just don't want to have any excuses or unnecessary delays. After all, I'm just one guy working on side projects. So my current attitude is fail, learn, and iterate fast. All while doing good work.

Back to FlatFile. As much as I wanted to use their promising software I asked myself if I really needed all their bells and whistles and if fighting their docs was worth it. Definitely not. So I went back to my search and landed on Papa Parse. I recalled seeing it in my previous search. Their main heading read “The powerful, in-browser CSV parser for big boys and girls.” 😆 I was in.

Implementation

First things first, I added a POST route to my API.

'import-contacts API POST route'

verifyToken is a function I use in all my routes that does exactly that—verifies the users id token. I use multer in my app which is a node.js middleware for handling file uploads. upload.single('file') is a multer function that helps me upload files to my s3 bucket.

Inside the route, I grab the user id and file through destructuring.

code displaying the extraction of a user id and file properties from the request through the use of destructuring

Like the npm package docs for Papa Parse state, “Papa Parse can parse a Readable Stream instead of a File when used in Node.js environments (in addition to plain strings).”

So I prepared to stream the file directly to Papa Parse by creating said stream from my s3 bucket as well as an empty array to hold the results data. Can't forget about handling potential errors.

create a read stream of a file from an s3 bucket

Then I finally pass the stream to Papa Parse, set my config options and handle any errors coming from the results.

passing the stream directly to Papa Parse

In the code above, complete is a Papa Parse property that takes a callback function. It executes once the parsing is complete. I then grab a hold of the data provided by results as parsedData.

After this, it's time to run some queries on the database and process the contacts. But, I need to store a connection the the db to run the queries on first.

code displaying the storing of a database connection

This next part is a lot of code in a try catch statement, so I'll just give it to you straight with some comments on it.

processing and conditionally inserting contacts data into a PostgresQL database table

As you can see, I

  • fetch existing contacts
  • filter out duplicate contacts using emails, since no two emails can be the same
  • bulk insert the non-duplicate contacts into the table

Frontend

The frontend will be largely specific to my approach of the app, but let's connect the dots here.

The Import Contacts page does one thing so it's very simple. I use the native file upload button which is really an input.

native input element that accepts .csv files and triggers a handling function

When the input detects a change I trigger a handleFileUpload function.

Inside of the handleFileUpload function, I first set the loading state to true so that I can display my little loading spinner to the user while this process takes place.

code displaying a loading state being set to true at the beginning of a function

When a user successfully uploads their .csv file, I append it to a new formData object and send it to my backend route above to process it. I directly use a fetch request here since it's the only place in my app that's going to hit the /import-contacts endpoint. If another part of my app needed to hit the route, I would store the request using context and use that to avoid repeating code. Also, you can see the error handling I have set up…

code displaying the sending of a file and handling of the response from an api endpoint

Upon successful handling of the file or if it errors out, the loading state gets set back to false, and I trigger an appropriate toast alert to let the user know exactly what happened in a nice way.

It feels so nice to log in, upload a .csv file of contacts, get a successful toast alert, and then see all of the new contacts populated in your account. And it's so quick. You might see the loading spinner for just a second. The bulk insert query also helps a lot there.

From Maybe Usable to Usable

Before adding this feature I wondered how usable the app truly was. Now, there's no question about that. Although it wasn't super complex, it's a feature you would expect to see in this type of application so I found it a requirement to implement. I think it makes it a little more serious of a project. Aside from that, I've never done anything with .csv files which made this super fun to work on. Papa Parse integrated so well with all the tools I was already using which made it super easy. I definitely recommend using it.

If you made it this far, cheers to you for reading this 🥂...
and cheers to software that doesn't suck 🥂

p.s I'm still wondering if my project sucks 😂
If you want to check it out here's the link again.

Til next time!

Top comments (0)