I’ll now provide a sample code using Express, although in a real-world scenario, I implemented this with Nest.js and utilized ‘@nestjs/common’s StreamableFile.
In this example, I used csv-stringify/sync for converting data to csv format. Simply we can return converted data as result. The important thing here is set filename and content type in header.
const { stringify } = require("csv-stringify/sync");
const express = require("express");
const app = express();
const port = 3000;
app.get("/csv", (req, res) => {
const data = [ // defined
["Name", "Age", "Country"],
["John Doe", 30, "USA"],
["Jane Doe", 25, "Canada"],
["Bob Smith", 40, "UK"],
];
const csvData = stringify(data);
res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-Disposition", "attachment; filename=data.csv");
res.send(csvData);
});
For csv contains ShiftJIS
Just returning csv as the response is simple, however, in the case that the filename and data contain Shift-JIS(Shift Japanese Industrial Standards), it is necessary to specify UTF-8 with BOM (Byte Order Mark) with ufeff. Otherwise, this is not previewed correctly on Excel (It works fine without utf8 bom on Spreadsheet and Mac Preview)
app.get("/csv-sjis", (req, res) => {
const data = [
["名前(name)", "歳(age)", "国(Country)"],
["三笠", 30, "日本"],
["のび太", 25, "アメリカ"],
["もぐろ", 40, "イギリス"],
];
const csvData = stringify(data, {
bom: true,
});
const csvDataWithBom = `\ufeff${csvData}`;
const filename = "日本語ファイル名.csv";
res.setHeader("Content-Type", "text/csv");
res.setHeader(
"Content-Disposition",
`attachment; filename*=UTF-8''${encodeURIComponent(filename)}`
);
res.send(csvDataWithBom);
});
This part specifies the filename. The filename* parameter is used to provide a Unicode filename. The UTF-8’’ indicates that the filename is encoded using UTF-8. encodeURIComponent(filename) is used to ensure that special characters in the filename are properly encoded for a URL.
res.setHeader(
"Content-Disposition",
`attachment; filename*=UTF-8''${encodeURIComponent(filename)}`
);
When working with CSV files and dealing with character encodings, using UTF-8 ensures compatibility with a wide range of international characters. The BOM is a sequence of bytes at the beginning of a text file that helps identify the file’s encoding, and in the case of UTF-8, it aids in correctly interpreting the character order. Including the BOM is particularly beneficial when handling CSV files in environments that may not automatically detect UTF-8 encoding, ensuring seamless processing of data with diverse character sets.
Generating TSV
Additionally I had to generate TSV file which contains ShiftJIS as well. TSV stands for “Tab-Separated Values.” It is a plain text format for representing data where each row of the data is in a separate line, and the values within each row are separated by tabs. TSV is similar to CSV.
In order to handle with ShiftJIS, I had to use a library “iconv-lite” to set utf8 with BOM. Here is the sample code. What the code does is same as generating csv.
const iconv = require('iconv-lite');
app.get('/api/tsv', (req, res) => {
const data = [
['名前(name)', '歳(age)', '国(Country)'],
['三笠', 30, '日本'],
['のび太', 25, 'アメリカ'],
['もぐろ', 40, 'イギリス'],
];
const tsvData = data.map(row => row.join('\t')).join('\n');
const bomUtf8Data = iconv.encode('\ufeff' + tsvData, 'utf-8');
res.setHeader('Content-Type', 'text/tab-separated-values; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename=data.tsv');
res.send(bomUtf8Data);
});