firebaseのいろはのい Webアプリ開発から公開まで

#はじめに
この間paizaの記事（下記）を見て、Firebaseと言うものに興味が湧いたので、Webアプリ（的なもの）を作ってみました。
[Web開発が捗るFirebase入門！JavaScriptで「Webユーザー認証」機能を](http://paiza.hatenablog.com/entry/2016/10/03/Web%E9%96%8B%E7%99%BA%E3%81%8C%E6%8D%97%E3%82%8BFirebase%E5%85%A5%E9%96%80%EF%BC%81JavaScript%E3%81%A7%E3%80%8CWeb%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E8%AA%8D%E8%A8%BC%E3%80%8D%E6%A9%9F%E8%83%BD%E3%82%92)
この記事では主にDatabase、Authentication、Hostingについて記述します。

# Firebaseとは
2014年にGoogleが買収し、同社が運営するBaaS,mBaaS
簡単に言うと、複雑なバックエンド処理（ユーザー認証、データベース、プッシュ通知、ファイル管理等々）が全部APIで出来るよっていうモノ
データベースはFirebaseの中にAuthenticationやDatabaseといった機能があって、好きなものを使えます。

## mBaaS,BaaSとは
(mobile) Backend as a Service のことで、ユーザーから見えない処理（バックエンド）をクラウド上で提供してくれるサービスです。

## 種類
- ログインしたり現在のユーザー情報を取得したりできるAuthentication
    - ログインに使えるのはメール・Googleアカウント・Twitter・Facebook・GitHub・匿名アカウント
- json形式の"リアルタイム"データベースを扱えるRealtime Database
- ユーザーの画像などのファイルを保存したり取得したり出来るStorage
- 静的な（HTMLファイル、CSS、Javascript等）ウェブコンテンツを配信できるHosting
- 通知を送るNotifications（iOS、アンドロイド）
など、他にも機能が盛り沢山です。
特にAuthentication、Realtime Database、StorageなどはiOS・Android・Web(javascript)で使用できます。便利。

# 導入
##firebaseページでの設定
https://firebase.google.com から始められます。
[公式ドキュメント](https://firebase.google.com/docs/)に先に目を通しておくのも良いかもしれません。
![SS_NoName_20161026044939_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/e54117ff-d990-8d75-cbcf-c9f32669a0a7.jpeg)
無料（！）で開始ボタンからGoogleアカウントでログインできます。
![SS_NoName_20161026044952_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/566e1b53-4bca-9a6a-f695-208bc6fb04fa.jpeg)
新規プロジェクトを作成ボタンからプロジェクトを作成しましょう。（プロジェクト名は適当）
![SS_NoName_20161026045023_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/3cf983bb-8474-df35-77cf-c2a49915f226.jpeg)
![SS_NoName_20161026045049_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/05d2e5f1-8ce9-7490-d275-e48fd97a7ac4.jpeg)
これでコンソール（管理画面）にアクセスできるようになりましたね。

## ローカルのコンソールでの設定
ホスティングは基本的にコンソール（筆者環境はWindowsなのでPowerShell）でfirebaseコマンドを叩いてデプロイします。
![SS_NoName_20161026045619_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/343914a7-6198-5789-82ad-f63bf21b185b.jpeg)
左のメニューからホスティングを選ぶとスタートガイドに案内されます。
![SS_NoName_20161026045642_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/fb4cb9da-914f-55fd-bf3a-78e6a77738eb.jpeg)
![SS_NoName_20161026045652_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/c2ddcc11-e36a-01de-5d9b-47ce127d1f73.jpeg)
とてもわかりやすい……

```firebase login```を実行すると別ウィンドウでGoogleアカウントでログインする画面が開きます。いちいち便利ですね。
```firebase init```でかっこいいAAとともにHostingかDatabaseか聞かれるのでホスティングを選びましょう
プロジェクトを新規作成するか聞かれるので、作成済みのものを選択しましょう。
（十字キーで選択　わからない質問はそのままエンターでデフォルト設定で問題ないでしょう）

## ホスティング
すると、publicフォルダと、その中にindex.htmlが生成されるはずです。これらを変更して、WEB上にデプロイします。
```firebase serve```でローカルでホスティングします。(http://localhost:5000)
```firebase deploy```で本番のWEB上にデプロイします。

これだけで静的なWEBサイトを公開できます。

#作ったもの
Hosting,Database,Authenticationを利用して、アンケート作成サイトを作ってみました。
https://doomou-df383.firebaseapp.com/
Hostingでは独自ドメインの設定も出来るようなので、ドメインもつけてみたらおもしろそうですね。

javascriptで記述しています。

#DB設計
データベースはSQLではなくjson形式（連想配列の形）です。javascriptと相性良さそうですね。
## Realtime Databaseとは
普通のデータベースでは、一度読み込んで表示、という流れになると思いますが、
Realtime Databaseでは、変更があったときにイベントが呼ばれ、リアルタイムに変更を反映することが出来ます。
今回作成したアプリにはあまり関係がありませんが、チャットアプリなどではその威力を存分に発揮するでしょう。（その分値段は高めらしい）

## 構造化
なるべく浅く広くを心がけたほうが良いようです。（あまりネストしない）
それは何故かといいますと、

```json
{
  "enquetes":{
    "enquete_id":{
      "message":"バナナはお好きですか？",
      "answers":["はい","はい","いいえ","まぁまぁ"]
    },
    "enquete_id2":{
      ...
    }
  }
}
```
このようにアンケートの一覧の中に回答が含まれていた時、アンケート一覧を取得したい場合は```enquete/```にアクセスするわけですが、この時回答もすべて読み込まれてしまいます。しかもどこかに変更があるたびに毎回ロードされるので、どこかのアンケートで誰かが答える度に更新されてしまいます。Databaseのアクセスもそれなりに値段がかかるようなので、大規模なアプリの場合は問題になるでしょう。また、アクセス権も複雑になりがちです。
この場合は、アンケートはアンケート、回答は回答とわけたほうが良さそうですね。

ちなみにRealtime Databaseは最大で32階層までネストできます。念のため。

```json
{
  "enquetes":{
    "enq_id":{
      "description":"バナナはお好きですか？"
    }
  },
  "answers":{
     "enq_id":["はい","いいえ","はい","まぁまぁ"],
  }
}  
```
アンケートの一覧を取得するときのことも考えて、こんな感じにしてみました。（本当はもう少し色々なデータを含みます）
SQLの正規化とはまた違うので上手くいっているかわかりませんが、少なくともアンケート一覧を取得する際に膨大なデータを読み込む必要はなくなりました。

## 初期化
ここからは実際にコード上でDBを操作します。
その前に、先に以下のコードを書いてfirebaseを初期化しておいてください。
https://firebase.google.com/docs/web/setup

```html
  // TODO: Replace with your project's customized code snippet
  <script src="https://www.gstatic.com/firebasejs/3.0.2/firebase.js"></script>
  <script>
    // Initialize Firebase
    var config = {
      apiKey: '<your-api-key>',
      authDomain: '<your-auth-domain>',
      databaseURL: '<your-database-url>',
      storageBucket: '<your-storage-bucket>'
    };
    firebase.initializeApp(config);
  </script>
```
このコードは、管理画面の左側のバーの上部にある、アプリ名の右の歯車アイコンをクリックし、ウェブアプリにfirebaseを追加ボタンを押すと生成できます。
![SS_NoName_20161026053659_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/0be21afe-752e-46f8-4fad-dc2134485c27.jpeg)

## ルール
データベースにどのユーザーがアクセスできるかを、ルールとして割りと細かく設定できるようです。
ルールタブからルールを変更します。
![SS_NoName_20161026061619_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/d1466ec0-36ea-69aa-8473-dfc7e172cb55.jpeg)
上のルールだと、誰でも読み込めて、ログインした人だけ書き込めるというルールになっています。Authenticationを使う前のテストでは両方共trueにしておきましょうか。
この辺はまたいずれ書こうかと思います。

## 書き込み

書き込みは、```firebase.databse().ref(path).set(data)```で行えます。
例えば、

```js
var data = {"id1":{"message":"バナナはお好きですか"}};
firebase.database().ref("enquetes").set(data);
```
とすると、DBでは

```json
{
"enquetes":{
    "id1":{
      "message":"バナナはお好きですか"
    }
  }
}
```
のようになるわけですね。

###注意
ここで、もう一つアンケートを追加しようとして、

```js
var data = {"id2":{"message":"あなたの血液型は"}};
firebase.database().ref("enquetes").set(data);
```
としても

```json
{
"enquetes":{
    "id1":{
      "message":"バナナはお好きですか"
    },
    "id2":{
      "message":"あなたの血液型は"
    }
  }
}
```
とはなりません。

```json
{
"enquetes":{
    "id2":{
      "message":"あなたの血液型は"
    }
  }
}
```
こうなります。
指定したパス以下をsetした値に置き換えるといった感じでしょうか。
もしアンケートを追加したいなら、

```js
var data = {"message":"あなたの血液型は"};
firebase.database().ref("enquetes/id2").set(data);
```
としましょう。

### push
pushメソッドでリストに一意なIDでデータを追加できます。

```json
{
"data":{
  "id1":"hoge",
  "id2":"fuga"
}
}
```
のようなデータのとき

```js
var key =  firebase.database().ref("data").push("piyo").key;
```
とすると

```json
{
  "data":{
    "id1":"hoge",
    "id2":"fuga",
    "newId":"piyo"
  }
}
```
となり、```var key```にはこの場合newIdが入ります。
この時newId、つまりpushで自動生成されるIDはランダムですが、タイムスタンプに基づいているらしいので、そのままソートすると時系列順になるそうです。

## 読み込み
変更に対してイベントを設定する方法と、一度だけ実行するイベントを設定する方法があります。

### イベントリスナーのアタッチ

```js
firebase.databse().ref("path").on("value",function(snapshot){
  if(snapshot.exists()){
    var data = snapshot.val();
  }
});
```
onメソッドの引数に、イベントの種類と処理を与えます。
イベントの種類は以下のとおりです。

イベント|概要
-------|----
value|最初の1回と、以降のパスの要素と子要素が変更されるたび呼ばれる
child_added|取得＋リスト内のアイテムの追加があった時
child_changed|リスト内のアイテムに変更があった時
child_removed|リスト内のアイテムが削除される時
child_moved|アイテムが並べ替えられた時

リスナーをでタッチするときは、```off()```メソッドを使います。
引数無しで呼び出したときは、その場所のすべてのリスナーが削除されます。

####1回だけ読み取る場合
once()メソッドを使います。

```js
firebase.database().ref("path/path").once("value").then(function(snapshot){
  if(snapshot.exists()){
    var name = snapshot.val().name;
    ...
  }
});
```

###snapshot
そのイベント発生時点でのデータベースの情報です。```snapshot.val()```で値を取得できます。
指定したパスにデータがなければnullが帰ってきます。```exists()```でもパスの存在を確認できます。
```key```でキーを取得できます。配列に使える```forEach()```と合わせるとインデックスを取得したり出来ます。

``` js
snapshot.forEach(function(childSnapshot){
  var index = childSnapshot.key;
});
```
みたいな。

注意したいのは、onceも非同期なイベントのため、そのまま取得した気で下に処理を書いてくと、まだ取得されていないうちに次の処理が来てしまったりします。


#ログイン
![SS_NoName_20161026062307_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/87fe17e8-8ea6-28d7-2d01-c9a0eb5f2b7a.jpeg)
Authenticationのログイン方法タブから、有効にしたいサービスを選択します。
例えばTwitter
![SS_NoName_20161026062546_No00.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/bcaadd98-9283-2590-c80a-0b97e6309d95.jpeg)
APIキー・シークレットはTwitterの[アプリ管理画面](https://apps.twitter.com/)の方から取得します。
また、ツイッター側にもコールバックURLを設定しましょう。そのへんはツイッターの方で検索してください。

参照：https://firebase.google.com/docs/auth/web/twitter-login

- ポップアップでログイン

```js
var provider = new firebase.auth.TwitterAuthProvider();

firebase.auth().signInWithPopup(provider).then(function(result) {
  // This gives you a the Twitter OAuth 1.0 Access Token and Secret.
  // You can use these server side with your app's credentials to access the Twitter API.
  var token = result.credential.accessToken;
  var secret = result.credential.secret;
  // The signed-in user info.
  var user = result.user;
  // ...
}).catch(function(error) {
  // Handle Errors here.
  var errorCode = error.code;
  var errorMessage = error.message;
  // The email of the user's account used.
  var email = error.email;
  // The firebase.auth.AuthCredential type that was used.
  var credential = error.credential;
  // ...
});
```
## ログイン時の処理

```js
firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    // ログイン中
  } else {
    // ログインしてない
  }
});
```
注意としては、ログインしたまま放っておくと、一時的に切断・再接続などでログイン状況が変わり、もう一度このイベントが呼ばれることがあります。一度しか実行されないつもりで書いているとハマります。ハマりました。

userオブジェクトにはユーザーの様々な情報が格納されています。user.uidでfirebaseが作成した一意なIDを取得できます。
これは各サービスでのIDとは違いますので注意。

## ログアウト

```js
firebase.auth().signOut();
```



#料金
https://firebase.google.com/pricing/


#まとめ
firebaseはBaaSであり、ログイン処理やデータベースなどの処理をやってくれる。

間違っているところも多々あると思いますので、皆様是非ご教授ください。