へっぽこが1週間でwebサービスを開発する。どっちらいくって何？-

![どっちらいく.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/4b944f0b-8925-65a1-90e9-e29b14642213.png)

[どっちらいく](https://dotti.buzz/)をリリースしました。 2020.05.24

今後も運営していくかは今の所未定です。。

#開発経緯

だらさんって方が開催しているイベント#web1weekに参加しテーマが「like」だったので素直にlike=好きと捉えて今回のサービスを作成しました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">5/18～5/24の1週間でWebサービスを作るイベントです！　Hello worldレベルのサービスでもOKですのでぜひご参加をお願いします！ <a href="https://twitter.com/hashtag/%E9%A7%86%E3%81%91%E5%87%BA%E3%81%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2?src=hash&amp;ref_src=twsrc%5Etfw">#駆け出しエンジニア</a> 仲間が多い方は是非シェアもお願いします～<br>お題は「Like」です。<a href="https://t.co/ORZQGb6Yu2">https://t.co/ORZQGb6Yu2</a> <a href="https://twitter.com/hashtag/Crieit?src=hash&amp;ref_src=twsrc%5Etfw">#Crieit</a> <a href="https://twitter.com/hashtag/web1week?src=hash&amp;ref_src=twsrc%5Etfw">#web1week</a> <a href="https://twitter.com/hashtag/StayHome?src=hash&amp;ref_src=twsrc%5Etfw">#StayHome</a></p>&mdash; だら🎄5/18～5/24開催Webサービス作るイベント (@dala00) <a href="https://twitter.com/dala00/status/1262035532386689025?ref_src=twsrc%5Etfw">May 17, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#僕のスペック
・**<font color="Orange">へっぽこエンジニア</font>**
・開発歴：３年
・個人開発歴:半年(2020.01に**今年は個人開発をやる！**と決めた。)
・開発言語：python(半年)
・昨今の新技術のスピードにはついていけてない。
・dockerに興味をつ(現在のレベルは**クジラかわいい**と思ってるだけ)
・Flutterにも興味を持つ(最近までjavascript系のフレーム枠だと思ってた。)

#本番環境
![どっちらいく-構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/5f2498ad-3087-b786-465e-12868d1e9365.png)

こんな感じ。

DBの開発環境はmysqlを利用していましたが、何も考えずHerokuにデプロイ後
マイグレイトしたらPostgreSQLで作成されたので致し方なくPostgreSQLをそのまま利用。
(herokuのプラグインで勝手にターゲットのDBがPostgreSQLになったのかな？)

今回は**完全無料環境**を目指してHerokuを利用してみました。
(ドメインはもともと所有していたドメインのサブドメインを利用しているので実質完全無料ということにしておきます。)

###cloudflare
**cloudflare**は今回の完全無料環境を構築する上で一番のネックとなったSSL証明の無料化実現にとても役立ちました！！

herokuは無料でLet's Encrypt(無料SSL証明)を利用できるものの、条件として月額7ドルくらいするプランじゃないと利用できませんでした。。

###cloudinary
cloudinaryは詳細は後ほど記載しますが、動的にOGP画像を生成できたので採用しました。
”**動的にOGP画像を生成**”：個人的にこれが今回の難関の壁でした。

Canvasとかいうものの利用も検討しましたが、僕にはまだよく理解することができませんでした。。。
今回は元々用意した画像にただ、文字を載せたいという用件だけでしたので、いまの僕にはオーバースペックであることは理解できました。。

そこで、他に色々探していたら、cloudinaryは画像URLにパラメータを付与するだけで、画像に文字を被せることができるとわかり、採用に至りました！

以下のようなOGP画像が動的に作成できます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">OGPのテスト<br> <a href="https://t.co/atgxtXq32N">https://t.co/atgxtXq32N</a> <a href="https://twitter.com/hashtag/%E3%81%A9%E3%81%A3%E3%81%A1%E3%82%89%E3%81%84%E3%81%8F?src=hash&amp;ref_src=twsrc%5Etfw">#どっちらいく</a> <a href="https://twitter.com/hashtag/web1week?src=hash&amp;ref_src=twsrc%5Etfw">#web1week</a></p>&mdash; ふーど。@個人開発 (@fooddev24) <a href="https://twitter.com/fooddev24/status/1264226186441285634?ref_src=twsrc%5Etfw">May 23, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

###UptimeRobot
UptimeRobotの利用目的としては、Herokuが無料環境だと30分アクセスがないと一時的にサーバが停止状態になるという条件を回避するために利用しました。

ネット上にはHerokuのアドインを利用して実現する系の記事が多かったのですが、これも僕からしたらオーバスペックに感じたのでもっと簡単に(ただ誰かが30分おきにアクセスさえしてくれればいい)実現できる方法を模索していたところ本サービスを見つけました。

UptimeRobotの元々の利用用途はサーバやネットワークの死活監視でしたが、５分くらいで任意間隔で指定サーバへの自動アクセスの設定ができたので採用しました！

#完全無料環境の構築手順(サブドメインを使えばね。。)

今回は僕はDjangoを利用していたのでDjangoを前提に記載しますが、多くの内容はDjango以外でも同じだと思います。

##Herokuへのデプロイ
DjangoをHerokuへデプロイする際には以下の操作が必要になります。

必要モジュールのインストール

```terminal
$pip install gunicorn
$pip install django_heroku
```
django_herokuはherokuにデプロイする際に必要な設定を諸々自動でしてくれます。
(どうやら開発環境でmysqlを使用していても、このモジュールのお陰で自動的にPostgreSQLでマイグレイトされるみたいです。)

settings.pyの変更

```python:setting.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    ...
    'whitenoise.middleware.WhiteNoiseMiddleware', #追加
]

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'


import django_heroku #追加
django_heroku.settings(locals(), staticfiles=False) #追加
```

django_heroku.settings(locals())はおそらくsettings.pyの設定をいい感じでオーバライドしてHeroku用にしてくれてるので、一番下に追加します。

 **ただし、staticfiles=False**にしておかないと静的ファイルが読み込めなかったのでFalseにしています。
Herokuにstaticファイルを任せないのが一番、平和的解決だと思います。



次に以下のファイルを作成します。

```terminal
$echo "web: gunicorn <プロジェクト名>.wsgi --log-file -" > Procfile
$echo "python-3.6.10" > runtime.txt #pythonのバージョンは適宜変更してください
$pip freeze > requirements.txt

```

ここまでが完了したら次にHerokuへデプロイしていきます。

```terminal
$heroku login --interactive #Herokuへログイン 

```

--interactiveをつけるとターミナル上で認証が行えます。
僕はcloud9で開発しているのでターミナル上で認証が行えないと詰みます。。

あとはHerokuの対象のアプリケーションページに記載してある順番でコマンドを入力していけばデプロイ完了です。


デプロイする前に以下のようにHerokuにcollectstaticを任せないように設定します。
Herokuは何も設定がないとデプロイ時に自動でcollectstaticして、ほぼデプロイに失敗します。
Herokuにcollectstatic任せて本当にうまくいくのか疑わしい。。。

```terminal
$heroku config:set DISABLE_COLLECTSTATIC=1
```


デプロイ後は以下のコマンドでDjangoお馴染みの初期マイグレイトを実施すれば動くようになります。

```terminal
$heroku run python manage.py migrate 
$heroku run python manage.py createsuperuser
```

次に独自ドメインの設定とSSL化していきます！！(あとちょっと！)

##Cloudflareの設定

まずはcloudflareにユーザ登録！

ログインした際には以下の画面になると思いますので、既存のドメインを入力します。
![スクリーンショット 2020-05-25 20.38.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/25fa5020-d692-194a-9395-2aaed779be70.png)

**今回のcloudflareを利用する場合は対象ドメインのネームサーバが変わるのでDNSレコードを既存で設定している場合は全てcloudflare側に移す必要があります。**(基本的には初回に自動で取得してくれてました。)

ドメインの入力が完了すると以下のようにネームサーバを変更してね！って言われるので既存のドメインに設定されていたネームサーバを変更します。
![スクリーンショット 2020-05-25 20.42.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/3ec9a518-7ea9-ede8-d784-b8199788e48a.png)

今回、僕はお名前どっとこむを利用していたのでお名前どっとこむを例にネームサーバの変更方法を記載します。

１.まずはお名前どっとこむにログイン！
2.お前に用はない！と毎回思わされるドメイン契約更新のページからTOPページを開きます
余談ですが、お名前.comからのメールうっとしすぎませんか？？何通送るねん！っていうぐらいメールが届きます。。

3.トップページからネームサーバを設定するを選択
![スクリーンショット 2020-05-25 20.52.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/943b854e-fb79-59bc-f3c6-894797fef103.png)

4.対象のドメインを選択してネームサーバ１、２にcloudflareから指定されたネームサーバを入力します。それ以外は削除してください。
![スクリーンショット 2020-05-25 20.53.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/93e40f6c-3205-fbfe-aca1-da0aeef75a9b.png)

5.cloudflare側に以下を設定します

```terminal

●タイプ：CNAME
●名前：ドメイン名(ここはサブドメインでもOKです)
●コンテンツ(ターゲット)：<アプリ名>.herokuapp.com #HerokuのアプリケーションURLです
●TTL:自動
```

コンテンツ(ターゲット)のところは**HerokuのアプリケーションURL**になりますので注意が必要です！

プロキシステータスのところは”DNSのみ”と”プロキシ済み”が選択できますが今回はSSLもcloudflare側でやってもらうのでプロキシ済みを選択します。

DNSのみを選択した場合はDNSとしての役割だけが適用されます。

今回だと実際に僕の場合はもともとルートドメインの方には別アプリケーション先となっており、そっちは別途でSSL等の設定をしていたのでルートドメインの方には”DNSのみ”を選択しました。

あとは最後にSSL/TLSメニューのところで”フル”を選択します。

![スクリーンショット 2020-05-25 21.04.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/e3e10151-9d9e-67e5-103b-eb09aa2909b9.png)

あとは**指をくわえて待ってるだけでSSL化**されます！！

今回僕は初めてcloudflareを利用したのですが、すごくないですか！こんなに簡単に無料SSLが使えたとは知りませんでした！人生の1/100000くらいは損してたんじゃないでしょうか！？（知らんけど。）

ここまでできたら、せっかくならあれですよね。Herokuの30分リクエストないと休眠しちゃう問題もなんとかしたいですね。

本サービスを実装しているときはなんせ1週間でwebサービスを作ることが目標で、この時点で残り１日しかなかったので、とにかく簡単に、**Herokuの休眠問題を解決する方法**を探していましたが見つかりました！

残り1日あれば余裕じゃん、herokuアドオン使えばいいじゃんって思われる方もいらっしゃると思いますが、僕にはそんな気力はありませんでした。

"Heroku アドオン"で検索したらソースコードが書いてあるページがいっぱい出てきてソースコード書くんかい！と思って諦めました。


##UptimeRobotの登録

そう、これです！

このサービスは任意の間隔でサーバにリクエストを投げてくれるサービスです！
そうつまり、Herokuの30分リクエストないと休眠しちゃう問題を解消することができます！
夢の**Heroku無休稼働**実現です。

ユーザ登録含めて5分で終わります。

 Add New Monitorから下記のように好きな間隔で設定してあげればその間隔で自動でリクエストを投げてくれます。

![スクリーンショット 2020-05-25 21.23.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/52bce21e-7948-de87-8908-e3f4b6555b6b.png)


以上で完了です！！


#使用ライブラリ

どっちらいくを開発する上で使用したライブラリを紹介します。

##cloudinary


cloudinaryはクラウドのファイルストレージで、無料プランもあります。

僕がcloudinaryを使用したのはストレージのためというよりも画像加工のためです。


cloudinaryは画像加工をすごく簡単に実施できるかつ、それはURL等にパラメータを付与するだけで、一時的に加工画像を生成できます。

つまり一つの画像から動的にいくつもの画像を生成できます。

どっちらいくではOGP画像の生成等で利用しており、例えば以下の共通画像を一つ用意します。
![OGP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/b43aad8d-9861-730c-ed96-119cc3ba1fb9.png)

この画像に文字を被せていく場合は以下のようなURLでアクセスすることで
https://res.cloudinary.com/fooddev/image/upload/l_text:Verdana_30:React%20Native,x_-300,y_100/l_text:Verdana_30:Flutter,x_300,y_100/v1590643106/media/which-like/OGP/OGP_kd0eir.png

![OGP_kd0eir.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/801c655c-93ad-79c1-9e1f-99bc7f2df4cf.png)

URLのパラメータで画像を加工する方法はサンプルとともに公式ドキュメントに詳しく書いてありますので一度ご参考にしてください！

[公式ドキュメント](https://cloudinary.com/documentation/image_transformations)

###Djangoでの実装方法

Djangoからcloudinaryへファイルをアップロードしたり参照したりする方法を記載します。

```python

pip install django-cloudinary-storage #Django2まで

pip install dj3-cloudinary-storage #Django3の場合はこっち！

```

ここで注意が必要なのがDjango3.0以降のバージョンを利用してる場合はインストールするライブラリが異なります。（ここが結構ハマりました。）

現在、Djangoに関するドキュメントやライブラリはDjango2系のものがほとんどです。。
僕はDjango3.0.6 を利用しています。利用はありません。

何も考えずインストールしたらこのバージョンがインストールされました。。。
下げ方もよくわから何ので3.0で強行突破してます。

インストールが完了したら設定ファイルに以下を追記します。

```python:settings.py
INSTALLED_APPS = [
    ...　
    #順番大事！
    'django.contrib.staticfiles',
    'cloudinary_storage', # 追加
    'cloudinary', #追加
]

#追加
CLOUDINARY_STORAGE = {
    'CLOUD_NAME': '<Cloud name>',
    'API_KEY': '<API Key>',
    'API_SECRET': '<API Secret>'
}

MEDIA_URL = '/media/'
DEFAULT_FILE_STORAGE = 'cloudinary_storage.storage.MediaCloudinaryStorage'


```

基本的には上記の設定でいいはずなんですが僕の環境ではエラーになるので環境変数を追加します。

```
export CLOUDINARY_URL=cloudinary://<API Environment variable>
```

Herokuの場合

```
heroku config:set CLOUDINARY_URL=cloudinary://<API Environment variable>
```

上記設定がされていれば基本的にDjangoのモデルのImageFieldであれば自動的にcloudinaryにアップロードされます。

```python:models.py

img=models.ImageField(upload_to='<階層を指定したい場合はここに記載>', blank=True, null=True)

```

最後にtemplateからcloudinaryの画像を参照する方法を記載します。

```html:template.html

{% load cloudinary %} #追加

#オブジェクト名：test imgカラム名:img の場合
{% cloudinary  test.img.name width=600 height=450 effect="outline:10:200" color="#bbdefb" %}

#effect="outline:10:200" color="#bbdefb"はオプションです。
#使用できるオプションは公式ドキュメントに記載されてます。

#またオプションを指定しない場合は以下でも参照できます。
<img src="{{ test.img.url }}" >　#こちらを利用する場合は{% load cloudinary %}の記載は必要ありません。

```

今回はあまりオプションを使用してませんが、公式ドキュメントを見る限り、かなり色々なことができそうです。
今後も使いたい！そう思えました！

pythonだけでなく、今流行りの言語の多くをSDKでサポートしてます。



##bulma

CSSライブラリ　もちろん今回もCSSはライブラリの力をお借りします。(CSSゼロベースは無理。時間とかの問題ではい。。。。)

今回は過去に利用したことのないCSSライブラリを使用することを目的に選びました。
ただ、一般的なCSSライブラリにはちょっとした動きをつけてくれるjavascriptの実装部分も付いてくるのですがbulmaにはそれはありません。(レスポンシブはあります。)

へっぽこにとっては選択ミスでしたが、引き返すのもあれなのでbulmaで強行突破しました。

普通のCSSライブラリってメニューとかクリックしたらびょ〜んとかなる処理とかも付いているのですがbulmaにはもちろんありません。

なので、びょ〜んとかびゅ〜んは実装してません。

そして、今回は極力デフォルトの設定を変えたくなかったので(時間かかるし面倒だから、デザインスキルもないし。)デフォルトのままで行こうと思ってまして、そんな時にbulmaを拡張してあらかじめ一色の配色デザイン等を用意してくれているサイトを見つけました。

[これ](https://jenil.github.io/bulmaswatch/)

色々パターンがあって選びきれなかったので、いっそ全部取り込んでやりました！！

実際にどっちらいくのテーマカラーってところから上記サイトで提供されているデザインパターンを確認することができます。(保存とかはてしないので際ロードするまでの命です。)
![スクリーンショット 2020-05-28 22.15.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/dcb69159-3877-64ce-7ac5-aaa33c6d0034.png)

#最後に！

どっちらいくはもともとお勉強のために開発してみたサービスなので今後も運用を続けるが未定ですが、
ぜひ一度使ってみてください！

フィードバックいただけると嬉しいです！

[どっちらいく](https://dotti.buzz)


(バグはあります。)

<div class="TwitterCardsGrid TwitterCard TwitterCard--animation">

  <script type="text/twitter-cards-serialization">
  {
    "strings": { },
    "card": {
  "is_caps_enabled" : true,
  "forward" : "false",
  "publisher_id" : "1096015769811378176",
  "is_logged_in" : false,
  "is_author" : false,
  "language" : "en",
  "card_name" : "summary_large_image",
  "token" : "",
  "is_emojify_enabled" : true,
  "scribe_context" : "%7B%7D",
  "is_static_view" : false,
  "card_uri" : "https://like.youdev.tech/theme/2",
  "tweet_id" : "0",
  "card_url" : "https://like.youdev.tech/theme/2"
},
    "twitter_cldr": false,
    "scribeData": {
      "card_name": "summary_large_image",
      "card_url": "https://like.youdev.tech/theme/2"
      
      , "publisher_id": "1096015769811378176"
      
    }
  }
</script>



  <div class="TwitterCardsGrid-col--12 TwitterCardsGrid-col--spacerBottom CardContent">
    <a class="js-openLink u-block TwitterCardsGrid-col--12 TwitterCard-container TwitterCard-container--clickable SummaryCard--large" href="https://like.youdev.tech/theme/2" rel="noopener" data-card-breakpoints="w400 w350 w300 w250 w200 w150 w100 w50 ">
  <div class="SummaryCard-image TwitterCardsGrid-col--12">
    <div class="tcu-imageContainer tcu-imageAspect--2to1">
  <div class="tcu-imageWrapper" style="opacity: 1; background-image: url(&quot;https://pbs.twimg.com/card_img/1266586278985789441/WxNjPRFU?format=png&amp;name=600x314&quot;); background-size: cover;" data-style="background-image: url(https://pbs.twimg.com/card_img/1266586278985789441/WxNjPRFU?format=png&amp;name=600x314); background-size: cover;">
    <img class="u-block" data-src="https://pbs.twimg.com/card_img/1266586278985789441/WxNjPRFU?format=png&amp;name=600x314" alt="" src="https://pbs.twimg.com/card_img/1266586278985789441/WxNjPRFU?format=png&amp;name=600x314">
  </div>
</div>

  </div>
  <div class="SummaryCard-contentContainer TwitterCardsGrid-col--12">
    <div class="SummaryCard-content TwitterCardsGrid-ltr">
  
</div>

  </div>
</a>

  </div>


</div>