Laravel+Vueでゼロからサービスを作りました。

こんにちは、タカです。

2020/7/6に『RECOMEN』というサービスをリリースしました。

 

https://www.recomen.tokyo/

 

自分でゼロからデザイン、DB設計、実装をしデプロイまでした初めてのサービスです。

完成したことの喜びがある反面、自分の課題がくっきりと浮かび上がったのも事実。

この記事では、サービスの概要を紹介したあとに実装において苦労したところ、またどんなことが課題として残ったのかをお伝えしたいと思います。

特に駆け出しのエンジニアや未経験でこれから何かを作ろうとしている、作ってるけどあまりうまくいってない・・・という方の参考になれば嬉しいです。

 

RECOMENというサービスについて

サービス概要

このRECOMENというサービスは、「個人的な嗜好の共有」ということをコンセプトにしています。自分では当たり前に使っている製品やサービスも、他人から見ると「何これ?!」「こんな面白い、素敵なモノがあるんだ!」という発見がたくさんあります。

このRECOMENではこうした自分の好きなもの、ずっと使っているもの、偏愛しているもの、そうしたものを自分の言葉とともに紹介し合う、という「場」を提供しています。

今まで自分が知りもしなかった製品やサービスを知ることで、日々の生活が楽しくなったり彩りを与えてくれたり、そうした体験ができるWebサービスがこのRECOMENです。

 

機能一覧

ログイン・ログアウト機能

会員登録

メール送信によるパスワード再設定機能

Googleアカウントによるログインと会員登録

写真投稿・プレビュー(写真の保存はAWS_S3で行う)

プロフィール編集(こちらも画像保存含む)

フォロー・フォロワー

いいね機能

投稿のタグ付けとタグ検索

Twitterでのシェア連携

レスポンシブ対応

使用言語

Laravel:6.18.16

Vue:2.6.11

Sass:1.15.2

 

なぜRECOMENというサービスを立ち上げたのか?

このサービスを立上ちげた理由。

それは「自分がこんなサービスがほしいと思ったから」です。

 

自分は、使っていてなぜだか心地いい、テンションがあがる、デザインがとても好きだ・・・そうした製品やサービスを日々の暮らしの中で使ったり、身につけたいと思っています。

ただそうした製品・サービスは簡単に見つかるものではなく、実店舗に行く・雑誌から情報を得る・ネットで検索するということを能動的に繰り返すことで出会えたりします。

その中で自分にとって「いいな」と思えるものが一番見つかりやすかったのは、「他者が使っているものを知る・紹介される」ということです。

 

例えばこの「ヒバブロック」も1つの例です。

http://store.culdesac.jp/?pid=114396951

 

これは青森のヒバから取れる精油を、同じ素材のウッドブロックに垂らすことでアロマ効果や消臭効果を得られるという製品です。

これを知ったキッカケは、雑誌やネットではなく同じ職場で働いている同僚が、ある日お店のレストルームに置いたことでした。

それまでこんな製品が存在することすら知らなかったのですが、知った翌々日には自宅のお手洗いにも置いてました。

 

こんな風に、自分が当たり前に使っている製品やサービスは誰かにとっては当たり前じゃないことがたくさんあります。

こうした体験を共有したい、誰かが使ってる、自分がまだ知りもしないモノやサービスを知りたい。

そうした想いから、このRECOMENというWebサービスを作りました。

 

実装で苦労したところ

S3への画像データ保存

今まで画像保存はMySQLで行っていましたが、今回はAWSのS3というストレージサービスを用いて作成することにしました。

ただ、この実装をするまでAWSで画像保存機能を実装したことはなく、全くの手探りからのスタートでした。

そのため、公式ドキュメントを見つついくつかQiitaや技術ブログも参考にしての実装となりましたがなんとか実装することができました。

<参考にしたサイト>

https://readouble.com/laravel/5.8/ja/filesystem.html

https://qiita.com/nobu0717/items/51dfcecda90d3c5958b8

LaravelでS3に画像をアップロードして表示・削除する

 

画像投稿+プレビューをVueコンポーネント化した

