Go GraphQL AWS S3への画像アップロード

今回はGraphQLで画像のアップロードを行なっていきたいと思います。 アップロード先のストレージにはAWSのS3を使用します。 尚、この記事ではS3のバケットポリシーやAWS関連については深掘りしません、aws-sdk-go-v2 というGoのaws-sdkを使用しますがコード関連が中心となります。

使用技術

  • gqlgen v0.13.0
  • aws-sdk-go-v2

*このブログでは今までにも何度かgqlgenを使用した内容を紹介しており、 コードは同じレポジトリに随時追加しております。 お時間あればぜひご覧頂けると嬉しいです。

schema

まずはスキーマを定義します。 ファイルは適宜置き換えて下さい。

schema.graphql

type Mutation {
  uploadFile(input: UploadFileInput!): UploadFilePayload!
}

file.graphql

input UploadFileInput {
  file: Upload!
}

type UploadFilePayload {
  uploadedPath: String!
}

シンプルなMutationですが、 inputの Upload! 型に注目して下さい。 これは gqlgenの Scalar type と言って組み込みのカスタム型です。 Upload 型は以下のような構造体を呼び出します。

type Upload struct {
    File        io.Reader
    Filename    string
    Size        int64
    ContentType string
}

Scalar typeを使用するには 任意の *.graphql ファイル にコードを追加する必要があります。

schema.graphql

scalar Upload

これでスキーマ定義ができたので、gqlgen コマンドで型とResolverを生成します。

*筆者はファイルを分割する為に自動生成の機能をオフにしています。

Reolver

func (r *mutationResolver) UploadFile(ctx context.Context, input model.UploadFileInput) (*model.UploadFilePayload, error) {
    panic(fmt.Errorf("not implemented"))
}

models_gen.go

type UploadFileInput struct {
    File graphql.Upload `json:"file"`
}

type UploadFilePayload struct {
    UploadedPath string `json:"uploadedPath"`
}

UploadFileInput の構造体に注目して下さい。 graphql.Uploadを呼び出しています。

これが先ほど紹介した以下の構造体です。

type Upload struct {
    File        io.Reader
    Filename    string
    Size        int64
    ContentType string
}

アップロードするファイルは この構造体としてResolverに渡されます。

次はアップロード先のs3の準備を行います。

s3の準備

今回はアップロード先にs3を使用するので事前にバケットの作成と設定をして下さい。アップロード可能な状態に設定する必要があります。

次に必要なパッケージをダウンロードします。

以下のパッケージをそれぞれ go get して下さい。

 "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    "github.com/aws/aws-sdk-go-v2/config"

The AWS SDK for Go requires Go 1.15 or later.

sdk-v2 は Goのversion 1.15から対応になります。

aws-sdk-go-v2 は比較的新しいので aws-sdk-go と混同しないようお気をつけ下さい。 自動インポートなどを使用すると v1 の方が呼び出されたりするので注意が必要です。

Getting Started with the AWS SDK for Go V2 | AWS SDK for Go V2

Resolverの実装

事前準備が完了したのであとは実装するのみです。 *今回はレイヤーやモジュール分割を行いません。

func (r *mutationResolver) UploadFile(ctx context.Context, input model.UploadFileInput) (*model.UploadFilePayload, error) {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        return nil, err
    }

    client := s3.NewFromConfig(cfg)
    uploader := manager.NewUploader(client)

    res, err := uploader.Upload(ctx, &s3.PutObjectInput{
        Bucket:      aws.String("bucketName"),
        Key:         aws.String("uploadPath/" + input.File.Filename),
        Body:        input.File.File,
        ContentType: aws.String(input.File.ContentType),
    })
    if err != nil {
        return nil, err
    }

    return &model.UploadFilePayload{
        UploadedPath: res.Location,
    }, nil
}

上から順に解説します。

こちらのコードはawsのconfigを読み込んでいます。 以下のように何もオプションを指定しないことで

   cfg, err := config.LoadDefaultConfig(context.TODO())

以下の順に credential を読みに行きます。

1.Environment variables. Static Credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) Web Identity Token (AWS_WEB_IDENTITY_TOKEN_FILE)

2.Shared configuration files. SDK defaults to credentials file under .aws folder that is placed in the home folder on your computer. SDK defaults to config file under .aws folder that is placed in the home folder on your computer.

3.If your application uses an ECS task definition or RunTask API operation, IAM role for tasks.

4.if your application is running on an Amazon EC2 instance, IAM role for Amazon EC2.

Configuring the AWS SDK for Go V2 | AWS SDK for Go V2

本番環境と開発環境でcredentialを別の場所に配置しても自動的に読みに行く場所を変更してくれるので便利ですね。

    client := s3.NewFromConfig(cfg)
    uploader := manager.NewUploader(client)

    res, err := uploader.Upload(ctx, &s3.PutObjectInput{
        Bucket:      aws.String("bucketName"),
        Key:         aws.String("uploadPath/" + input.File.Filename),
        Body:        input.File.File,
        ContentType: aws.String(input.File.ContentType),
    })

あとは上記のように inputから s3に渡す構造体を整形し Uploadメソッドでアップロードします。 簡単ですね!

APIを実行

それでは実装したAPIを実行してみましょう。 恐らく現時点でgqlgenのplaygroundには ファイルのアップロード機能は搭載されていません。

その為今回は以下 firecamp というクライアントアプリを使いました。

https://firecamp.io/graphql

画像下部にあるようにファイルを指定して実行します。

無事にアップロードしたpathが返ってきました! filepathをDBに保存すればフロント側から呼び出し画像表示ができますね。

今回の内容は以上です。


最後に、 このブログではweb開発について発信していくのでまたご覧頂けると嬉しいです。 最後までお読み頂きありがとうございました。