JavaScript
vue.js
画像アップロード
nuxt.js

Nuxt.jsで画像を含む投稿機能を作る


はじめに

Nuxt.jsで画像を含む情報の投稿機能を作ったので備忘録で残します!

ちなみに今回開発を行う中で、以下の環境で開発を進めていました。

Web API: Rails 5.2.2

Front: Nuxt.js 2.3.4

今回はじめてAPIとフロントを切り分けて開発をしたので投稿機能を作るのにかなり手こずりました。まだ勉強しながらということもありダメダメな実装法かも知れませんが暖かく見守ってくださいまし🙇間違っているところとかがあれば教えていただけると幸いです😊

今回はNuxt.js側の実装法を書くので、Railsの実装法はあまり紹介しないのでご了承ください🙇

ちなみにAPIへのリクエストはcurlで書くとこんな感じです。

curl -X POST -F 'name=テスト商品' \

-F 'description=テスト説明' \
-F 'price=1000' \
-F 'item_count=3' \
-F 'images[]=@public/400x200.jpeg' \
-F 'images[]=@public/800x400.jpeg' \
http://localhost:4000/api/v1/products \
-H 'Content-Type: multipart/form-data'

これでリクエストを送ると商品情報が投稿できると言った感じです。今回はこのAPIをNuxt.js側から叩いてみたいと思います。


templateを書いていく

まず投稿ページの見た目の部分を書いていきます。今回はVue.js用の?bootstrapであるBootstrapVue を使って書きます。

導入方法はこちらから


/pages/products/new.vue

<template>

<div class="container">
<h2 class="text-center page-title">商品登録</h2>
<b-form enctype="multipart/form-data"/>
<b-form-group
id="exampleInputGroup1"
label="商品名"
class="form-margin"
label-for="exampleInput1"
>
<b-form-input
id="exampleInput1"
v-model="name"
required />
</b-form-group>

<b-form-group
id="exampleInputGroup2"
label="価格"
label-for="exampleInput2"
class="form-margin"
>
<b-form-input
id="exampleInput2"
type="number"
v-model="price"
required />
</b-form-group>

<b-form-group id="exampleInputGroup3" label="商品について" label-for="exampleInput3" class="form-margin">
<b-form-textarea
id="textarea"
rows="3"
v-model="description"
max-rows="6"
/>
</b-form-group>

<b-form-group
id="exampleInputGroup4"
label="在庫数"
label-for="exampleInput4"
class="form-margin"
>
<b-form-input
id="exampleInput4"
type="number"
class="form-width"
v-model="itemCount"
required />
</b-form-group>

<b-form-group
id="exampleInputGroup5"
label="商品画像"
label-for="exampleInput5"
class="form-margin"
>

<b-form-file class="mt-3" @change="onUpload()" ref="files" multiple plain />
</b-form-group>
<div class="text-center">
<b-button type="submit" variant="outline-info" @click="createProduct()">登録する</b-button>
</div>
</div>
</template>


ここでは<input><textarea />からのテキスト情報はv-modelを使って各DOMからの入力データとjsで扱うデータとを双方向でデータバインディングをしています。

  <b-form-input id="exampleInput2" type="number" v-model="price" required />

fileを選択する<input>は単純にバインディングをしてもファイル名しか取得できないのでデータそのものをjsで扱うためにchangeイベントで取得できるようにしています。

  <b-form-file class="mt-3" @change="onUpload()" ref="files" multiple plain />


投稿するためのjsを書いていく

次に投稿する処理を同様のファイルの<script>部分に書いていきます。


/pages/products/new.vue

<script>

export default {
data: function() {
return {
name: "",
price: 0,
description: "",
itemCount: 0,
images: "",
}
},
methods: {
onUpload: function() {
this.images = event.target.files;
},
createProduct: function() {
let formData = new FormData
formData.append('name', this.name)
formData.append('price', this.price)
formData.append('description', this.description)
formData.append('item_count', this.itemCount)
formData.append('shop_id', this.shopId)
for( let i = 0; i < this.images.length; i++) {
let image = this.images[i];
formData.append('images[]', image);
}
this.$axios.$post('http://localhost:4000/api/v1/products',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
console.log(reponse.data.status);
}).catch(error => {
console.log(error);
})
}
}
}
</script>

今回は<input type="file">からファイルが選択されたときにchangeイベントからonUpload()が走るようしています。

event.target.filesで選択されたファイルが取得できるのでこれをdataプロパティで宣言したimagesに代入します。またこれらは配列としてそれぞれ選択されたファイルにアクセスができるので、投稿の際にformDataとしてそれぞれ追加しています。(ここのformData.appendの部分もう少しきれいに書きたい。。。)

createProduct: function() {

let formData = new FormData
formData.append('name', this.name)
formData.append('price', this.price)
formData.append('description', this.description)
formData.append('item_count', this.itemCount)
formData.append('shop_id', this.shopId)
for( let i = 0; i < this.images.length; i++) {
let image = this.images[i];
formData.append('images[]', image);
}
this.$axios.$post('http://localhost:4000/api/v1/products',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
console.log(reponse.data.status);
}).catch(error => {
console.log(error);
})
}
}


ちょっとコケたあれこれ

上記のコードを書いていざ投稿ボタンをポチると以下のようなエラーが。

Access to XMLHttpRequest at 'http://localhost:4000/api/v1/products' from origin 

'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't
pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested
resource.

よく読んでみるとどうやらクロスオリジン関係でRails側で弾かれたみたいです。

とりあえず以下の記事を参考に対処すると動きました。

https://qiita.com/residenti/items/3a03e5e0268b354284b7


最後に

これでhttp://localhost:3000/products/new にアクセスすると動くと思います!また今回は特にVuexを考えて書いていないので学習を進めてそれを意識した書き方も載せられればと思っています〜

なにか指摘事項があれば教えていただけると嬉しいです