画像周りでの機能実装での苦戦が続きます。

今回はLaravel+Vueを用いてサービスを作成していますが、画像を編集・アップロードするのは、記事の投稿(最大3件)とプロフィール編集とで計4×2(編集と投稿)で8箇所の実装が必要でした。

ただ、それら全てに同じようなコードを書くと冗長だし、メンテナンス性も低下するのでVue側でアップロード機能は作り、Laravel側からpropsで値を渡すことで、それぞれの画像を表示・保存できるようにしました。

 

例えば画像投稿だとLaravel側はこんな感じです。

     <div class="c-photo--block">
            <img-view
            select-pic-first="pic1"
            img-data-first="{{ $product->pic1 ?? '' }}"
            >
            </img-view>
            <span class="c-photo--imgNum">Image_1</span>
            
        </div>
        <div class="c-photo--block">
            <img-view
            select-pic-second="pic2"
            img-data-second="{{ $product->pic2 ?? '' }}"
            >
            </img-view>
            <span class="c-photo--imgNum">Image_2</span>
        </div>
        <div class="c-photo--block">
            <img-view
            select-pic-third="pic3"
            img-data-third="{{ $product->pic3 ?? '' }}"
            >
            </img-view>
            <span class="c-photo--imgNum">Image_3</span>
        </div>

コード中の<img-view>がVueのコンポーネントですが、それを共通で使いつつ各投稿箇所でVueにpropsで2つの値を渡しています。

 

select-pic-*** →これは「どこにあるinputのデータか」をVueに渡しています。

img-data-*** →これは「編集画面の際にどの画像をあらかじめ表示するか」をVueに渡しています。

 

これだけですと分かりづらいので、Vue側のコードも記載します。

 

<template>
    <label>
        <div class="c-upload--wrapper l-row--central">
            <input class="c-upload--input" type="file" ref="file" id="" :name="picNum" @change="onFileChange"> 
            <i aria-hidden="true" class="fas fa-plus fa-7x c-upload--icon"></i>
            <img class="c-upload--img" :src="uploadedImage" alt="" v-show="uploadedImage">
            <button class="c-button--resetImg c-button--resetImg__margin" v-if="uploadedImage" @click="resetFile">Reset</button>
        </div>
    </label>
