text: How to Import a CSV Using Next.js and Node.js
  August 13th, 2021
  What You Will Learn in This Tutorial
  How to parse a CSV into a JavaScript array and upload it to a server via fetch and insert it into a MongoDB database.
  Table of Contents
  Getting started
  For this tutorial, we're going to use the CheatCode Node.js Boilerplate on the server and the CheatCode Next.js Boilerplate on the client.
  Starting with the Node.js boilerplate...
  Terminal
  git clone  server
  Next, install the boilerplate's dependencies:
  Terminal
  cd server && npm install
  Next, start up the Node.js boilerplate:
  Terminal
  npm run dev
  After the server is running, next, we want to set up the Next.js Boilerplate. In another terminal tab or window, clone a copy:
  Terminal
  git clone  client
  Next, install the boilerplate's dependencies:
  Terminal
  cd client && npm install
  Before starting the boilerplate, we need to install one additional dependency,
  papaparse which we'll use to help us parse our CSV file:
  Terminal
  npm i papaparse
  Finally, with that, go ahead and start up the boilerplate:
  Terminal
  npm run dev
  With that, we're ready to get started!
  Building an Express route to handle uploads
  To start, we're going to set up a route using Express (already implemented in the Node.js Boilerplate we just set up) where we'll upload our CSV:
  /server/api/index.js
  import Documents from "./documents"; import graphql from "./graphql/server"; export default (app) => { graphql(app); app.use("/uploads/csv", (req, res) => { // We'll handle our uploaded CSV here... res.send("CSV uploaded!"); }); };
  Inside of the boilerplate, an Express
  app instance is created and passed into a series of functions in
  /server/index.js. More specifically, by default we have two functions that consume the
  app instance:
  middleware() and
  api(). The former—defined in
  /middleware/index.js—is responsible for attaching our Express middleware functions (code that runs before each request received by our Express server is handed off to our routes). The latter—defined in
  /api/index.js—handles attaching our data-related APIs (by default, a GraphQL server).
  In that file, above, beneath the call to set up our
  graphql() server (we won't be using GraphQL in this tutorial so we can ignore this), we're adding a route to our
  app instance via the
  .use() method on that instance. As the first argument, we pass the URL in our app where we'll send a
  POST request from the browser containing our CSV data.
  By default, the boilerplate starts on port 5001, so we can expect this route to be available at
   Inside of the callback for the route, though we won't expect anything in return on the client, to ensure the request doesn't hang we respond with
  res.send() and a brief message acknowledging a successful upload.
  /server/api/index.js
  import Documents from "./documents"; import graphql from "./graphql/server"; import generateId from "../lib/generateId"; export default (app) => { graphql(app); app.use("/uploads/csv", (req, res) => { const documentsFromCSV = req?.body?.csv; for (let i = 0; i < documentsFromCSV.length; i += 1) { Documents.insertOne({ _id: generateId(), ...(documentsFromCSV[i] || {}), }); } res.send("CSV uploaded!"); }); };
  Adding the functionality we're really after, above, we've added in two big things:
  - An expectation of some
  documentsFromCSVbeing passed to us via the
  csvfield on the
  req.body(
  POSTrequest body).
  - A loop over those
  documentsFromCSV, adding each one to a MongoDB collection we've imported up top called
  Documents(the definition for this is included in the Node.js boilerplate for us as an example).
  For each iteration of the loop—this will run five times as our test
  .csv file will be five rows long—we call to
  Documents.insertOne(), passing an
  _id set equal to a call to the included
  generateId() function from
  /server/lib/generateId.js (this generates a unique, random hex string of 16 characters in length).
  Next, we use the JavaScript
  documentsFromCSV array at the same position—index—as the current value of
  i, return it and 'unpack' its contents onto the object alongside our
  _id (the document we'll ultimately insert into the database)." If for some reason we do not have a document, we fall back to an empty object with
  Documents.insertOne() in an
  if statement verifying this before we even call it.
  ...spread operator to say "if there is an object in the
  || {}to avoid a runtime error. Alternatively (and preferably, if your data may or may not be consistent), we could wrap the call to
  That's it for the server. Next, let's jump down to the client and see how to handle parsing our CSV file and getting it uploaded.
  Wiring up a React component to parse and upload our CSV
  Now, on the client, we're going to set up a React component with a file input that will allow us to select a CSV, parse it into a JavaScript object, and then upload it to the endpoint we just defined on the server.
  /client/pages/upload/index.js
  import React, { useState } from "react"; const Upload = () => { const [uploading, setUploading] = useState(false); const handleUploadCSV = () => { // We'll handle our CSV parsing and upload here... }; return ( <div> <h4 className="page-header mb-4">Upload a CSV</h4> <div className="mb-4"> <input disabled={uploading} </div> <button onClick={handleUploadCSV} disabled={uploading} {uploading ? "Uploading..." : "Upload"} </button> </div> ); }; Upload.propTypes = {}; export default Upload;
  Here, we're using the function component pattern in React to define a component called
  Upload. Because we're using Next.js (a framework built around React), we're defining our component in the
  /pages folder, nested under its own folder at
  /pages/upload/index.js. By doing this, Next.js will automatically render the component we're defining above in the browser when we visit the
  /upload route (the boilerplate starts on port
  5000 by default so this will be available at
  
  Focusing on the
  return value inside of the
  Upload function—again, this is a function component, so nothing more than a JavaScript function—we're returning some markup that will represent our component. Because the boilerplate uses the Bootstrap CSS framework, here, we've rendered some basic markup to give us a title, a file input, and a button that we can click to start an upload styled using that framework's CSS.
  Focusing on the
  useState() function being called at the top of our component, here, we're setting a state value that will be used to control the display of our input and button when we're uploading a file.
  When calling
  useState(), we pass it a default value of
  false and then expect it to return us a JavaScript array with two values: the current value and a method for setting the current value. Here, we use JavaScript array destructuring to allow us to assign variables to these elements in the array. We expect our current value in position
  0 (the first item in the array), and we've assigned it to the variable
  uploading here. In position
  1 (the second item in the array), we've assigned the variable
  setUploading (we expect this to be a function that will set our
  uploading value).
  Down in the
  return value, we can see
  uploading being assigned to the
  disabled attribute on our
  <input /> as well as our
  <button />. When
  uploading is
  true, we want to disable the ability to select another file or click the upload button. In addition to this, to add context for our users, when
  uploading is true, we want to change the text of our button to "Uploading..." and when we're not uploading to "Upload."
  With all of that in place, next, let's look at the
  handleUploadCSV function we've stubbed out near the middle of our component. Of note, notice that we're calling to this function whenever our
  <button /> is clicked.
  Parsing and uploading our CSV file
  Now for the fun part. Let's flesh out that
  handleUploadCSV function a bit and get this working.
  /client/pages/upload/index.js
  import React, { useState, useRef } from "react"; import Papa from "papaparse"; const Upload = () => { const [uploading, setUploading] = useState(false); const inputRef = useRef(); const handleUploadCSV = () => { setUploading(true); const input = inputRef?.current; const reader = new FileReader(); const [file] = input.files; reader.onloadend = ({ target }) => { const csv = Papa.parse(target.result, { header: true }); }; reader.readAsText(file); }; return ( <div> <h4 className="page-header mb-4">Upload a CSV</h4> <div className="mb-4"> <input ref={inputRef} disabled={uploading} </div> <button onClick={handleUploadCSV} disabled={uploading} {uploading ? "Uploading..." : "Upload"} </button> </div> ); }; Upload.propTypes = {}; export default Upload;
  We've added quite a bit of detail; let's walk through it. First, when we call to upload our CSV, the first thing we want to do is temporarily disable our
  <input /> and
  <button />, so we call to
  setUploading() passing in
  true (this will trigger a re-render in React automatically, making our input and button temporarily inaccessible).
  Next, in order to get access to the file selected by our user, we've added something special to our component. In React, while we can technically access elements rendered to the DOM using traditional methods like
  document.querySelector(), it's better if we use a convention called refs.
  Refs—short for references—are a way to give ourselves access to a particular DOM element as its rendered by React via a variable. Here, we've added the function
  useRef() to our
  react import up top and just beneath our call to
  useState() have defined a new variable
  inputRef set to a call to
  useRef().
  With that
  inputRef, down in our
  return value, we assign a
  ref attribute to our
  <input /> element, passing in the
  inputRef variable. Now, automatically, when React renders this component, it will see this
  ref value and assign
  inputRef back to the DOM node it renders.
  Back in
  handleUploadCSV, we put this to use by calling to
  inputRef?.current. Here,
  current represents the currently rendered DOM node (literally, the element as it's rendered in the browser). The
  inputRef? part is just saying "if
  inputRef is defined, give us its
  current value (shorthand for
  inputRef && inputRef.current)."
  With that stored in a variable, next, we create an instance of the native
  FileReader() class (native meaning it's built-in to the browser and there is nothing to install). Like the name hints, this will help us to manage actually reading the file our user selects via our
  <input /> into memory.
  With our
  reader instance, next, we need to get access to the DOM representation of our file, so we call to
  input (containing our DOM node) and access its
  files property. This contains the file selected by the user in an array, so here, we use JavaScript array destructuring again to "pluck off" the first item in that array and assign it to the variable
  file.
  Next, down at the bottom of our function, notice that we're making a call to
  reader.readAsText(file). Here, we're telling our
  FileReader() instance to load the
  file our user selected into memory as plain text. Just above this, we add a callback function
  .onloadend which is automatically called by
  reader once it's "read" the file into memory.
  Inside of that callback, we expect to get access to the JavaScript event representing the
  onloadend event as the first argument passed to the callback function. On that event object, we expect a
  target attribute which itself will contain a
  result attribute. Because we asked the
  reader to read our file as plain text, we expect
  target.result to contain the contents of our file as a plain text string.
  Finally, utilizing the
  Papa object we imported via the
  papaparse package we installed earlier, we call the
  .parse() function passing two arguments:
  - Our
  target.result(the plain text string containing our
  .csvfile's contents).
  - An options object for
  papaparsewhich sets the
  headeroption to
  truewhich is interpreted by the library as expecting the first row in our CSV to be the column titles we want to use as object properties in the objects generated by
  papaparse(one per row in our CSV).
  We're almost done. Now, with our parsed
  csv, we're ready to call to our server and get this uploaded.
  Uploading our CSV to the server
  Last part. Let's spit out all of the code and step through it:
  /client/pages/upload/index.js
  import React, { useState, useRef } from "react"; import Papa from "papaparse"; import pong from "../../lib/pong"; const Upload = () => { const [uploading, setUploading] = useState(false); const inputRef = useRef(); const handleUploadCSV = () => { setUploading(true); ... reader.onloadend = ({ target }) => { const csv = Papa.parse(target.result, { header: true }); fetch(" { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ csv: csv?.data, }), }) .then(() => { setUploading(false); pong.success("CSV uploaded!"); }) .catch((error) => { setUploading(false); console.warn(error); }); }; reader.readAsText(file); }; return (...); }; Upload.propTypes = {}; export default Upload;
  To do our upload, we're going to use the built-in browser
  fetch() function. Remember that earlier in the tutorial, we set up our route on the server at
  /uploads/csv and suggested that it will be available at
   Here, we continue with that assumption, passing that as the URL for our
  fetch() request.
  Next, as the second argument to
  fetch(), we pass an options object describing the request. Because we want to send our data in the
  body of our request, we set the HTTP
  method field to
  POST. Next, we set the
  Content-Type header to
  application/json to let our server know that our request
  body contains data in a JSON format (if you're curious, this tells our
  bodyParser middleware at
  /server/middleware/bodyParser.js how to convert the raw body data before it hands it off to our routes).
  Now, for the important part, to the
  body property we pass an object to
  JSON.stringify()—
  fetch() expects us to pass our request body as a string—and on that object, we set the
  csv property we've anticipated on the server, equal to the
  csv.data property. Here,
  csv represents the response we received from
  Papa.parse() and
  data contains the array of rows in our CSV parsed as JavaScript objects (remember on the server, we loop over this array).
  Finally, because we expect
  fetch() to return us a JavaScript Promise, we add two callback functions
  .then() and
  .catch(). The former handling the "success" state if our upload is successful and the latter handling any errors that might occur. Inside of
  .then(), we make sure to
  setUploading() to
  false to make our
  <input /> and
  <button /> accessible again and use the
  pong library included in the boilerplate to display an alert message when our upload is successful. In the
  .catch(), we also
  setUploading() to
  false and then log out the error to the browser console.
  Done! Now, when we select our CSV file (grab a test file here on Github if you don't have one) and click "Upload," our file will be parsed, uploaded to the server, and then inserted into the database.
  Wrapping up
  In this tutorial, we learned how to built a React component with a file input that allowed us to select a
  .csv file and upload it to the server. To do it, we used the HTML5 FileReader API in conjunction with the
  papaparse library to read and parse our CSV into a JavaScript object.
  Finally, we used the browser
  fetch() method to hand that parsed CSV off to the server where we defined an Express route that copied our CSV data into a MongoDB database collection.
  Get the latest free JavaScript and Node.js tutorials, course announcements, and updates from CheatCode in your inbox.
  No spam. Just new tutorials, course announcements, and updates from CheatCode.