1
/
5

Wantedlyは、300万人が利用する国内最大のビジネスSNSです

This page is intended for users in Japan. Go to the page for users in United States.

API Gateway + LambdaでCSVデータ取得のWebAPIを作る [Node.js]

こんにちは!開発Gの大西です。

今まで会社の雰囲気が伝わるストーリーを中心に書いてきましたが、今回はちょっと趣向を変えてストーリーで初めてテック系の記事を書いてみたいと思います。

受諾開発に携わって日々学ぶことを、自分の中だけでしまっておくのってもったいないですし、同じことを実現したい人や躓いてる人の助けになるかもしれないのでこれから積極的に書いていこうかなーと思います!

本記事では、サーバーレス開発するならまず触るであろう「API Gateway」と「Lambda」を使って、「ブラウザからCSVアップロード→データ取得する」というWebAPIの実装について紹介します。

実際に私が実装した機能は、「API Gateway」→「Lambda」→「RDS」にデータを登録するというものでした。RDSが絡むとVPCや権限周りの設定がややこしいのでまた別にまとめるとして、今回はとてもシンプルに「データを取得してみるまで」をテーマにまとめています💡

(※説明不足な点等があれば、記事を更新していきます)
🌸本内容をQiitaの方にもまとめています。

はじめに

やりたいことの流れとしてはこんな感じです。

①クライアント側でCSVファイルをアップロード
②URLを叩いて、API Gatewayにアクセス
③Lambda発火
④LambdaがアップロードされたCSVからデータを取得する

図を書くまでもないかもしれないですが、一応このような感じですね。

Lambdaの前準備

Lambdaのコードを乗せる箱をAWS上に作ります。Lambdaのコンソール画面から任意の関数名をつけて新しく関数を作成してください。今回VPCに入れる必要はないので、詳細設定は特に設定せずに作成しておきます。

API Gateway

以下、API Gatewayの設定です。今回はREST APIで作っています。
アクションのプルダウンからメソッド「POST」を作成します。Lambdaプロキシ統合を使用しています。リージョンと作成したLambda関数を指定して「保存」をクリックします。
メソッドレスポンスのHTTPステータスにはデフォルトで「200」しか設定されていませんので、「400」「500」も追加してあげましょう。
※OPTIONSメソッドが自動で作成されます。これはCORS設定で必要なメソッドのため消さずにこのままにしておきます。参考リンク

バイナリメディアタイプの設定

クライアント側からはCSVファイルを添付して、multipart/form-data形式でフォームデータを送信します。上記までのAPI Gatewayの設定のままではバイナリデータを受け取れません。別途設定してあげる必要があります。

バイナリメディアタイプにmultipar/form-dataを追加して、任意のステージにデプロイしたらAPI Gatewayの設定は完了です。
Lambda関数のコンソール画面で、作成したAPIエンドポイントが紐付いているのを確認してください。

Lambda

multipart/form-data形式のパラメータを受け取り、CSVデータを取得するコードの記述例を載せます。

自前でmultipart/form-dataやCSVをパースしたりするのは大変です。
そこで以下ライブラリを使用しました。

lambda-multipart-parserは、イベントで受信したmultipart/form-dataのbodyをパースしてファイルデータとテキストデータをJSON形式にしてくれます。

{
files: [
{
filename: 'test.pdf',
content: <Buffer 25 50 6f 62 ... >,
contentType: 'application/pdf',
encoding: '7bit',
fieldname: 'uploadFile1'
}
],
field1: 'VALUE1',
field2: 'VALUE2',
}

ライブラリ「csv-parse」は、Windows, Apple, Linuxそれぞれの改行コードを自動認識してくれるので便利です。

エンコードライブラリを入れたのは、CSVファイルの文字コードにUTF-8(BOM付・BOM無)、Shift-JIS、EUC-JP等が存在するからです。ライブラリの「csv-parse」にもエンコードするオプションがついていますが、日本語特有のEUC-JPには対応していない等、これだけに頼るのはちょっと心配です。日本語のあらゆる文字コードに対応した「encoding-japanese」で「csv-parse」で対応する文字コードに変換(今回はUTF-8)してあげてから、CSVデータをパースするようにします。

今回は、アップロードしたCSVの中身のデータをクライアント側にレスポンスとして返してあげます。

'use strict'

const parser = require('lambda-multipart-parser');
const parse = require('csv-parse/lib/sync');
const Encoding = require('encoding-japanese');

