Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What is going on with this article?
@minato-naka

【AWS Amplify × Vue.js 簡単サーバーレスアプリ構築チュートリアル】⑤ StorageでS3に画像保存実装編

はじめに

この記事は、全部で6記事にわたるたチュートリアルの5つ目です。

① 概要説明編
② Amplify利用準備・初期設定編
③ Authでユーザー登録、ログイン機能実装編
④ API(Graphql)でCRUD実装編
⑤ StorageでS3に画像保存実装編
↑↑↑今ここ↑↑↑
⑥ Hostingでアプリ公開&自動デプロイ実装編

前回の記事では、AmplifyのAPI機能を使って
基本のCRUD機能を一通り実装しました。

今回はAmplifyのStorage機能を使って
S3に画像を登録する機能を実装していきます。

今回も、
Amplify利用準備・初期設定編のAmplify機能の利用手順で説明した通り、
①AmplifyCLIのaddでバックエンド設定追加
②AmplifyCLIのpushでバックエンドリソース作成
③フロントエンドにバックエンド連携コード実装
の3ステップでStorage機能を追加していきます。

AmplifyCLIのaddでバックエンド設定追加

ターミナルで下記コマンドを実行します。
amplify add storage

すると質問がいろいろ表示されるので
下記のように回答しましょう。

? Please select from one of the below mentioned services:Content (Images, audio, video, etc.)
? Please provide a friendly name for your resource that will be used to label this category in the project:amplifyvuealbum
? Please provide bucket name:amplify-vue-album
? Who should have access:Auth users only
? What kind of access do you want for Authenticated users?create/update, read, delete
? Do you want to add a Lambda Trigger for your S3 Bucket?No

下記メッセージが表示され、バックエンド設定ファイルが更新されます。

Successfully added resource amplifyvuealbum locally

コミット

さらに、前回のAPI編でテーブルを作成しましたが
Photoテーブルのs3keyカラムは追加していませんでした。
今回このカラムを利用するので、
schema.graphqlを編集してs3keyカラムを追加しましょう。

amplify/backend/api/amplifyvuealbum/schema.graphql
 type Photo
   @model
   @auth(rules: [{ allow: owner }])
   @key(name: "byAlbum", fields: ["albumID"]) {
   id: ID!
   name: String!
+  s3key: String!
   albumID: ID!
   album: Album @connection(fields: ["albumID"])
 }

コミット

AmplifyCLIのpushでバックエンドリソース作成

これでバックエンド設定は作成完了なので
Storageの追加と、テーブル定義の変更をpushして
実際にAWS上に反映させましょう。

下記コマンドを実行します。
amplify push

確認が表示されるのでyesにします。

| Category | Resource name           | Operation | Provider plugin   |
| -------- | ----------------------- | --------- | ----------------- |
| Storage  | amplifyvuealbum         | Create    | awscloudformation |
| Api      | amplifyvuealbum         | Update    | awscloudformation |
| Auth     | amplifyvuealbum921b97fd | No Change | awscloudformation |
? Are you sure you want to continue? Yes

さらに、今回はテーブル定義を変更したため、
それに伴ってquery、mutationも自動で修正するかどうかを聞かれます。
yesと答えて自動更新しましょう。

? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types?
This will overwrite your current graphql queries, mutations and subscriptions Yes

コミット

しばらく待って処理が完了すると、
実際にS3バケットが作成されているはずなので、
AWS上で実際に確認してみましょう。

フロントエンドにバックエンド連携コード実装

バックエンドにS3の準備ができたので、
実際に画像をS3に登録、取得するためのコードを実装していきます。

写真登録ページへの画像登録フォームの追加と
アルバム詳細ページへの写真一覧表示、削除機能の追加をします。

・写真登録

src/views/photo/Create.vue
  <template>
    <div>
      <h1>写真登録</h1>
      <form @submit.prevent="submitCreate">
        <label>Name</label><br />
        <input v-model="form.name" placeholder="Enter photo name" /><br />
-       <p>ここに画像登録フォーム</p>
+       <label>Photo</label><br />
+       <input type="file" @change="onFileChange" /><br />
        <input type="submit" value="Submit" />
      </form>
    </div>
  </template>
  <script>
  import { API } from "aws-amplify";
  import { createPhoto } from "../../graphql/mutations";
+ import { Storage } from "aws-amplify";
  export default {
    name: "PhotoCreate",
    props: {
      albumId: String,
    },
    data() {
      return {
        form: {
          albumID: this.albumId,
          name: "",
+         s3key: "",
        },
+       image: null,
      };
    },
    methods: {
+     onFileChange(e) {
+       this.image = e.target.files[0];
+     },
      async submitCreate() {
+       const s3key = new Date().getTime().toString(16) + this.image.name;
+       await Storage.put(s3key, this.image, {
+         level: "private",
+       })
+         .then((result) => {
+           this.form.s3key = result.key;
+         })
+         .catch((error) => {
+           console.log(error);
+         });
        await API.graphql({
          query: createPhoto,
          variables: { input: this.form },
        })
          .then((result) => {
            console.log(result);
            this.$router.push({
              name: "AlbumShow",
              params: { albumId: this.albumId },
            });
          })
          .catch((error) => {
            console.log(error);
          });
      },
    },
  };
  </script>

コミット

・アルバム詳細

src/views/album/Show.vue
  <template>
    <div>
      <h1>アルバム詳細</h1>
      <h2>{{ album.name }}</h2>
      <router-link
        custom
        v-slot="{ navigate }"
        :to="{ name: 'PhotoCreate', params: { albumId: albumId } }"
      >
        <button @click="navigate">Add Photo</button>
      </router-link>
      <table border="1">
        <tr v-for="(photo, index) in album.photos.items" :key="photo.id">
          <td>{{ photo.name }}</td>
          <td>
