kkty’s blog

node.jsとかvue.jsとかの話が多いと思います

node.jsでaws s3のデータを読み書きする

aws-sdkパッケージを使います。以下のコマンドでインストールします。

npm i aws-sdk

このドキュメントは https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html で見ることができます。適宜参照しましょう。

また、別途S3の読み書き権限のあるアクセスキー(とシークレットキー)を発行しておきます。

準備が終わったら、以下のように書き始めます。今後s3オブジェクトを操作することになります。

const aws = require("aws-sdk");

const S3_ACCESS_KEY = '...';
const S3_SECRET_KEY = '...';

const s3 = new aws.S3({
  apiVersion: '2006-03-01',
  accessKeyId: S3_ACCESS_KEY,
  secretAccessKey: S3_SECRET_KEY,
});

オブジェクトを列挙するための関数を作ってみます。

const listObjects = params => new Promise((resolve, reject) => {
  s3.listObjectsV2(params, (err, data) => {
    if (err) reject(err);
    else resolve(data.Contents);
  });
});

以下のようにすれば、'some-bucket'バケット内のオブジェクトを3つ列挙してくれます。並び順は辞書順のはずです。

listObjects({
  Bucket: 'some-bucket',
  MaxKeys: 3,
})
  .then((objectList) => {
    // 何らかの処理
  });

ここで、objectListはETag, Key, LastModified, Sizeなどをキーとして持つオブジェクトの配列です。

次に、バケット名・キー名からファイルを読み出す関数を作ってみます。ストリームを用いるケースもあると思いますが、今回は省略します。

const getObject = (bucket, key) => new Promise((resolve, reject) => {
  s3.getObject({
    Bucket: bucket,
    Key: key
  }, (err, data) => {
    if (err) reject(err);
    else resolve(data);
  });
});

以下のようにすれば、'some-bucket'内の、'some-key'という名前のオブジェクトのデータを取得できます。

getObject('some-bucket', 'some-key')
  .then((object) => {
    // 何らかの処理
  });

ここでobjectはBody, ETag, LastModified, ContentLengthなどをキーとして持つオブジェクトです。

そして最後に、バケットに新しいデータを追加する関数を作ってみます。

const putObject = (bucket, key, data) => new Promise((resolve, reject) => {
  s3.putObject({
    Body: data,
    Bucket: bucket,
    Key: key
  }, (err, data) => {
    if (err) reject(err);
    else resolve(data);
  });
});

以下のようにして、'some-bucket'内に'some-key'という名前のファイルを作成できます。中身は'some-content'となります。

putObject('some-bucket', 'some-key', Buffer.from('some-content'))
  .then(() => {
    // 何らかの処理
  });

dockerのコンテナ・ボリュームを掃除する

逐一きれいにしていくのが一番なのですが、dockerを用いていろいろやっていると、不要なファイルが残っていたりします。

そういうときには以下のコマンドを実行します。

docker rm `docker ps -qf "status=exited"`
docker volume rm `docker volume ls -qf "dangling=true"`

一行目のコマンドでは、停止しているコンテナを列挙し(docker ps...の部分)、それをdocker rmで削除しています。

また、二行目のコマンドでは、コンテナから参照されていない(≒不要な)ボリュームを列挙して(docker volume ls ...)、それをdocker volume rmで削除しています。

knex.jsを用いてnode.jsでmysqlを使う

knex.jsとは

Node.js向けのSQLクエリービルダーです。

以下のデータベースに対応しています。

  • Postgres
  • MSSQL
  • MySQL
  • MariaDB
  • SQLite3
  • Oracle
  • Amazon Redshift

実際に使ってみる

今回はmysqlとのセットでknex.jsを使ってみます。

まず、今回使用するmysqlをdockerで用意します。以下のコマンドで、mysqlの動いているコンテナが起動します。

docker run -e MYSQL_ROOT_PASSWORD=password -d -p 3306:3306 mysql:5 --character-set-server=utf8mb4

そして、以下のコマンドで、'db'という名前のデータベースを作成します。

echo 'CREATE DATABASE db' | mysql -h 127.0.0.1 -u root -ppassword

以下、このデータベースをknex.jsから操作することにします。

適当なディレクトリを作り、knexとmysqlをインストールします。

npm i knex mysql

同一ディレクトリ内で作業します。

knexをインポートし、clientを定義します。

const knex = require('knex');

const client = knex({
  client: 'mysql',
  connection: {
    host: '127.0.0.1',
    user: 'root',
    password: 'password',
    database: 'db',
  },  
});

以下で、'tbl'という名前のテーブルが定義できます。

