JavaScript >> Javascript 文檔 >  >> Node.js

如何使用 Next.js 和 Node.js 導入 CSV

如何將 CSV 解析為 JavaScript 數組並通過 fetch 將其上傳到服務器並插入到 MongoDB 數據庫中。

開始使用

對於本教程,我們將在服務器上使用 CheatCode Node.js Boilerplate,在客戶端使用 CheatCode Next.js Boilerplate。

從 Node.js 樣板開始...

終端

git clone https://github.com/cheatcode/nodejs-server-boilerplate server

接下來,安裝樣板的依賴:

終端

cd server && npm install

接下來,啟動 Node.js 樣板:

終端

npm run dev

服務器運行後,接下來,我們要設置 Next.js 樣板。在另一個終端選項卡或窗口中,克隆一個副本:

終端

git clone https://github.com/cheatcode/nextjs-boilerplate client

接下來,安裝樣板的依賴:

終端

cd client && npm install

在開始樣板之前,我們需要安裝一個額外的依賴,papaparse 我們將使用它來幫助我們解析 CSV 文件:

終端

npm i papaparse

最後,繼續並啟動樣板:

終端

npm run dev

有了這個,我們就可以開始了!

構建處理上傳的 Express 路由

首先,我們將使用 Express 設置一個路由(已經在我們剛剛設置的 Node.js 樣板中實現),我們將上傳我們的 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!");
  });
};

在樣板文件內部,一個 Express app 實例被創建並傳遞給 /server/index.js 中的一系列函數 .更具體地說,默認情況下,我們有兩個函數使用 app 實例:middleware()api() .前者——在 /middleware/index.js 中定義 — 負責附加我們的 Express 中間件功能(在我們的 Express 服務器收到的每個請求被傳遞到我們的路由之前運行的代碼)。後者——在 /api/index.js 中定義 — 處理附加我們與數據相關的 API(默認情況下,GraphQL 服務器)。

在上面的那個文件中,在設置我們的 graphql() 的調用下方 服務器(我們不會在本教程中使用 GraphQL,所以我們可以忽略它),我們正在向我們的 app 添加一個路由 實例通過 .use() 該實例上的方法。作為第一個參數,我們在應用程序中傳遞 URL,我們將在其中發送 POST 來自包含我們 CSV 數據的瀏覽器的請求。

默認情況下,樣板文件從端口 5001 開始,因此我們可以預期此路由在 http://localhost:5001/uploads/csv 處可用 .在路由的回調內部,雖然我們不會 期望客戶端得到任何回報,以確保請求不會掛起,我們使用 res.send() 進行響應 以及確認上傳成功的簡短消息。

/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!");
  });
};

添加我們真正追求的功能,上面,我們添加了兩件大事:

  1. 一些documentsFromCSV的期望 通過 csv 傳遞給我們 req.body 上的字段 (POST 請求正文)。
  2. 對那些 documentsFromCSV 進行循環 ,將每一個添加到我們在頂部導入的名為 Documents 的 MongoDB 集合中 (作為示例,此定義包含在 Node.js 樣板中)。

對於循環的每次迭代——這將作為我們的測試 .csv 運行五次 文件將有五行長——我們調用 Documents.insertOne() ,傳遞一個 _id 設置等於對包含的 generateId() 的調用 /server/lib/generateId.js 的函數 (這會生成一個唯一的、隨機的十六進製字符串,長度為 16 個字符)。

接下來,我們使用 JavaScript ... 擴展運算符表示“如果 documentsFromCSV 中有對象 數組位於與 i 的當前值相同的位置(索引) ,返回它並將其內容“解包”到我們的 _id 旁邊的對像上 (我們最終將插入數據庫的文檔)。”如果出於某種原因我們 有一個文檔,我們使用 || {} 回退到一個空對象 以避免運行時錯誤。或者(最好,如果您的數據可能一致或不一致),我們可以將調用包裝到 Documents.insertOne()if 中 在我們調用它之前驗證這一點的聲明。

這就是服務器。接下來,讓我們跳到客戶端,看看如何處理解析我們的 CSV 文件並將其上傳。

連接一個 React 組件來解析和上傳我們的 CSV

現在,在客戶端,我們將設置一個帶有文件輸入的 React 組件,它允許我們選擇 CSV,將其解析為 JavaScript 對象,然後將其上傳到我們剛剛在服務器上定義的端點。

/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} type="file" className="form-control" />
      </div>
      <button
        onClick={handleUploadCSV}
        disabled={uploading}
        className="btn btn-primary"
      >
        {uploading ? "Uploading..." : "Upload"}
      </button>
    </div>
  );
};

Upload.propTypes = {};

export default Upload;

