はじめに
こにゃにゃちは!ganariyaです!
本記事では、「Firebaseを初めて触った人間が、初めて触るときに躓きそうな点について」まとめようと思います。
そのため、Firebase全体に関する説明ではないのでご容赦ください><
Q&A形式でまとめていきます!
Firebaseってまず何よ?
Firebaseは、Googleが作っている「Webアプリやスマホアプリを作るときに便利であるようなツールを全部まとめたもの」です。
Firebaseの初期画面は
のようになっており、プロジェクトごとの画面は
になっています。
Firebaseでは、プロジェクトごとに作成していきます。
各プロジェクトには
- Authentication(認証)
- データベース(データを格納)
- Storage(使ってないけど多分静的コンテンツを入れれる)
- Hosting(作ったサイトをデプロイできる)
- Functions(セキュリティ周りなど、フロントから直接触らせるのいやなときにかますための関数)
- Machine Learning(たぶん機械学習、それはそう)
の機能があります。
また、各プロジェクトにはさらに「アプリ」の概念があります。
例えば、仮にQiitaだとすると「Qiita」「Qiita Jobs」「Qiita Teams」という感じでしょうか・・・。アプリがあるメリットとして、データベースや認証を共通したいけれど、アプリとしては分けたい・・・ という意味で存在しているのだと思います。
とりあえず、Firebaseは認証からデータベース・ホスティングまでサーバーサイドをすべて肩代わりしてくれるサービスと思うと良さそうです。
データベースって?
まずは、データベースから見ていきます。
データベースは現在「Cloud Firestore」が標準のようです。昔はRealtime Databaseでしたが、こちらのCloud Firestoreがより高機能で最新っぽいです(機能が違うので、Realtime Databaseのほうが良いこともあります)
Firestoreは、NoSQL型のデータベースです。
上記のように、コレクションの中にドキュメントを入れる形になります。
コレクションがSQLデータベースのテーブル名、ドキュメントが各行、ドキュメントの属性が各列に値します。
上記だとguidesというコレクション(フォルダ)があり、ドキュメントが4つ存在します。
そのうち、1つ目のドキュメントにはcontent, titleが格納されています。
ドキュメント自体にも名前があることに注意してください。
イメージ的にはdocument.json
というdocument名がついており、それをエディタで開くとcontent
, title
のような実際の中身が入っているイメージです。
そもそも、なぜ「データベース」をGoogleのサーバーでやるのでしょうか?
これは、サーバー側のデータベース作成をしなくてよいためです。これまでは、VPSサーバなどを借りてApacheやNginxを入れて、さらにMySQLのソフトウェアをインストール、設定などをいちいち行う必要がありました。また、クエリが多く飛んで落ちる可能性もあります。
そこで、Googleが代わりにデータベースを作ってくれれば、僕たちはそれに「アクセス」するだけで良くなります。
ここで、「アクセス」は「クライアントサイド」からされることに注意が必要です。
これまでのデータベースでは、サーバー側のPHPプログラムといった、サーバー側のプログラムがデータベースサーバにアクセスするという形をとっていました。クライアント側のJavaScriptやHTMLのPOST通信が、サーバーにアクセスして、サーバーがプログラムを実行し、データベースまで取りに行っていたのです。
しかし、これではサーバー側のPHPプログラムなどの実行がボトルネックになってしまいます。
一方、CloudFireStoreは、「クライアントサイド」から直接データベースにアクセスします。そのため、サーバーサイド側のプログラムが必要なくなり、フロントエンド側でJavaScriptやTypeScriptで完結することになります。手間が省けますね><
ただし、その分セキュリティには注意する必要があります。
Webアプリでの「初期コード」ってどこにあるん?
QiitaなどでFirebaseチュートリアルなどをやると、
var firebaseConfig = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
};
firebase.initializeApp(firebaseConfig);
のfirebaseを使用するために必要な初期コードが求められます。
これ、どこにあるか探すのに時間がかかりました。
初期画面のこのコードのところにあります。
ここで、Webアプリを作りますか?というのが出たら作る!を選んであげてください。
認証ってどうやるの?
ユーザ認証をするときはどうすればいいのでしょうか?
Webアプリなので、多くの人間が使うことを想定すると、「ある人間のデータは他の人間に見れないようにログイン機能を作る」必要があります。
AuthenticationのSign−in methodからログインで使いたいものを選択すればいいです。
僕はとりあえずメール・パスワードを選択しました。
ログインをユーザがすると
Usersに認証データが追加されます。
ここで、Authenticationの下のDatabase(Firestore)には、ユーザの認証データは入っていません。
Authenticationで一括管理されているようです。
Googleが全部認証をやってくれるので、パスワードの暗号化し忘れみたいなやばいことを避けられますね><
実際にどう登録・ログイン・ログアウトするねや?
実際に登録やログアウトを実装するときは、Firebaseが提供しているライブラリを用います。
今回は、javascriptでやりますが、PythonやPHPなど他のライブラリでも基本は同じだと思います。
今回はnpm, yarnなどは使わずhtmlで直接cdnにアクセスする形にします。
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-functions.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-analytics.js"></script>
ここらへんをとりあえずインポートしてあげます。
その後、先程の初期化をします。
var firebaseConfig = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
};
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const db = firebase.firestore();
authは、認証のライブラリ、firestoreはfirestoreのライブラリです。
登録をするときは
signupForm.addEventListener('submit', (e) => {
e.preventDefault();
const email = HTMLから取り出す
const password = HTMLから取り出す
// Userを作成する
auth.createUserWithEmailAndPassword(email, password)
.then(cred => {
// 登録成功
console.log(cred);
})
})
などとすればいいです。今回はEmailAndPasswordでメールとパスワードを使用していますが、Googleアカウントなどの他の登録メソッドもあります。
認証が成功すると、次からは自動でログイン状態になっています。
一回登録・ログインすれば、特にクライアント側でコードを書かなくてもライブラリが自動でログイン状態をcookieなどで安全に保ってくれるのでうれしいですね><
ログインをするときは
loginForm.addEventListener('submit', (e) => {
e.preventDefault();
const email = HTMLから取り出す
const password = HTMLから取り出す
auth.signInWithEmailAndPassword(email, password)
.then(() => {
console.log('success')
})
}
のようにすればいいです。ログインができ、ページ遷移することなく自動でログイン状態になります。
ログアウトでは、auth.signOut();
をすればいいです。
このとき、ログイン状態が変わったらコードを実行したいんだよね(UIを変えたり)などがしたいと思います。
このようなときは
auth.onAuthStateChanged(user => {
if (user) {
// login state
} else {
// ログインしてない
}
})
のように、ログイン状態が変わったら自動発火するイベントが用意されているため、コールバックを登録してあげればいいです。
ログインしているユーザのID知りたいんだけど
ログインしているとき、ユーザ認証では各ユーザにIDが割り振られています。
ユーザIDは
の最も右欄で確認できます。
ユーザIDは
let uid = auth.currentUser.uid;
で取得できるため、ユーザのログイン状態は間接的にこれがnullかどうかでもチェックできそうですね><
このuidを使えば、データベースもうまく設計できそうです。
ユーザごとにデータベース作りたいんだけど
この設計が正しいかわからないため、ご意見をいただければと思います。
通常のMySQLなどのリレーショナルデータベースでは、
- Usersテーブル
- ユーザーID
- パスワード
- Todosテーブル
- todo ID
- name
- ユーザーID
みたいになっています。
こうすることで、今ログインしているユーザのIDを用いて、Todosテーブルと関連付けてユーザーIDが一致している行だけを引っ張ってきます。
ただ、これはNoSQL(Firestore)だと適切か分かってないです(アドバイスいただければと思います)。
SQLと異なり、Firestoreはjsonに近い設計であるため
- ここにこのデータしか入れれない!
- テーブルごとの設計にしろ!
というのがありません。
これらはクライアントサイドで注意して行う必要があります。
良く言えばリレーショナルデータベースよりも柔軟で変化させやすいというのがありますが、ミスすると変な設計になる という裏返しみたいな状況になります。
リレーショナルデータベースは、これを設計手法でガチガチに固めています。
そのため、今回は以下のようにTodosを設計しました。
Todosには現在2個のドキュメントが入っています。
この2個のドキュメントの名前はユーザIDになっています。auth.currentUser.uid
で取り出せるものですね。
この名前でドキュメント名をつけることで
db.collection('users').doc(auth.currentUser.uid)
で
ユーザIDに関するTodosを取れるようにしています。
つまり、今回はユーザごとにTodoのデータベースを作っているイメージです(これが正しい設計なのかは分かりません・・・)
このuid.document
にはuser_todos
というコレクションを入れています。
これはサブコレクションであり、コレクションのドキュメントにコレクション
を入れています。
上記のように、
todosコレクション > ユーザIDドキュメント > user_todosドキュメント > ランダム生成ドキュメントID > {content, title...}
のようになっています。
こうすることで、ユーザIDに関係するデータのみ持ってこれるようにはなります。
データベースが変化したときにメソッド実行したいんだけど
データベースにデータが登録・削除など、データベースに変更があったときに、なにかUIを変化させたい・表示内容などを変えたいなどがあります。
これも認証と同じように、イベントが設定できます。
db.collection('todos').doc(user.uid).collection('user_todos').onSnapshot(snapshot => {
let docs = snapshot.docs;
})
上記は、todosコレクションのuserIDのドキュメントのuser_todosコレクションに変更が会ったときに実行されるイベントです。
snapshotには、変更されたときのuser_todosコレクション全体のデータが入っています。
Firestoreには、「reference」と「実体」があるようです。
感覚が全然つかめていないのですが、let todosRef = db.collection('todos')
などは、todosコレクションへの参照状態を変数としており、まだデータを取ってきていない?ようです。
実際に撮ってくるときは.get()などをつけるようです。
もっと勉強しようと思います(アドバイスいただければと幸いです)
セキュリティってどう担保するの?
データベースには、フロントエンド(ユーザ)から直接アクセスされます。
そのため、かなり念入りに「他のユーザからデータを好き勝手触られないように設定する」必要があります。
これはデータベースのルールから設定できます。
上記のように、アクセスするドキュメントごとに誰がアクセスしていいのか?などの設定をします。(コレクションではないです)
/users/{userId}
ですが、{userId}
はドキュメント名{userId}
を変数として持っています。
そのため、request.auth.uid != null
などをチェックすることで、ドキュメントへのアクセスを制限できます。
理解が非常にあやしいのでルールを参考にしてみてください。
ホスティングってどうやるの?
ホスティングを参考にしました。
Firebase CLIを入れた後、ステップにしたがってdeployしたら、簡単にpublicフォルダ以下をデプロイできました。すごいですね(すごい)
今回、public以下で作業をしていなかったので、カレントディレクトリから小ディレクトリのpublicフォルダにhtmlやjsを移しました。
Nuxtなどで作業すれば、ビルドしたものをpublicとしてフォルダに出力すれば簡単にホストまで行けそうです!
良いチュートリアルない?
- https://www.youtube.com/playlist?list=PL4cUxeGkcC9itfjle0ji1xOZ2cjRGY_WB
- https://www.youtube.com/watch?v=-OKrloDzGpU
- https://www.youtube.com/playlist?list=PL4cUxeGkcC9jUPIes_B8vRjn1_GaplOPQ
- https://www.youtube.com/playlist?list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ (今度見る)
最後に
だいぶ簡潔にまとめました。
分かりづらい・間違っている・伝わりづらい点がありましたら、ご指摘いただければ嬉しいです!
ありがとうございました!!