Sending CSV back with express

Viewed 8694

I am new to the exporting CSV from JSON... I am pretty close to gettin this... but I dont know how to send the CSV back to the client from my express server.

The user selects the customers to export, and hits the export button... I'd like the CSV file to be sent back.

I got the JSON data converted to CSV on the server, how would I send this file back to the client?

Here is my express route so far -

    router.post('/SaveCSV', (req, res) => {

    const csv = JSONToCSV(req.body, { fields: ["Customer Name", "Business Name", "Customer Email", "Customer ID", "School ID", "Student Count", "Admin Count", "Courses Count" ]})

    
    
})

I have tried res.send, res.sendFile, and res.attachment.. any guidance?

Update***

    router.post('/SaveCSV', (req, res) => {

    const csv = JSONToCSV(req.body, { fields: ["Customer Name", "Business Name", "Customer Email", "Customer ID", "School ID", "Student Count", "Admin Count", "Courses Count" ]})

    res.attachment('CustomerData.csv').send(csv)
    
})

Here is my fetch call on the client --

const saveCsv = async (customers) => {
    const token = await getAccessTokenSilently();
    try {
        const response = await fetch('/api/SaveCSV', {
            method: 'POST',
            headers: {
                Authorization: `Bearer ${token}`,
                "content-type": 'application/json; Charset=UTF-8'
            },
            body: JSON.stringify(customers)
        })
        const responseData = await response.json()
        console.log(responseData)
    } catch (error) {
        console.log(error)
    }
}
2 Answers

On the server side, you can just send the CSV data back. By default that will come with content type text/html, though - better to send it with the correct type text/csv. This could be done by using res.type('text/csv') first.

Now, it is debatable if this is necessary if you don't navigate the browser to your endpoint anyway but consume the download programmatically in your frontend code, but: To mark it as a "download" (that should not be displayed but actually saved to a file), you can use res.attachment(). If you specify a filename, it will then also send that filename in the header but also set the content type automatically based on the extension. So you can use res.attachment('customers.csv').send(csv) for example. If a browser were to navigate to your URL, it would see the CSV as a file download with name customers.csv.

router.post('/SaveCSV', (req, res) => {
    const csv = JSONToCSV(req.body, { fields: ["Customer Name", "Business Name", "Customer Email", "Customer ID", "School ID", "Student Count", "Admin Count", "Courses Count" ]})
    
    res.attachment('customers.csv').send(csv)
})

On the client side, you could then get the raw response body as a blob using response.blob(), and you could have the user download that blob. For more explanation how that could be done, you can research online (there are lots of resources about that). A full example that gets the filename right automatically and works with legacy browsers as well would be here (which was also linked in a comment to this Stack Overflow answer) - note that you'll need text/csv instead of application/pdf or read the content type from the response.

Let me instead post a simplified example with less functionality:

const blob = await response.blob()
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = 'customers.csv'
link.click()
setTimeout(() => URL.revokeObjectURL(link.href), 0)

But let's think this over. According to the code you showed, all your server code does is take the data from the frontend, stuff it into a CSV and send it back. I would say, you don't need the server for that at all! You could do the whole thing in the frontend code to begin with!

const saveCsv = (customers) => {
  const csv = JSONToCSV(customers, { fields: ["Customer Name", "Business Name", "Customer Email", "Customer ID", "School ID", "Student Count", "Admin Count", "Courses Count" ]})
  
  const link = document.createElement('a')
  link.href = 'data:text/csv,' + encodeURIComponent(csv)
  link.download = 'customers.csv'
  link.click()
}

This assumes of course that you can make the JSONToCSV library available in the frontend, or that you would add your own function to build the CSV data.

The JSONtoCSV probably generates a string in the format of CSV from JSON.

From this string, you have two options.

  1. send the string into a text document, then res.sendFile that file.

    This can be done as such.

    I recommend using this package, although you can do it vanilla. Just create the file and write the string to it, then send it via res.sendFile.

    const csv = new ObjectsToCsv(csvdata);
    
    await csv.toDisk("./data.csv");
    
    res.sendFile("./data.csv");
    
  2. send the string as JSON, then parse it into a download in the frontend. This is the most common approach.

    This can be done as shown here:

    // Parse it into CSV with a frontend CSV library.
    
    // Then, create a hidden element and automatically download the file.
    var hiddenElement = document.createElement('a');
    hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
    hiddenElement.target = '_blank';
    hiddenElement.download = 'exported.csv';
    hiddenElement.click();
    

    Source

Related