在這裡,我們使用 React 中的函數組件模式來定義一個名為 Upload 的組件 .因為我們使用的是 Next.js(一個圍繞 React 構建的框架),所以我們在 /pages 中定義我們的組件 文件夾,嵌套在 /pages/upload/index.js 的自己的文件夾下 .通過這樣做,Next.js 將在我們訪問 /upload 時自動在瀏覽器中呈現我們上面定義的組件 路線(樣板從端口 5000 開始 默認情況下,這將在 http://localhost:5000/upload 可用 )。

專注於return Upload 內的值 函數——同樣,這是一個函數 組件,所以只不過是一個 JavaScript 函數——我們返回了一些代表我們組件的標記。因為樣板文件使用了 Bootstrap CSS 框架,所以在這裡,我們渲染了一些基本標記,為我們提供了一個標題、一個文件輸入和一個按鈕,我們可以單擊該按鈕來開始使用該框架的 CSS 設置樣式的上傳。

專注於useState() 在我們的組件頂部調用的函數,在這裡,我們設置了一個狀態值,用於在上傳文件時控制輸入和按鈕的顯示。

調用 useState() 時 ,我們傳遞一個默認值 false 然後期望它返回一個包含兩個值的 JavaScript 數組:當前值和設置當前值的方法。在這裡,我們使用 JavaScript 數組解構來允許我們將變量分配給數組中的這些元素。我們期望當前值在 0 位置 (數組中的第一項),我們已將其分配給變量 uploading 這裡。在位置 1 (數組中的第二項),我們分配了變量 setUploading (我們希望這是一個set的函數 我們的 uploading 值)。

return中 值,我們可以看到 uploading 被分配給 disabled <input /> 上的屬性 以及我們的 <button /> .當uploadingtrue ,我們要禁用選擇另一個文件或單擊上傳按鈕的功能。除此之外,為我們的用戶添加上下文,當 uploading 是的,我們希望將按鈕的文本更改為“正在上傳...”,而當我們 不是 上傳到“上傳”。

一切就緒後,接下來讓我們看看 handleUploadCSV 我們在組件中間附近刪除的函數。值得注意的是,只要我們的 <button /> 被點擊了。

解析和上傳我們的 CSV 文件

現在是有趣的部分。讓我們充實一下 handleUploadCSV 運行一點,讓它工作。

/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} type="file" className="form-control" />
      </div>
      <button
        onClick={handleUploadCSV}
        disabled={uploading}
        className="btn btn-primary"
      >
        {uploading ? "Uploading..." : "Upload"}
      </button>
    </div>
  );
};

Upload.propTypes = {};

export default Upload;

我們添加了很多細節;讓我們來看看它。首先,當我們調用上傳 CSV 文件時,我們要做的第一件事就是暫時禁用我們的 <input /><button /> ,所以我們調用 setUploading() 傳入 true (這將在 React 中自動觸發重新渲染,使我們的輸入和按鈕暫時無法訪問)。

接下來,為了訪問我們的用戶選擇的文件,我們為我們的組件添加了一些特殊的東西。在 React 中,雖然我們可以 從技術上講,使用 document.querySelector() 等傳統方法訪問呈現給 DOM 的元素 ,如果我們使用稱為 refs 的約定會更好。

Refs——references 的縮寫——是一種讓我們自己訪問特定 DOM 元素的方法,因為它由 React 通過變量呈現。在這裡,我們添加了函數 useRef() 到我們的 react 在我們調用 useState() 的頂部和下方導入 定義了一個新變量inputRef 設置為調用 useRef() .

用那個 inputRef , 在我們的 return 值,我們分配一個 ref 屬性到我們的 <input /> 元素,傳入 inputRef 多變的。現在,當 React 渲染這個組件時,它會自動看到這個 ref 賦值並賦值 inputRef 回到它渲染的 DOM 節點。

返回 handleUploadCSV ,我們通過調用 inputRef?.current 來使用它 .這裡,current 表示當前呈現的 DOM 節點(字面意思是在瀏覽器中呈現的元素)。 inputRef? 部分只是說“如果 inputRef 已定義,給我們它的 current 值(inputRef && inputRef.current 的簡寫 )。”

將其存儲在變量中,接下來,我們創建原生 FileReader() 的實例 類(原生意味著它是瀏覽器內置的,無需安裝)。就像名稱提示一樣,這將幫助我們管理實際讀取用戶通過我們的 <input /> 選擇的文件 進入記憶。

使用我們的 reader 例如,接下來,我們需要訪問文件的 DOM 表示,所以我們調用 input (包含我們的 DOM 節點)並訪問它的 files 財產。這包含用戶在數組中選擇的文件,所以在這裡,我們再次使用 JavaScript 數組解構來“取出”該數組中的第一項並將其分配給變量 file .

接下來,在我們函數的底部,注意我們正在調用 reader.readAsText(file) .在這裡,我們告訴我們的 FileReader() 加載 file 的實例 我們的用戶作為純文本選擇到內存中。在此之上,我們添加一個回調函數 .onloadendreader 自動調用 一旦將文件“讀入”到內存中。