</template>
<script>
export default {
    props: {
        selectPicFirst: {
            type: String,
        },
        selectPicSecond: {
            type: String,
        },
        selectPicThird: {
            type: String,
        },
        selectUserProf: {
            type: String,
        },
        imgDataFirst: {
            type: String,
        },
        imgDataSecond: {
            type: String,
        },
        imgDataThird: {
            type: String,
        },
},

data() {
return {
            uploadedImage:'',
        }
    },
mounted() {
 if(this.imgDataFirst) {
                     return this.uploadedImage =this.imgDataFirst;
        }elseif(this.imgDataSecond){
                    return this.uploadedImage =this.imgDataSecond;
        }elseif(this.imgDataThird){
                   return this.uploadedImage =this.imgDataThird;
        }else{
                 return this.uploadedImage =this.imgDataUserProf;
        }
    },
computed: {
           picNum() {
               if(this.selectPicFirst) {
                   return this.selectPicFirst;
               }else if(this.selectPicSecond) {
                   return this.selectPicSecond;
               }else if(this.selectPicThird){
                   return this.selectPicThird;
               }else{
                   return this.selectUserProf;
               }
           }
    },
</script>

 

まず

select-pic-*** →これは「どこにあるinputのデータか」

についてですが、実際に画像がS3に保存されるのは、下記の部分が担っています。

 

 <input class="c-upload--input" type="file" ref="file" id="" :name="picNum" @change="onFileChange"> 

 

この「name:picNum」が大事で、これをpropsでlaravelから渡すことで、アップロードするimgを振り分けています。

 

次に

img-data-*** →これは「編集画面の際にどの画像をあらかじめ表示するか」

についてですが、これは編集する際に画面が読み込まれたタイミングで、S3に保存している画像を表示するために値をLaravelからVueに渡しています。

 

 img-data-first="{{ $product->pic2 ?? '' }}

 

これはLaravel側のコードですが、こうすることでもし$product->pic2に既にDB常にS3の画像パスが保存されていたら、その値をpropsでVueに、ない場合にはnullは許容する、という意味になります。

Null合体演算子

 

編集画面では通常自分がアップロードした画像はすでに表示されていて、そこから変更や削除というアクションになります。

そのため、画面が表示される際に同時にアップロードされている画像も表示させる必要がありました。

この機能を実現するために今回はVueの「mounted」を利用しています。

 

どういうことかと言うと、

 <img class="c-upload--img" :src="uploadedImage" alt="" v-show="uploadedImage">

ここに:src=”uploadedImage“とすることで、もし img-data-first="{{ $product->pic2 ?? '' }}

に値があればVue側にpropsとしてそれが渡され、mountedでそれをreturnして、S3の画像パスを入れている、という形です。

 

今回の画像周りの機能について、実装はできました。

ただ、いいコードだとは少しも思っていません。if/elseを頻繁に使い、propsでの値もかなり多くなっています。それによってコードも長いし、処理も遅くなると思います。

今後学習や経験を重ねることで、いつかリファクタしたいところです。

 

デプロイ

自分で作ったサービスやアプリは、世の中に出すことで初めて完成となります。

そのため、今回は初めてこの世の中に出すこと=「デプロイ」を行いました。

 

ただ・・・・・・・これがめちゃくちゃ大変でした。

今回はgitHubにローカルで作成したLaravelのプロジェクトをプッシュして、それをエックスサーバーにクローンすることでデプロイしました。

その過程での、.envファイル設定やDBへの接続・シンボリックリンクの作成などでどハマりし、公開するのを諦めようかな・・・と思ったぐらいです。

このデプロイ部分はまた別途記事にしようと思います。

<参考サイト>

https://qiita.com/n_oshiumi/items/2a1cc7d147ee1eff3e23

https://soybelln.com/blog/deployment2/

Laravelで作ったWEBアプリをXserverにデプロイする方法

 

課題と今後の学習について

 


本当、その通りでした。

今回のサービスをリリースしたことで、ものすごいたくさんの課題が見えてきました。

以下は自分が特に「今後意識して取り組まないとな」と感じたことを、まとめています。

 

デザインとUI/UXの知見が足りない

今回の実装で特に時間がかかったのが、CSSとレスポンシブ対応です。

で、なんで時間がかかったのかというと、デザイン・UI/UXの知識と経験が足りないからだな、と感じました。

 

ボタン1つとっても、ユーザーが使いやすい見た目・デザイン・動きはどういうものがいいのか、そうした知見があれば、margin/padding/font-color/:hoverなどは少ない試行回数で決めることができます。

 

でも自分にはそうした経験が圧倒的に不足しており、コードを書いては確認、コードを書いては確認・・・。よし、と思っても「これで本当にいいのか?」と迷うこともありました。

 

これによってバック側の処理はできたものの、フロント側の実装に非常に苦労し時間がかかりました。

 

今後は下記の書籍やサイトを参考にしつつ、「どんなUI/UXが最適なのか」「いいデザインとはどういうことで、具体的なCSSコードはどうなっているのか」を、アウトプットを通じて自分の中に落とし込みたいと思っています。

 

https://www.hypertextcandy.com/design-trace

https://peaks.cc/books/ui_design

 

実機テスト

画面表示の確認をPC/スマートフォン、どちらもChromeのDevツールで行っていました。

そしていざ自分のIPhoneでサイト確認したときに、レイアウトがめちゃくちゃ崩れていました。

その修正をすると、他の部分に影響してまた別のところを修正して・・・とCSS修正の堂々めぐりが発生しました。

 

今後は必ず実機でデザインやレイアウトを確認し、CSSのスタイルを当てていきたいと思います。

 

<参考サイト>

iPhoneとMacで実機の表示確認をしてデバッグする方法

 

FLOCSSのマイルール化

CSS設計手法の1つ、FLOCSSを取り入れて今回実装を進めました。

初めのうちは命名規則も厳密にし、Foundation/Layout/Component/Projectという各セクションの振り分けもうまくいっていました。

 

ただ、コード量や増えるにつれて特にComponetとProjectの住み分けが曖昧になっていきました。

結果、CSSコードの原則である、予測・再利用・保守・拡張という指針は到底達成できていないようなコードだと思います。

以前も簡単なLPのアウトプットをFLOCSSを使って行いましたが、同じような問題が発生しました。これは単に自分の経験が足りないのか、もしくはFLOCSSという設計手法が向いていないのかもしれません。

 

今後はアトミックデザインという設計手法+Storybook+Vue or Reactを学習し、この「CSSのカオス化」が起こらないようにしていきたいです。

 

辞書みたいに厚いけど、いま自分が知りたいことがめちゃくちゃ書いてありました。

 

他者が見てもわかるコードを書く

今回の実装について、「自分の開発だから、どういう機能を期待してのコードかはわかる」という前提でコードを書いてしまったな、と思いました。

でも、実際の現場では他者が自分のコードを見てそれを評価、修正する必要があるかと思います。

そうした時にぱっとコードを見て「どんな機能や動きを想定したものか」を判断するには、コメントをつけて自分がどういう意図で書いたのか、を記録する方が間違いなくいいと感じました。

今回の実装について、沢山ググって他者のコードを見る機会がありましたが、「で、結局どんな挙動をするの?」ということを、コードを見ないとわからないというのは、時間がもったいないと強く感じました。

最初から、「画像をリサイズしてS3にアップロードするためのコード」とコメントしてあった方がはるかに解読コストは低いです。

 

今後は「他者に見られる前提でコードを書く」を意識していきたいと思います。

具体的にはコメントを残す、条件分岐での早期return、改行やインデントをきっちりと揃えて見やすくする、ということを実践したいと思います。

 

サービスをリリースする前に読んでおけば・・・・っ!

収益化を考慮していなかった

最後はプログラミングではなく、ビジネスの話しです。

仕事としてプログラミングを行う、ということのゴールは「価値を生む。利益を出す」ということだと思っています。

極論ですが、どんなに見た目が汚ないスパゲッティコードだとしても、それを使うお客様やクライアントの課題を解決し、利益を出すのであればそのコードは「価値がある」と言えます。

逆にものすごい美しいコードが書かれていて、一切のバグもなく最新の技術で作られたサービスでも、使う人がおらず利益が出ていなければ、それは「存在しないのと一緒」かもしれません。

 

今回は自分の学習のためにサービスをリリースしましたが、「どのように収益をつくるのか」「どんな層に、どのようなアプローチでサービスを知ってもらい、使ってもらうのか」といったマーケティングとビジネスモデルの双方の視点が欠落していたように思います。

もちろん、サービスを作って簡単に利益が出るとは思っておらず、繰り返しリリースと検証をすることで初めて達成できることだと考えています。

「利益を出そうと考え、サービスを作ろう」と意識して作るのと、最初から「このサービスは自分の勉強のためにつくろう」として作るのとでは、全く違う結果になると思います。

 

それでいうと、今回は「自分のためにサービスを作ろう+学習のアウトプットとして作ろう」という意識が強かったと感じています。

今後はこうしたもっと俯瞰した視点、「ビジネスモデルとマーケティング」をプログラミング学習と並行して勉強し、またサービスを作っていきたいと考えています。

・・・ものすごい難しいですけど。

 

おわりに

 

かなり長くなってしまいました。

今回この「RECOMEN」をゼロから作ることで、自分の力や経験が積めたことはもちろんですが、何よりも収穫だったのは「自分に今足りないものは何か。今後どういった学習をするべきなのか」ということがクリアに見えたことです。

 

前述した課題を1つ1つ意識して取り組み、また新たにサービスを作りたいと思います。

そこでまた課題が出るかと思いますが、その繰り返しが「エンジニア」という仕事なのかもしれない、と感じました。

 

最後までお読み頂きありがとうございます。

少しでも読んで頂いた方の役に立っていれば、と思います。

 

では、また。

最新情報をチェックしよう!