Edited at

LoopBackフレームワークでCSV出力API実装

More than 1 year has passed since last update.

モデルにCSV出力のremote methodを付けてみた。


  • Streamな感じに出力したい

  • シフトJISな文字コードで出力したい

という点はおおむね達成できたが、csvと組み合わせるとうまく動作しなかったのが心残りだ。

StreamとシフトJIS変換はiconv-liteさまさま。さらにcsvなモジュールでstreamにpipeでうまく処理できるとパーフェクトなのだが。


common/models/note.js

'use strict';

const iconv = require('iconv-lite');
module.exports = function(Note) {
// サンプル用にModelのfindの代わり
function find(filter, cb) {
cb(null, [
{id: 1, date: new Date(2017, 4, 26), name: 'ラーメン', price: 600},
{id: 2, date: new Date(2017, 4, 27), name: 'カツ丼', price: 700},
{id: 3, date: new Date(2017, 4, 28), name: '唐揚げ定食', price: 800},
]);
}

Note.csv = function(filter, cb) {
find(filter, function(err, list) {
if (err) return cb(err);

const stream = iconv.encodeStream('Shift_JIS');
cb(null,
stream,
'text/csv; charset=shift_jis',
'attachment; filename=note.csv'
);

for (const item of list) {
const line = ['id', 'date', 'name', 'price'].map(function(key) {
return item[key];
}).join(',') + '\n';
stream.write(line);
}
stream.end();
});
};

Note.remoteMethod('csv', {
accepts: {arg: 'filter', type: 'string', http: {source: 'query'}},
returns: [
{arg: 'body', type: 'file', root: true},
{arg: 'Content-Type', type: 'string', http: {target: 'header'}},
{arg: 'Content-Disposition', type: 'string', http: {target: 'header'}},
],
});
};


remote methodをLoopBack API Explorerからたたくと、charset付けていてもutf-8に変換されるのは仕様なのだろうか?

HTMLファイルにform作ってpostしてみると、こちらからはちゃんとシフトJISでダウンロード出来るので良しとする。


日本語のファイル名でダウンロードさせたい場合

「日本語.csv」のようなファイル名は、レスポンスヘッダを少し変えて、次のようにする。


Content-Disposition: attachment; filename*=utf-8''%E6%97%A5%E6%9C%AC%E8%AA%9E.csv


filename=ファイル名はブラウザによって対応方法が異なるため、

"attachment; filename*=utf-8''" + encodeURIComponent('日本語.csv')

と、filename*=utf-8''URLエンコードしたファイル名を付けると良さそうです。


モデルのリレーション先も含める場合

findメソッドで、filter引数のincludeプロパティを使うとリレーションも含めた結果がもらえる。

※ただし、結果のリレーション部分のプロパティはFunctionで、もうひと手間toJSON()をかけてやらないとアクセスできないので注意。

参考:https://loopback.io/doc/en/lb3/Include-filter.html#access-included-objects


より汎用的に

モデルのdefinition.rawPropertiesを参照すると、汎用的な処理にできそうです。

mixinにしてしまえば、他のモデルへのCSV出力API追加もさくっと対応できますね。