在該回調中,我們希望能夠訪問表示 onloadend 的 JavaScript 事件 事件作為第一個參數傳遞給回調函數。在那個事件對像上,我們期望一個 target 本身將包含 result 的屬性 屬性。因為我們問了reader 要將我們的文件作為純文本讀取,我們需要 target.result 以純文本字符串的形式包含我們文件的內容。

最後,利用 Papa 我們通過 papaparse 導入的對象 我們之前安裝的包,我們稱之為 .parse() 傳遞兩個參數的函數:

  1. 我們的target.result (包含我們的 .csv 的純文本字符串 文件的內容)。
  2. papaparse 的選項對象 設置 header true 的選項 庫將其解釋為期望 CSV 中的第一行是我們想要用作 papaparse 生成的對像中的對象屬性的列標題 (在我們的 CSV 文件中每行一個)。

我們快完成了。現在,使用我們解析的 csv ,我們已準備好調用我們的服務器並將其上傳。

將我們的 CSV 上傳到服務器

最後部分。讓我們吐出所有的代碼並單步執行:

/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("http://localhost:5001/uploads/csv", {
        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;

要進行上傳,我們將使用內置瀏覽器 fetch() 功能。請記住,在本教程的前面,我們在服務器上的 /uploads/csv 處設置了路由 並建議它將在 http://localhost:5001/uploads/csv 可用 .在這裡,我們繼續這個假設,將它作為我們的 fetch() 的 URL 請求。

接下來,作為 fetch() 的第二個參數 ,我們傳遞一個描述請求的選項對象。因為我們要在 body 中發送我們的數據 我們的請求,我們設置 HTTP method POST 的字段 .接下來,我們設置 Content-Type application/json 的標頭 讓我們的服務器知道我們的請求 body 包含 JSON 格式的數據(如果你好奇,這告訴我們的 bodyParser /server/middleware/bodyParser.js 的中間件 如何在將原始身體數據傳遞給我們的路線之前對其進行轉換)。

現在,對於重要的部分,body 屬性我們將一個對像傳遞給 JSON.stringify()fetch() 期望我們將請求正文作為字符串傳遞——在該對像上,我們設置 csv 我們在服務器上預期的屬性,等於 csv.data 財產。這裡,csv 表示我們從 Papa.parse() 收到的響應 和 data 包含我們 CSV 中被解析為 JavaScript 對象的行數組(請記住,在服務器上,我們會遍歷這個數組)。

最後,因為我們期望 fetch() 為了返回一個 JavaScript Promise,我們添加了兩個回調函數 .then().catch() .如果我們的上傳成功,前者處理“成功”狀態,後者處理可能發生的任何錯誤。 .then() 內部 ,我們確保 setUploading()false 製作我們的 <input /><button /> 再次訪問並使用 pong 樣板中包含的庫,用於在我們的上傳成功時顯示警報消息。在 .catch() ,我們也setUploading()false 然後將錯誤註銷到瀏覽器控制台。

完畢!現在,當我們選擇我們的 CSV 文件(如果沒有,請在 Github 上獲取一個測試文件)並單擊“上傳”,我們的文件將被解析,上傳到服務器,然後插入到數據庫中。

總結

在本教程中,我們學習瞭如何使用允許我們選擇 .csv 的文件輸入構建 React 組件 文件並將其上傳到服務器。為此,我們將 HTML5 FileReader API 與 papaparse 結合使用 庫來讀取我們的 CSV 並將其解析為 JavaScript 對象。

最後,我們使用了瀏覽器fetch() 方法將解析後的 CSV 交給服務器,在該服務器上我們定義了一個 Express 路由,將我們的 CSV 數據複製到 MongoDB 數據庫集合中。


Tutorial JavaScript 教程
  1. 理解 JavaScript 中的 this 關鍵字

  2. 如何使用 Next.js(Facebook 克隆)構建社交網站

  3. 介紹帶有 Flutter 支持的 Appwrite 0.6

  4. 創建代碼圖像的一些資源

  5. 引擎蓋下的 Node.js #5 - 隱藏類和變量分配

  6. 如何使用 jQuery 獲取 CSS 屬性的數字部分?

  7. 我是 6 個多月的全職區塊鏈開發人員,AMA!

  1. 問號 (?) 運算符盡快

  2. 使用 TypeScript 聲明合併擴展 Express 類型 - TypeScript 4

  3. 如何在 RegExp javascript 中放置 [](方括號)?

  4. NotAuthorizedException 用戶名或密碼不正確 amazon-cognito-identity-js

  5. TypeScript - 接口

  6. React Debounce:使用 React Hooks 去抖動

  7. 使用 Plop.js 使用 CLI 生成您自己的 React 組件

  1. 使用 Rust 和 WebAssembly 處理來自視頻源的像素

  2. 來自 Elm 的 Vue 101

  3. 推特列表支持的粉絲頁面

  4. 帶有簡單示例的 ES2016 功能