[Conoha VPS]　ReactからNode経由でオブジェクトストレージへ画像保存する方法

#はじめに
Webアプリを作る際にConohaのオブジェクトストレージに画像をアップロードするのが大変でしたので記事に書きました。
[回覧板アプリ](https://www.seazech.com)
サーバーサイドのNodeを経由した理由は、データベースでユーザー情報と画像ファイル名を紐付けして管理したいからです。

#画像アップロードの流れ
私が作成したWebアプリ内で画像アップロード方法は以下のようにしています。
1.ReactからNodeに画像を送信
2.NodeのMulterを使って、VPS内のフォルダに画像データを一時保存
3.オブジェクトストレージのトークン取得
4.NodeでVPSで一時保存している画像データをオブジェクトストレージへ送信
5.VPS内の一時保存している画像データを削除

#Conohaのオブジェクトストレージとは
Conohaが提供しているオブジェクトストレージで、特徴としては以下の通りです。
[Conohaオブジェクトストレージ](https://www.conoha.jp/objectstorage/)
1.データ転送料金無料
2.容量無制限
3.三重データコピーで耐障害性・信頼性
4.100GBあたり月額495円
5.RESTful API対応

#1.ReactからNodeに画像を送信
axiosを使ってNodeに画像を送信しています。
以下はフックの関数です。

```react:useUpdateImage.ts
const updateUserImage = useCallback((props: Props) => {

        const { imageFile } = props;
        const data = new FormData();
        if (imageFile !== null){data.append('file', imageFile)};
        const header = { 
            headers: {
            "content-type": "multipart/form-data"
            }
        }

        axios.post('APIのパス', data, header).then((res) => {
            if(!res.data.success){
                console.log('エラーです');
            } else {
                console.log('成功です');
            }           
        }).catch((error) => {
            console.log(error);
        })
}, [])
```

#2.NodeのMulterを使って、VPS内のフォルダに画像データを一時保存
次にMulterを使って、Reactから送られてきた画像データをVPS内フォルダに保存します。
[Multer](https://www.npmjs.com/package/multer)

```javascript:server.js
const multer = require('multer');
const uuid = require('uuid').v4;

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, 'public/temporary') //一時的に画像を保存するパスを指定
    },
    filename: (req, file, cb) => {
        const { originalname } = file; //画像のオリジナルネームの取得
        const fileName = encodeURI(`${uuid()}-${originalname}`); //uuidで差別化した名前に変更
        cb(null, fileName);
    }
})

const uploadAsync = (req, res) => {
    return new Promise((resolve, reject) => {
        upload(req,res,function(err){
            if (err instanceof multer.MulterError) {
                // A Multer error occurred when uploading.
                reject(err);
              } else if (err) {
                // An unknown error occurred when uploading.
                reject(err);
              }
            else {resolve()}
        });
    })
}

//画像アップロードのAPI
app.post('APIのパス',
    (req, res, next) => {
        uploadAsync(req, res).then(() => {
            const file = req.file;
            const imageName = file.filename;   
            //トークン取得など続きの動作がここから入る
            })
   　 }
)
```

#3.オブジェクトストレージのトークン取得
次にオブジェクトストレージのトークンを取得します。

```javascript:server.js
const _ = require('lodash');
const httpRequest = require('request');

//以下にVPS内のAPI情報を入力します。
const tenantId = 'テナントID';
const username = 'ユーザー名';
const password = '設定したパスワード';

const api_url = 'https://identity.tyo2.conoha.io/v2.0';

const params = {
    "url": api_url+'/tokens',
    "headers": {
        "Accept": 'application/json'
    },
    "form": JSON.stringify({
        "auth": {
            "passwordCredentials": {
                "username": username,
                "password": password
            },
            "tenantId": tenantId
        }
    })
};

app.post('APIのパス',
    (req, res, next) => {
        uploadAsync(req, res).then(() => {
            const file = req.file;
            const imageName = file.filename;   
            //トークン取得
            httpRequest.post(params, function(err, result, body){
                if (_.isString(body)) {
                    const res = JSON.parse(body);
                    const token = res.access.token.id;
                    const token_expire = res.access.token.expires;
                    console.log('token: ' + token);
                    console.log('expire: ' + token_expire);
                    //画像アップロード等の動作が続く

                } else {
                    const res = body;
                    console.log(res);
                }
            })
        })
   　 }
)
```

#4.NodeでVPSで一時保存している画像データをオブジェクトストレージへ送信
最後に一時保存している画像データをオブジェクトストレージに送信します。

```javascript:server.js
app.post('APIのパス',
    (req, res, next) => {
        uploadAsync(req, res).then(() => {
            const file = req.file;
            const imageName = file.filename;   
            //トークン取得
            httpRequest.post(params, function(err, result, body){
                if (_.isString(body)) {
                    const res = JSON.parse(body);
                    const token = res.access.token.id;
                    const token_expire = res.access.token.expires;
                    console.log('token: ' + token);
                    console.log('expire: ' + token_expire);

                    //画像アップロード

                    //ヘッダーの設定
                    const headers = {
                        'X-Auth-Token': token,
                        "Transfer-Encoding":"chunked"
                    };

                    // 送信先など
                    const options = {
                        //API情報に記載のあるオブジェクトストレージAPIエンドポイント
                        url: `https://object-storage.tyo2.conoha.io/v1/・・・/・・・/${imageName}`,　
                        headers: headers,
                    }

                    fs.createReadStream(`public/temporary/${imageName}`)
                    .pipe(httpRequest.put( options , function(err , response , body){
                      console.log( response.headers );
                      console.log( 'ステータスコード:',response.statusCode);
                      console.log( body );
                      if(err)console.log(err);
                      else {
                        console.log( 'アップロードしました' );
                      }
                    }

                } else {
                    const res = body;
                    console.log(res);
                }
            })
        })
   　 }
)
```

#5.VPS内の一時保存している画像データを削除
VPS内で一時保存している画像データは不要なので削除します。

```javascript:server.js
fs.unlink(`public/temporary/${imageName}`, (err) => {
  if (err) throw err;
})
```

#参考
[Conohaのオブジェクトストレージを使ってみた(REST API Ver)](https://dai-yamashita.hatenablog.jp/entry/20141103/1415038231)