client.schema.createTable('tbl', function (table) {
  table.increments();
  table.string('name');
  table.timestamps();
})
  .then(console.log);

これによって吐き出されたSQL文はこのようなものでした。賢いですね。

making query: create table `tbl` (`id` int unsigned not null auto_increment primary key, `name` varchar(255), `created_at` datetime, `updated_at` datetime)

なおconsoleへの出力は以下のような感じになります。

[ OkPacket {
    fieldCount: 0,
    affectedRows: 0,
    insertId: 0,
    serverStatus: 2,
    warningCount: 0,
    message: '',
    protocol41: true,
    changedRows: 0 },
  undefined ]

以下のようなコードで、'tbl'テーブルにレコードを挿入できます。2行挿入するためにPromise.allを使っています。

Promise.all([
  client('tbl').insert({
    name: 'hoge',
    created_at: client.fn.now(),
    updated_at: client.fn.now(),
  }),
  client('tbl').insert({
    name: 'foo',
    created_at: client.fn.now(),
    updated_at: client.fn.now(),
  }),
])
  .then(console.log);

consoleへの出力は以下のようになりました。1と2の順番が入れ替わるかもしれません。

[ [ 1 ], [ 2 ] ]

以下のようなコードで'tbl'テーブルからデータを取得できます。

client('tbl')
  .select('name', 'created_at', 'updated_at')
  .then(console.log);

consoleへの出力は以下のようになりました。mysqlモジュールの挙動と同じですね。

[ RowDataPacket {
    id: 1,
    name: 'hoge',
    created_at: 2018-11-06T14:12:45.000Z,
    updated_at: 2018-11-06T14:12:45.000Z },
  RowDataPacket {
    id: 2,
    name: 'foo',
    created_at: 2018-11-06T14:12:45.000Z,
    updated_at: 2018-11-06T14:12:45.000Z } ]

キリがないのでここで紹介する関数はこれくらいにしますが、もちろん他にも多数の関数があります。https://knexjs.org/ を参照してください。

Dockerを用いて一行でmysqlサーバーを動かす

以下のコマンドを実行します。

docker run -e MYSQL_ROOT_PASSWORD=password -d -p 3306:3306 mysql --character-set-server=utf8mb4

これだけで、dockerのmysqlサーバーの動いているコンテナが立ち上がります。なお、以下のように設定しています。

  • rootのパスワードは'password'
  • 文字コードはutf8mb4
  • 3306番ポートをフォワード

以下のコマンドで、シェルから実際に接続できるはずです。

mysql -u root -ppassword

nginxでリバースプロキシを使ってみる

リバースプロキシとは

クライアントからWEBサーバーへのリクエストを経由させるサーバーのことです。

クライアント -> WEBサーバー という状況を、クライアント -> リバースプロキシ -> WEBサーバー のようにします。

この冗長化により、

  • セキュリティの強化
  • 負荷分散
  • SSLの対応
  • キャッシュ

などが便利になります。

実際に使ってみる

言葉だけではわかりにくいので、実際にDockerを用いて実験してみます。

次のように、3つのファイルを作ります。

  • docker-compose.yml
  • app.js
  • nginx.conf

以下、それぞれ見ていきます。

app.js

const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(3000);

簡単なexpressアプリケーションです。

ポート3000にアクセスすると'Hello World!'を返します。

nginx.conf

user  nginx;
worker_processes  1;

pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;

    server {
        listen       80;
        server_name  _;

        location / {
            proxy_pass http://app:3000;
        }
    }
}

nginxの設定ファイルです。

proxy_passの部分でリバースプロキシを設定しています。すべてのリクエストを http://app:3000 に転送しています。appというのはexpressのアプリケーションが動いているコンテナのホスト名です。(docker-compose.ymlで設定しています)

docker-compose.yml

version: '3'
services:
  app:
    image: node:11.1.0
    hostname: app
    volumes:
      - ./app.js:/src/app.js
    working_dir: /src
    command: sh -c "npm i express && node app.js"

  nginx:
    image: nginx:1.15.5
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    environment:
      - NGINX_PORT=80

dockerの設定ファイルです。

appというコンテナでexpressアプリケーションを実行し、nginxというコンテナでnginxサーバーを起動しています。volumesを用いて今回作成したファイルをマウントしています。

hostname: appという部分で、コンテナにappというホスト名を付与しています。これにより、nginxというコンテナからappというホスト名で該当コンテナにアクセスできるようになります。

確認

docker-compose upコマンドで、expressアプリケーション、nginxサーバーが起動します。

http://localhost/にアクセスすると、'Hello World!'と表示されるはずです。