exports.handler = async(event, context) => {
// 返却するレスポンスのステータスコード初期値は500にしておく
const res = {
statusCode: 500,
headers : { // API Gateway CORS有効時には以下のヘッダーリストが必要
"Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST"
}
};

try{
console.log(`Function ${contxt.functionName} started.`);

// multipart/form-data形式のパラメータをパース
const req = await parser.parse(event);

const params = {
file: req.files[0].content,
text: req.text // file以外のパラメータがあればこのように取得する
}

console.log(`エンコードを開始します...`)
    const detected = Encoding.detect(params.file); // 文字コード自動検出
    console.log(`アップロードファイルの文字コードは${detected}です`);

const convertedFile = Encoding.convert(params.file, 'UTF8', detected); // UTF8へ変換
const data = Buffer.from(convertedFile); // 変換後に文字コード配列になっている為、Bufferに戻す
console.log(`エンコードが完了しました`);

    console.log(`CSVデータのパースを開始...`);
const records = parse(data, {
delimiter: ",", // カンマ区切り
trim: true, // フィールド値に空白がある場合に無視
bom: true, // UTF8の場合にBOM有無判定
skip_empty_lines: true, // 空行があった場合は、読込をスキップする
from_line: 1 // 行の読み込み開始行を指定(今回は1行目固定)
});

const responseBody = {
records: records,
message: params.text
};

console.log(`Function ${context.functionName} end.`);

res.statusCode = 200;
res.body = JSON.stringify(responseBody);
return res;

}catch(err){
console.error(err, `Function ${context.functionName} abend.`);

res.body = JSON.stringify({ message: err.message });
return res;
}
}

梱包ファイル

今回ライブラリを使用しているので、コード単体では動作しません。モジュールを含めてzip化してからLambdaにアップロードします。

$ yarn init   (もしくは npm init)
initできたら、各ライブラリをyarn add もしくはnpm installでインストール

ディレクトリファイルを一覧化しました。(参考までに)

tree -L 1
.
├── index.js
├── node_modules
├── package.json
└── yarn.lock

1 directory, 3 files

作業しているディレクトリ配下でアップロード用にzip化するコマンドを入力してください。
(今回は動作を確認するだけなので、webpackは使用しません)

$ zip -r src.zip index.js node_modules/

同じ階層にzipファイルができているはずです。それをLambdaのコンソール画面でアップロードします。

HTMLでアップロード画面作成

クライアント側からファイルをアップロードできるように画面を作ります。

今回はVue.jsとaxiosを使って、API GatewayのエンドポイントURLに向けてPOSTを投げます。あまりに画面がシンプル過ぎたので、ボタンと入力フォームだけelemental-uiで中央寄せするスタイルを少しあてています。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>CSV Uploader</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js"></script>
<link rel="stylesheet" href="./style.css">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<div id="app">
<div class="wrapper">
<form>
<input type="file" id="file" ref="file" @change="handleFile" class="input"/><br>
<el-input type="text" v-model="message" placeholder="edit me!"></el-input>
<el-button type="primary" round @click="uploadFile" class="sendBtn">送信</el-button>
</form>
</div>
</div>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: "#app",
data() {
return {
file: '',
message: ''
}
},
methods: {
handleFile() {
this.file = this.$refs.file.files[0]; // アップロードファイルをセット
},
uploadFile() {
const formData = new FormData();
formData.append('file', this.file);
formData.append('text', this.message);
axios.post('API GatewayのエンドポイントURL',
formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(res => {
this.message = "";
console.log("Success!");
console.log(res.data.records);
console.log(res.data.message);
}).catch(err => {
console.log(`Failed... ${err}`);
})
}
}
})
</script>
</body>
</html>

アップロード画面

CSVファイルアップロード

こんな感じのランダムなデータで生成されたCSVファイルを用意しました。ちなみにこのファイルの文字コードはShift-JISです。

メッセージ付きで送信してみます。

レスポンスの中身をconsole.logで出しているので、デベロッパーツールで見てみます。「Success!」というログのあとに沢山の配列データと、入力して送信したメッセージ「てすと」が表示されています👀

違うメッセージを入力してもう一回送信してみます。配列データの中身も見てみると

CSVの中のデータが返ってきているのを確認できました^^

文字コードがちゃんと認識されていたか確認するには、Lambdaの方でログを出していたのでCloudWatchのログイベントを見てみます。

Shift-JISのファイルがちゃんと、SJIS(Shift-JIS)として認識されていますね💡

さいごに

今回は、Lambdaで取得したデータをそのままクライアント側へ返すだけでしたが、DBに登録したり、S3バケットにPutしてダウンロードできるようにしたりと取得したあとにできることの幅は色々あります。

パースする箇所だけ変えれば、Excelからも同じようにデータを取得できます。

サーバーレスのいいところは、1機能をWebAPIとしてさくっと作れるところです。今後もサーバーレスでできることを紹介していけたらと思います😃

株式会社ONE WEDGEでは一緒に働く仲間を募集しています
4 いいね!
4 いいね!
同じタグの記事
今週のランキング