Cómo analizar un CSV en una matriz de JavaScript y cargarlo en un servidor a través de búsqueda e insertarlo en una base de datos MongoDB.
Primeros pasos
Para este tutorial, vamos a utilizar CheatCode Node.js Boilerplate en el servidor y CheatCode Next.js Boilerplate en el cliente.
Comenzando con el modelo estándar de Node.js...
Terminal
git clone https://github.com/cheatcode/nodejs-server-boilerplate server
A continuación, instale las dependencias repetitivas:
Terminal
cd server && npm install
A continuación, inicie el modelo estándar de Node.js:
Terminal
npm run dev
Después de que el servidor se esté ejecutando, a continuación, queremos configurar el modelo estándar de Next.js. En otra pestaña o ventana de terminal, clona una copia:
Terminal
git clone https://github.com/cheatcode/nextjs-boilerplate client
A continuación, instale las dependencias repetitivas:
Terminal
cd client && npm install
Antes de comenzar con el repetitivo, necesitamos instalar una dependencia adicional, papaparse
que usaremos para ayudarnos a analizar nuestro archivo CSV:
Terminal
npm i papaparse
Finalmente, con eso, continúe y ponga en marcha el repetitivo:
Terminal
npm run dev
Con eso, ¡estamos listos para comenzar!
Construyendo una ruta Express para manejar cargas
Para comenzar, configuraremos una ruta usando Express (ya implementado en el Boilerplate de Node.js que acabamos de configurar) donde cargaremos nuestro CSV:
/servidor/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!");
});
};
Dentro del repetitivo, un Express app
la instancia se crea y se pasa a una serie de funciones en /server/index.js
. Más específicamente, por defecto tenemos dos funciones que consumen el app
instancia:middleware()
y api()
. El primero—definido en /middleware/index.js
—es responsable de adjuntar nuestras funciones de middleware Express (código que se ejecuta antes de que cada solicitud recibida por nuestro servidor Express se transfiera a nuestras rutas). Este último, definido en /api/index.js
—se encarga de adjuntar nuestras API relacionadas con los datos (de forma predeterminada, un servidor GraphQL).
En ese archivo, arriba, debajo de la llamada para configurar nuestro graphql()
servidor (no usaremos GraphQL en este tutorial, así que podemos ignorar esto), estamos agregando una ruta a nuestro app
instancia a través del .use()
método en esa instancia. Como primer argumento, pasamos la URL en nuestra aplicación donde enviaremos un POST
solicitud del navegador que contiene nuestros datos CSV.
De forma predeterminada, el modelo estándar comienza en el puerto 5001, por lo que podemos esperar que esta ruta esté disponible en http://localhost:5001/uploads/csv
. Dentro de la devolución de llamada de la ruta, aunque no lo haremos esperar algo a cambio del cliente, para asegurarnos de que la solicitud no se cuelgue, respondemos con res.send()
y un breve mensaje reconociendo una carga exitosa.
/servidor/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!");
});
};
Al agregar la funcionalidad que realmente buscamos, arriba, hemos agregado dos cosas importantes:
- Una expectativa de algún
documentsFromCSV
se nos pasa a través delcsv
campo en elreq.body
(POST
cuerpo de la solicitud). - Un bucle sobre esos
documentsFromCSV
, agregando cada uno a una colección de MongoDB que hemos importado arriba llamadaDocuments
(La definición de esto se incluye en el modelo de Node.js para nosotros como ejemplo).
Para cada iteración del ciclo, esto se ejecutará cinco veces como nuestra prueba .csv
el archivo tendrá cinco filas; llamamos a Documents.insertOne()
, pasando un _id
establecer igual a una llamada al generateId()
incluido función de /server/lib/generateId.js
(esto genera una cadena hexadecimal aleatoria única de 16 caracteres de longitud).
A continuación, usamos JavaScript ...
operador de propagación para decir "si hay un objeto en el documentsFromCSV
matriz en la misma posición (índice) que el valor actual de i
, devuélvalo y 'descomprima' su contenido en el objeto junto con nuestro _id
(el documento que finalmente insertaremos en la base de datos)." Si por alguna razón no tenemos un documento, recurrimos a un objeto vacío con || {}
para evitar un error de tiempo de ejecución. Alternativamente (y preferiblemente, si sus datos pueden o no ser consistentes), podríamos ajustar la llamada a Documents.insertOne()
en un if
declaración que verifica esto incluso antes de que lo llamemos.
Eso es todo para el servidor. A continuación, pasemos al cliente y veamos cómo analizar nuestro archivo CSV y subirlo.
Conectando un componente React para analizar y cargar nuestro CSV
Ahora, en el cliente, configuraremos un componente React con una entrada de archivo que nos permitirá seleccionar un CSV, analizarlo en un objeto JavaScript y luego cargarlo en el punto final que acabamos de definir en el servidor.
/cliente/páginas/subir/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;
Aquí, estamos usando el patrón de componente de función en React para definir un componente llamado Upload
. Debido a que estamos usando Next.js (un marco construido alrededor de React), estamos definiendo nuestro componente en el /pages
carpeta, anidada en su propia carpeta en /pages/upload/index.js
. Al hacer esto, Next.js mostrará automáticamente el componente que estamos definiendo arriba en el navegador cuando visitemos el /upload
ruta (el texto estándar comienza en el puerto 5000
de forma predeterminada, estará disponible en http://localhost:5000/upload
).
Centrándose en el return
valor dentro del Upload
función—de nuevo, esta es una función componente, así que nada más que una función de JavaScript:estamos devolviendo un marcado que representará nuestro componente. Debido a que el repetitivo usa el marco CSS de Bootstrap, aquí hemos renderizado algunas marcas básicas para darnos un título, una entrada de archivo y un botón en el que podemos hacer clic para iniciar una carga con estilo usando el CSS de ese marco.
Centrándose en el useState()
llamada a la función en la parte superior de nuestro componente, aquí, estamos configurando un valor de estado que se usará para controlar la visualización de nuestra entrada y el botón cuando estemos cargando un archivo.
Al llamar al useState()
, le pasamos un valor predeterminado de false
y luego esperar que nos devuelva una matriz de JavaScript con dos valores:el valor actual y un método para establecer el valor actual. Aquí, usamos la desestructuración de matrices de JavaScript para permitirnos asignar variables a estos elementos en la matriz. Esperamos nuestro valor actual en la posición 0
(el primer elemento de la matriz), y lo hemos asignado a la variable uploading
aquí. En la posición 1
(el segundo elemento de la matriz), hemos asignado la variable setUploading
(esperamos que esta sea una función que establecerá nuestro uploading
valor).
Abajo en el return
valor, podemos ver uploading
siendo asignado al disabled
atributo en nuestro <input />
así como nuestro <button />
. Cuando uploading
es true
, queremos deshabilitar la capacidad de seleccionar otro archivo o hacer clic en el botón de carga. Además de esto, para agregar contexto a nuestros usuarios, cuando uploading
es cierto, queremos cambiar el texto de nuestro botón a "Cargando..." y cuando no subiendo a "Subir".
Con todo eso en su lugar, ahora veamos el handleUploadCSV
función que hemos eliminado cerca de la mitad de nuestro componente. Cabe destacar que estamos llamando a esta función cada vez que nuestro <button />
se hace clic.
Analizando y subiendo nuestro archivo CSV
Ahora viene la parte divertida. Desarrollemos ese handleUploadCSV
funciona un poco y haz que esto funcione.
/cliente/páginas/subir/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;
Hemos agregado un poco de detalle; vamos a caminar a través de él. Primero, cuando llamamos para subir nuestro CSV, lo primero que queremos hacer es deshabilitar temporalmente nuestro <input />
y <button />
, entonces llamamos a setUploading()
pasando true
(esto activará una nueva representación en React automáticamente, haciendo que nuestra entrada y el botón sean temporalmente inaccesibles).
Luego, para obtener acceso al archivo seleccionado por nuestro usuario, agregamos algo especial a nuestro componente. En React, mientras podemos acceder técnicamente a los elementos representados en el DOM utilizando métodos tradicionales como document.querySelector()
, es mejor si usamos una convención llamada refs.
Las referencias, abreviatura de referencias, son una forma de darnos acceso a un elemento DOM en particular tal como lo representa React a través de una variable. Aquí, hemos agregado la función useRef()
a nuestro react
importar arriba y justo debajo de nuestra llamada a useState()
han definido una nueva variable inputRef
configurado para una llamada a useRef()
.
Con ese inputRef
, abajo en nuestro return
valor, le asignamos un ref
atributo a nuestro <input />
elemento, pasando el inputRef
variable. Ahora, automáticamente, cuando React renderice este componente, verá este ref
valor y asignar inputRef
de vuelta al nodo DOM que representa.
De vuelta en handleUploadCSV
, usamos esto llamando a inputRef?.current
. Aquí, current
representa el nodo DOM representado actualmente (literalmente, el elemento tal como se representa en el navegador). El inputRef?
parte solo dice "if inputRef
está definido, danos su current
valor (abreviatura de inputRef && inputRef.current
)".
Con eso almacenado en una variable, a continuación, creamos una instancia del FileReader()
nativo clase (significa nativo que está integrado en el navegador y no hay nada que instalar). Al igual que las sugerencias de nombres, esto nos ayudará a manejar la lectura real del archivo que nuestro usuario selecciona a través de nuestro <input />
en la memoria.
Con nuestro reader
instancia, a continuación, necesitamos obtener acceso a la representación DOM de nuestro archivo, por lo que llamamos a input
(que contiene nuestro nodo DOM) y acceda a su files
propiedad. Esto contiene el archivo seleccionado por el usuario en una matriz, por lo que aquí, usamos la desestructuración de la matriz de JavaScript nuevamente para "arrancar" el primer elemento de esa matriz y asignarlo a la variable file
.
A continuación, en la parte inferior de nuestra función, observe que estamos haciendo una llamada a reader.readAsText(file)
. Aquí le decimos a nuestro FileReader()
instancia para cargar el file
nuestro usuario seleccionó en la memoria como texto sin formato. Justo encima de esto, agregamos una función de devolución de llamada .onloadend
que es llamado automáticamente por reader
una vez que haya "leído" el archivo en la memoria.
Dentro de esa devolución de llamada, esperamos obtener acceso al evento de JavaScript que representa el onloadend
evento como el primer argumento pasado a la función de devolución de llamada. En ese objeto de evento, esperamos un target
atributo que en sí mismo contendrá un result
atributo. Porque le preguntamos al reader
para leer nuestro archivo como texto sin formato, esperamos target.result
para contener el contenido de nuestro archivo como una cadena de texto sin formato.
Finalmente, utilizando el Papa
objeto que importamos a través del papaparse
paquete que instalamos anteriormente, llamamos al .parse()
función que pasa dos argumentos:
- Nuestro
target.result
(la cadena de texto sin formato que contiene nuestro.csv
contenido del archivo). - Un objeto de opciones para
papaparse
que establece elheader
opción atrue
lo cual es interpretado por la biblioteca como esperando que la primera fila en nuestro CSV sean los títulos de columna que queremos usar como propiedades de objeto en los objetos generados porpapaparse
(uno por fila en nuestro CSV).
Ya casi hemos terminado. Ahora, con nuestro csv
analizado , estamos listos para llamar a nuestro servidor y cargar esto.
Subiendo nuestro CSV al servidor
Ultima parte. Escupamos todo el código y analicemos:
/cliente/páginas/subir/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;
Para hacer nuestra carga, vamos a usar el navegador incorporado fetch()
función. Recuerde que anteriormente en el tutorial, configuramos nuestra ruta en el servidor en /uploads/csv
y sugirió que estará disponible en http://localhost:5001/uploads/csv
. Aquí, continuamos con esa suposición, pasándola como la URL de nuestro fetch()
solicitud.
A continuación, como segundo argumento de fetch()
, pasamos un objeto de opciones que describe la solicitud. Porque queremos enviar nuestros datos en el body
de nuestra solicitud, configuramos el HTTP method
campo a POST
. A continuación, configuramos el Content-Type
encabezado a application/json
para que nuestro servidor sepa que nuestra solicitud body
contiene datos en formato JSON (si tiene curiosidad, esto le dice a nuestro bodyParser
software intermedio en /server/middleware/bodyParser.js
cómo convertir los datos del cuerpo sin procesar antes de entregarlos a nuestras rutas).
Ahora, para la parte importante, al body
propiedad pasamos un objeto a JSON.stringify()
—fetch()
espera que pasemos el cuerpo de nuestra solicitud como una cadena, y en ese objeto, establecemos el csv
propiedad que hemos anticipado en el servidor, igual al csv.data
propiedad. Aquí, csv
representa la respuesta que recibimos de Papa.parse()
y data
contiene la matriz de filas en nuestro CSV analizado como objetos JavaScript (recuerde que en el servidor, recorremos esta matriz).
Finalmente, porque esperamos fetch()
para devolvernos una promesa de JavaScript, agregamos dos funciones de devolución de llamada .then()
y .catch()
. El primero maneja el estado de "éxito" si nuestra carga es exitosa y el segundo maneja cualquier error que pueda ocurrir. Dentro de .then()
, nos aseguramos de setUploading()
a false
para hacer nuestro <input />
y <button />
accesible de nuevo y use el pong
biblioteca incluida en el repetitivo para mostrar un mensaje de alerta cuando nuestra carga sea exitosa. En el .catch()
, también setUploading()
a false
y luego cierre la sesión del error en la consola del navegador.
¡Hecho! Ahora, cuando seleccionemos nuestro archivo CSV (tome un archivo de prueba aquí en Github si no tiene uno) y haga clic en "Cargar", nuestro archivo se analizará, se cargará en el servidor y luego se insertará en la base de datos.
Terminando
En este tutorial, aprendimos cómo crear un componente React con una entrada de archivo que nos permitió seleccionar un .csv
archivo y súbalo al servidor. Para hacerlo, usamos la API FileReader de HTML5 junto con el papaparse
biblioteca para leer y analizar nuestro CSV en un objeto JavaScript.
Finalmente, usamos el navegador fetch()
para entregar ese CSV analizado al servidor donde definimos una ruta Express que copió nuestros datos CSV en una colección de base de datos MongoDB.