-           ここに画像表示
+           <amplify-s3-image :img-key="photo.s3key" level="private" />
          </td>
          <td>
            <button @click="deletePhoto(index, photo)">Delete Photo</button>
          </td>
        </tr>
      </table>
    </div>
  </template>
  <script>
  import { API } from "aws-amplify";
  import { getAlbum } from "../../graphql/queries";
  import { deletePhoto } from "../../graphql/mutations";
+ import { Storage } from "aws-amplify";
  export default {
    name: "AlbumShow",
    props: {
      albumId: String,
    },
    async created() {
      this.getAlbum();
    },
    data() {
      return {
        album: {
          name: null,
          photos: []
        },
      };
    },
    methods: {
      async getAlbum() {
        await API.graphql({
          query: getAlbum,
          variables: { id: this.albumId },
        })
          .then((result) => {
            console.log(result);
            this.album = result.data.getAlbum;
          })
          .catch((error) => {
            console.log(error);
          });
      },
      async deletePhoto(index, photo) {
        if (!confirm("Delete Photo?")) return;
+       await Storage.remove(photo.s3key, {
+         level: "private",
+       })
+         .then((result) => {
+           console.log(result);
+         })
+         .catch((error) => {
+           console.log(error);
+         });
        await API.graphql({
          query: deletePhoto,
          variables: { input: { id: photo.id } },
        })
          .then((result) => {
            console.log(result);
            this.album.photos.items.splice(index, 1);
          })
          .catch((error) => {
            console.log(error);
          });
      },
    },
  };
  </script>


+ <style scoped>
+ amplify-s3-image {
+   --height: 100px;
+ }
+ </style>

コミット

これで、AmplifyのStorage機能を使ってS3画像を登録、表示、削除する
コードは完成です。

簡単にポイントを解説します。

・写真登録ページ
まずAmplifyのStorage機能を使うため、Storageモジュールをインポートしています。
import { Storage } from "aws-amplify";

そして、フォーム送信の submitCreate() メソッドで、
下記のコードによりS3への画像登録を行っています。

Storage.put(s3key, this.image, {
        level: "private",
      })

Storageモジュールのputメソッドに、s3keyとファイルデータ、オプションを渡すだけです。
s3keyは任意の文字列で問題ないです。
(今回はランダム文字列 + 実際の画像ファイル名をs3keyとしてセットしています)
this.imageには今回追加した画像フォームでアップロードしたファイルデータが入っています。
オプションには、privateをセットすることで、画像をアップしたユーザ自身しかその画像を参照更新できない設定にしています。

これで、Storage.putの次にあるAPIによるPhotoデータ登録時に、
先ほどカラム追加したs3keyもDBに登録されるようになっています。

・アルバム詳細
アルバム詳細の方では、S3画像を表示する機能と削除する機能の2つを追加しています。

S3画像の表示はこの1行だけで実装できます。
<amplify-s3-image :img-key="photo.s3key" level="private" />

この amplify-s3-image というタグは、
今回利用しているAmplifyのUIライブラリが用意してくれているコンポーネントです。
https://docs.amplify.aws/ui/storage/s3-image/q/framework/vue

:img-key に表示したい画像のS3キーをセットするだけでS3の画像を表示できちゃいます。
(先ほどs3keyはPhotoデータに登録されるようになっているので、ここで取得できる)
※そのままだとアップロードした画像のサイズそのまま表示されてレイアウトが崩れるので、一番下にサイズを統一するCSSを書いています

画像削除機能の方では、もともと作っていた deletePhoto() のメソッドに
下記コードを追加するだけです。

Storage.remove(photo.s3key, {
        level: "private",
      })

Storageのremoveメソッドにs3keyとオプションを渡すだけです。
これで、S3上から画像を削除することができます。

実際に画像の登録、表示、削除の機能をいろいろ動かしてみましょう。
※動作確認時にはPhotoを1件も登録していない(または新品の)アルバムで確認してください
(既存のPhotoデータはs3keyカラムがnullの状態なので、エラーが発生してしまいます)

画像登録をすると、
s3バケットに「/private」というディレクトリが作られ、
その配下にユーザーごとのディレクトリが作られ、
その下に画像データが登録されます。
AWSのS3のページに行って実際に確認してみるといいです。

また、画像を削除したらS3上からもその画像が削除されることを確認してみましょう。

これで、AmplifyのStorage機能を利用した
S3への画像登録、表示、削除機能の実装が完了です。

おわりに

これで、Auth、API、Storageの機能が導入されたので、
アプリの機能としてすべて実装完了です。

最後に、次の記事でAmplifyのHosting機能を利用して
アプリ公開する方法、GitHubと連携して自動デプロイする方法を解説します。
(次に進む前に、LGTMしてもらえるとうれしいです)
【AWS Amplify × Vue.js 簡単サーバーレスアプリ構築チュートリアル】⑥ Hostingでアプリ公開&自動デプロイ実装編

1
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
minato-naka
アジアクエスト株式会社(福岡オフィス) PHP/Laravel/Rails/AWS/Vue.js/Docker
asia-quest
DX実現を目指す企業と並走する「デジタルインテグレーター」です。 通常のシステムインテグレーションだけではなく、お客様のDXを共に考えるコンサルティングから、 DXに必要な様々なデジタルテクノロジーの専門チームを有し、お客様のゴールに向けてシステムの設計、開発、運用までを一貫して請け負います。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
1
Help us understand the problem. What is going on with this article?