Nest.jsから、Google Cloud Storageにアクセスしてデータをアップロードする方法

## 概要
Nest.jsから、GoogleのCloud Storageにデータをアップロードする方法について解説します。（Nest.jsから使う方法の記事があまり見つからないので書こうと思います:point_right:）

## 前提
* Google Cloud のアカウントを作成している。
* Google Cloud CLI（gcloud CLI）をダウンロード済みで、初期化してある。
* Google Cloud のサービスアカウントを作成し、その際にJSONファイルをダウンロードしてある。（これが後の、サービスアカウントキーのJSONファイルにあたります）
* Nest.jsおよびTypeScriptの基本的な使い方を理解している。

## 方法
### STEP1: Nest.jsのプロジェクトを作る。
以下のコマンドでサンプルのプロジェクトを作成する。

```ターミナル.
nest new --skip-git プロジェクト名
```
ここで、`--skip-git`と書くのは、gitの追跡ブランチが強制的にmainになってしまう現象の発生を防ぐためです。[こちら](https://qiita.com/maiamea/items/e8eb92cd52c3a750b072)の記事が参考になるかと思います

プロジェクトが作成できたら、
```ターミナル.
cd プロジェクト名
npm run start
```
の順にコマンドを実行して、`localhost:3000`にアクセスできればOKです。

### STEP2: サービスアカウントキーのJSONファイルを、Nest.jsのプロジェクトディレクトリ直下に配置する。
Cloud Storageにアクセスするために必要な情報として、作成済みのサービスアカウントキーのJSONファイルをプロジェクトディレクトリ直下に配置します。
JSONファイルには、以下の内容が記されています。（Chat GPTから引用）

```
type: サービスアカウントキーであることを示す文字列。通常は "service_account"。

project_id: サービスアカウントが関連付けられているGCPプロジェクトのID。

private_key_id: サービスアカウントキーのプライベートキーのID。

private_key: サービスアカウントキーのプライベートキー自体。この部分は非常に長くなり、改行などが含まれることがあります。

client_email: サービスアカウントのメールアドレス。通常は <service-account-name>@<project-id>.iam.gserviceaccount.com の形式。

client_id: サービスアカウントのクライアントID。

auth_uri: OAuth 2.0の認証エンドポイントのURI。

token_uri: OAuth 2.0のトークンエンドポイントのURI。

auth_provider_x509_cert_url: 公開鍵証明書を取得するためのURI。

client_x509_cert_url: クライアント証明書を取得するためのURI。
```

### STEP3: サービスアカウントにCloud Storageの書き込み権限を与える。
忘れてはいけないのが、Cloud Storage関連の権限をサービスアカウントに付与しておくことです。これが済んでいないと、アクセスしたときに403エラー（権限エラー）が返ってきてしまいます。手順は以下の通りです。

1. Google Cloudのコンソールにアクセスする。
2. メニューから、「IAMと管理」→「IAM」の順に選択する。
3. 使用するサービスアカウント名の書いてある行の一番右側の鉛筆マークをクリックして、「＜アカウント名＞に対する権限の編集」ダイアログを開く。
4. 「別のロールを追加」をクリックして、検索ボックスで「Cloud Storage」と入力して、「Storageオブジェクト管理者」を選択する。
5. ダイアログに戻ったら、「保存」をクリックする。

以上で権限の付与は完了です。

### STEP4: Cloud Storageにアクセスしてデータをアップロードする処理を実装する。
実装する前に、以下のコマンドをターミナルで実行して、クライアントライブラリを導入します。
```ターミナル.
npm install @google-cloud/storage
```
ライブラリがインストールできたら、以下のように処理を実装していきます。
```app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { StorageController } from './storage/storage.controller';
import { HttpModule } from '@nestjs/axios';
import { StorageService } from './storage/storage.service';

@Module({
  imports: [HttpModule],
  controllers: [AppController, StorageController],
  providers: [AppService, StorageService],
})
export class AppModule {}

```
```storage/storage.controller.ts
import { HttpService } from '@nestjs/axios';
import { Controller } from '@nestjs/common';
import { Get } from '@nestjs/common';
import { StorageService } from './storage.service';

@Controller('storage')
export class StorageController {
  constructor(
    private readonly httpService: HttpService,
    private readonly storageService: StorageService,
  ) {}

  //cloud storageにテストデータをpushする
  @Get('data-push-test')
  dataPushTest(): any {
    return this.storageService.dataPushTest();
  }
}

```
```storage/storage.service.ts
import { Injectable } from '@nestjs/common';
import { Storage } from '@google-cloud/storage';

@Injectable()
export class StorageService {
  private storage: Storage;
  constructor() {
    this.storage = new Storage({
      projectId: '<プロジェクトID>',
      keyFilename: './<サービスアカウントのJSONファイル名>.json',
    });
  }
  private bucketName = '<バケット名>';

  //cloud storageにテストデータをpushする
  dataPushTest(): Promise<{ message: string }> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file('push-test/push-test.json');
      const data = {
        name: 'test',
      };
      return new Promise<{ message: string }>((resolve, reject) => {
        file.save(JSON.stringify(data), (err) => {
          if (err) {
            const errMessage =
              'プッシュ時にエラーが発生しました！' + err.message;
            reject(new Error(errMessage));
          } else {
            resolve({ message: 'プッシュに成功しました！' });
          }
        });
      });
    } catch (error) {
      const errMessage = '何らかのエラーが発生しました。' + error.message;
      return Promise.reject<{ message: string }>({ message: errMessage });
    }
  }
}

```

ここで、バケット名とは、Cloud Storageにデータを格納するためのコンテナです（[Cloud Storageドキュメント](https://cloud.google.com/storage/docs/buckets?hl=ja)を参照）。
１つのバケットの中に、ファイルやフォルダが含まれ、これらは必ずバケットに格納される必要があります。

また今回は、データとして簡単なオブジェクトを用意しています。画像ファイルなどのアップロードについては、別の機会に記事にできればと思います。

### STEP4: サーバを起動して実行する。
サーバを起動して、作成したエントリポイント`localhost:3000/storage/data-push-test`に、Postmanなどを使ってGETリクエストでアクセスします。
これによって、Cloud Storageの指定したバケット内で、`push-test`というフォルダ内に`push-test.json`というファイルが作成（アップロード）されます。

### STEP5: アップロードされたか確認する。
Google CloudのCloud Storageの画面を表示されると、バケットがいくつか表示されます。プログラムで使用したバケット名を選択すると、上記のディレクトリとその中のファイルが作成できていることがわかります。


## 終わりに
今回は、Nest.jsからGoogle Cloud Storageにデータをアップロードする方法を紹介しました。
もしかしたら説明不足のところがあるかもしれません。また間違いが含まれているかもしれません。何かありましたら、お気軽にコメントへお寄せください。