プロローグ 「始まりの文章」
ーーこれは本気でヤバい。
何も書かれていないQiitaの下書き文章を見て、
彼は自分がアドカレ当日に1から文章を書くのだと気づいた。
頭からは全く書く内容が浮かんでこず、手先の感覚はすでにない。(手が止まった)
ただ、自分を責める嫌悪感のみが体を支配している。
ーーヤバい、ヤバい、ヤバい、ヤバい、ヤバい、ヤバい。
叫び声を上げようと口を開いた瞬間、こぼれ出たのは絶叫ではなく、
このコロナ情勢でただ虚しく一人で部屋にいるという、絶望感だ。
多くの自分のタスクと、オンラインにより増えた学校の課題に追われ、
そのうえさらに1つ記事を書くという首の皮一枚で繋がっている状態だ。
つまるところ、どうやら人生の「詰み」というやつに直面したみたいだ。
ただ願ったのは ーー 世の中のエンジニア界隈の人が一人でも救われるようなものが書けますように、
ということだけだった。
かすかに動いた指先が、自分の気持ちを手助けしてくれたような気がした。
「...待っていろ」
遠ざかる意識をモンスター(エナジー)の勢いで引っ掴み、無理やりに振り向かせて時間を稼ぐ。
「ヤバい」も「嫌悪感」も全ては遠く、無駄な足掻きの負け犬の遠吠えだ。
だが、それでもーー、
「俺が、必ずーー」
ーーお前を救ってみせる。
次の瞬間に彼ーー筆者はまぶたを閉じた。
第1話 「何を書くのか」
ーーこれは本気でヤバいことになった。
一文無しで途方に暮れながら、彼の心中はそんな一言で埋め付くされていた。
一文無しというのは正確ではない。普段の知見はNotionやGitHubのIssueにまとめているし、やや自らの発見が少ないという点を除けば全知識は確かにストックされているに違いない。
にも関わらず、一文無しと表現するしかない。
なにせ、
「やっぱ、既にある文章が多いんだよな...」
あらゆる自分のリポジトリ ーー 希少な努力の結晶を眺めて、少年は長いため息をこぼした。
これといった特徴のない少年だ。フロントエンドでは少しばかりの業務をしているだけで、
バックエンドを自ずと実装できるわけでもない、高くも低くもない平均的なレベル。
体格は運動しているのかやや細身で、スポーツマン風ではある。
「つまり、これはあれだな」
指を鳴らし、少しハッカーっぽい様子でパソコンに鳴らした指を向けながら、
「ーーほぼサーバーレスで5時間で作ったサイト、スピード実装の紹介をするしかない、ということらしい」
目の前を、暖かい暖房の風が横切っていった。
第2話 「どういう設計だったのか」
時が止まる、というのはこういうことだろうか。
動き出した指先と集中力は周りの時の流れを無視し、もはや食い止めるものなどいない。
まずはバックエンドを書かずに実装した理由を、次の画像で説明したい。
設計の段階で、総身を震えるような感動が走った。
なるほど、これならバックエンドを書かずしてもデータを拾うことができ、
デプロイまで済ませることができる。
ーーなぜこのような設計にしたのだろうか。
「〇〇君(筆者)、あるホームページを作って欲しいんだけど!」
それは同じ大学の体育会の部活に所属するマネージャーからの連絡だった。
「ホームページって...どんな?」
「部内関係者だけが見れる、卒業生が書く文章を閲覧できるようなページなんだけど。」
なるほど、それなら簡単なログイン認証つけて、文章もらったら、こっちからバックエンド通してCRUDついた機能のある、簡易的なブログみたいにすればいいか、なんだRailsチュー(ry
まあゆっくりコード書いても2,3日あれば...
待てよ。
以前にもこのような「お願い」は納期が...
「...ちなみにいつまでに欲しいん?」
「んーそうだね、明日の昼には公開しなきゃだから今日の夜には欲しいかも!(16:43とLINEに残っている)」
やはりだ。
学生のしがないエンジニアとも呼ぶべきかわからない自分を何者だと理解しているのだろうか。
夜と言われても日付が変わるまで、見積もってもタイムリミットは8時間を残していない。
「何か作ったら(お金とか...)くれたりするかい?」
「んー、そうだね、、、ジュース今度奢ってあげる!」
...可愛い。少しでもお金に目が眩みそうになった自分を蔑んだ。
できないかもしれない、とも思った。課題の締め切りもある。
...
「――あとのことは全部、俺に任せておけ!」
ーーただ、何もない空っぽのままで終わってしまうのが耐えられなかった。
※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※
そういって彼は電話を切った。
どうするばかりか、
丁寧にバックエンド側のコードを書く時間はない、
知り合いは閲覧できる状態、ローカルではいけない、デプロイまで済ませる必要がある。
そんな極地でふと舞い降りた設計がこれだった。それだけだ。
時間がない、追い詰められた状況で、鬼がかったやり方を思いついたものだ。
Firebase Authenticationでログイン認証、Hostingを使ってデプロイ、
Nuxt + Vueで簡単にニッチな**SPA(Single Page Application)**にできる。
効率重視。バックエンドなしではデメリットも大きい。テストも回しづらい。
だが、彼はそれらのデメリットを置き去りにして既にコードを書き始めていたのであった。
第3話 「ゼロから - 環境構築とベースアプリ」
それからの彼の作業は順調に滞っていた。
「よし、必要なものだけとりあえず入れたいからnpxでnuxt入れて環境構築済ませてしまおう」
npxを使えば、node_moduleに必要な物が揃っていなくても、
必要な時に一時的にインストールしてダウンロードを進めてくれる。
$ npx create-nuxt-app {appの名前}
create-nuxt-app v3.4.0
✨ Generating Nuxt.js project in {appの名前}
? Project name: {appの名前}
? Programming language: JavaScript
? Package manager: Yarn
? UI framework: Vuetify.js
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Axios
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Prettier
? Testing framework: None
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ jsconfig.json (Recommended for VS Code if you're not using typescript)
? What is your GitHub username? Shunya078
? Version control system: Git
...
🎉 Successfully created project testapp
To get started:
cd testapp
yarn dev
To build & start for production:
cd testapp
yarn build
yarn start
「よし、これで起動できるか確認しよう」
$ cd {appの名前}
$ yarn dev
---> 以下自動で走ります
yarn run v1.21.1
$ nuxt
╭───────────────────────────────────────╮
│ │
│ Nuxt @ v2.14.12 │
│ │
│ ▸ Environment: development │
│ ▸ Rendering: client-side │
│ ▸ Target: server │
│ │
│ Listening: http://localhost:3000/ │
│ │
╰───────────────────────────────────────╯
ℹ Preparing project for development
ℹ Initial build may take a while
✔ Builder initialized
✔ Nuxt files generated
✔ Client
Compiled successfully in 5.99s
ℹ Waiting for file changes
ℹ Memory usage: 332 MB (RSS: 497 MB)
ℹ Listening on: http://localhost:3000/
「これで http://localhost:3000/ にアクセスして、と。」
Vuetifyのテンプレート画面が出て、彼は思わず笑みを浮かべた。
「笑えマネージャー!今日の俺は、鬼より鬼がかってるぜぇ!!」
そして一度、Ctrl + Cボタンをターミナルで押し、ローカルを閉じた。
ここから、ゼロから始めよう。
筆者の物語を。
――ゼロから始める、NoSQL生活を。
第4話 「フロントページ攻略戦」
定刻通り(18:00)に携帯電話のアラームが鳴り響き、
宵闇の戦闘が開始する報を告げた。
(集中する時、作業時間に区切りをつけるため筆者は1時間ごとにアラームをつけたりします)
「――ぶちかませぇッ!!」
――先に明言しておくが、本記事では大まかな手順を辿るので、細かいコードを参照したい場合は実際に製作した努力の結晶(リポジトリ)を最後に載せておくのでそちらを見て欲しい。
そこからの作業は早かった。
Nuxtフレームワークによる自動ルーティング。
ルート遷移は全て .nuxt > router.js
にて$ yarn dev
するたびに自動で更新してくれる。
フロントに書きたいページはどこに書くかというとpages
ディレクトリにて
└── pages
├── index.vue
├── signin.vue
├── signup.vue
└── _id.vue
のように書けば、.nuxt > router.js
にて以下のようにルートを設定してくれるのだ。
...
routes: [{
path: "/signin",
component: _77dc0ee9,
name: "signin"
}, {
path: "/signup",
component: _e71b3542,
name: "signup"
}, {
path: "/",
component: _976eba4a,
name: "index"
}, {
path: "/:id",
component: _57c7a003,
name: "id"
}],
...
_id
はなんだ?と最初は見慣れないのだが、これはparamsを受け取ってくれる代物だ。
投稿した内容のidをユニークキーで制約すれば、そのidを取ることで、データを取得できる。
したがってVueファイル内に以下のように書くことで、
_id
の部分にparams:
で指定した値が代入される。
<router-link
class="button is-primary"
v-bind:to="{ name: 'id', params: { id: item.slug } }"
>{{ item.title }}</router-link
>
あとは今回デザインテンプレートとしてVuetifyを採用したので、特にCSSを書く必要などもなく
レスポンシブ化されたサイトとなり、いい感じにドキュメントを読み、デザインを仕上げた。
なお、Vuetifyの使い方は上の検索バーで検索するか、
UIコンポーネントを直感的に選ぶ。
本来はモックアップ等を作ってからページは作るべきだと、書いている今も筆者は思っている。
――ただ、フロントのページ作成を終えたとて、
いまだFirebaseを微塵も使っておらず、ここから難易度が跳ね上がる。
NoSQL戦、その終端はいまだ、見えない――。
第5話 「悪辣なるFirebase」
――悪夢と見紛う程の量のドキュメントと初期導入の壁で、頭を覆い尽くすFirebase。
歪なほどにどこから読めばいいかわからず、膨れていく「ドキュメント」の量には、限界が見えない。
Web以外にも多様な汎用性を見せるその数 ーー 数えるまでもなく百に迫る。
「ーーファイア・ベース!!」
「おやぁ? 使う前に呼ばれるとはこれもまた不思議なことデス!」
フロントのページをひと通り書き終え、ゴールが見えたかのように思えた筆者の思いとは裏腹に、
「さあ、コンソールから入り、プロジェクトを新規作成するのデス! そしてそしてそしてそしてそしてぇぇぇ! 我が愛の前に、勤勉なるワタシ(Firebase)の奉仕の前に! そのコードを、命を、魂を、捧げるの――デス!!」
「ぼけっとしとる暇なんぞあらへんぞ! 殺らな、殺られる――そうやろが!」
心の中で小さな自分がそう叫んだ気がした。
やらねばならない。
Firebaseコンソールを開き、以下の手順で進めた。
さらに、Firebase Authenticationを使用するために、メール/パスワードを認可。
「――――ッ!?」
そのまま必要なプラグインをyarn
を使用し、インストールする。
$ yarn add firebase
$ yarn add firebase-tools
package.json
にて、ライブラリが追加されたのを確認。
...
"dependencies": {
"core-js": "^3.6.5",
"firebase": "^8.2.1",
"firebase-tools": "^9.0.1",
"nuxt": "^2.14.6"
},
...
続けて、prugins > firebase.js
を作成し、以下のようにSDKを当てはめていく。
import firebase from "firebase";
import "firebase/auth";
import "firebase/firestore";
const firebaseConfig = {
apiKey: "{自分のキー}",
authDomain: "{自分のキー}",
databaseURL: "{自分のキー}",
projectId: "{自分のキー}",
storageBucket: "{自分のキー}",
messagingSenderId: "{自分のキー}",
appId: "{自分のキー}",
measurementId: "{自分のキー}",
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
const settings = { timestampsInSnapshots: true };
firebase.firestore().settings(settings);
}
export default firebase;
export const auth = firebase.auth();
export const firestore = firebase.firestore();
「笑えないのデス、冗談ではないのデス、あってはならないことなのデス! そのような簡単に見える手順で、ワタシ(Firebase)の愛(丁寧なリファレンス)を…、蔑にしようなどとぉ……!」
(絶対にリファレンスは大事です、蔑ろにしているわけではないです。(by 筆者後書き))
「『地獄』を知ってるのは俺だけでいい。そのために、俺がいるんだ」
これで彼のFirebaseに対する前準備は全て終了した。
あとは具体的にコードに落としていくのみである。
<template>
<div class="signup">
<h2>Sign up</h2>
<input type="text" placeholder="Email" v-model="email" />
<input type="password" placeholder="Password" v-model="password" />
<button @click="signUp">Register</button>
<p>
Do you have an account?
<router-link to="/signin">sign in now!!</router-link>
</p>
</div>
</template>
<script>
import firebase from "firebase";
import { auth } from "../plugins/firebase";
export default {
name: "Signup",
data() {
return {
email: "",
password: "",
};
},
methods: {
signUp: function () {
auth
.createUserWithEmailAndPassword(this.email, this.password)
.then((res) => {
console.log("Create account: ", res.user.email);
this.$router.push("/");
})
.catch((error) => {
console.log(error.message);
});
},
},
};
</script>
<template>
<div class="signin">
<h2>Sign in</h2>
<input type="text" placeholder="Email" v-model="email" />
<input type="password" placeholder="Password" v-model="password" />
<button @click="signIn">Login</button>
<p>
Don't you have an account?
<router-link to="/signup">sign up now!!</router-link>
</p>
</div>
</template>
<script>
import firebase from "firebase";
import { auth } from "../plugins/firebase";
export default {
name: "Signin",
data: function () {
return {
email: "",
password: "",
};
},
methods: {
signIn: function () {
auth.signInWithEmailAndPassword(this.email, this.password).then(
(res) => {
res.user.getIdToken().then((idToken) => {
localStorage.setItem("jwt", idToken.toString());
});
this.$router.push("/");
},
(err) => {
alert(err.message);
}
);
},
},
};
</script>
「――脳、が、震、え、る」
――意図した行動ではなかったのだろう。それはFirebaseにとっても理想とする、美しく手軽な行いであったのだろう。
これでAuthenticationの実装はできたのである。
「ファイア・ベース」
その一言をもって、この戦いに終止符が打たれる。
――最強のGoogle プラットフォーム Firebase。
――時刻およそ20時頃、先を急ぐ筆者の戦い。
少し熱くなり、ファンの鳴るパソコンの前で、筆者は小さく息を吸い、言った。
「――お前、『便利』だな」
さて、残すところは Firebase Hostingによるデプロイのみである。
第6話 「ただデプロイするだけの物語」
――気付けばまた、筆者の意識は暗闇の世界に導かれていた。
「どうすりゃHostingって使えるんだ...。」
今までHerokuであったり、ローカルで起動ばかりしてきた筆者にとっては、初めての感覚であった。もう正直、ネタも尽きてきて、使える文言もない。
そして彼は再び絶望を見ることとなる。
「このリファレンスを見ると、$ firebase init
して、$ firebase deploy
すればできるのか」
$ firebase init
zsh: command not found: firebase
「……うん、わかってる」
当然である。
グローバルにインストールしたわけではないので、怒られてしまうのだ。
yarnでローカルにインストールしたので、yarnのコマンドで動かす必要がある。
どうやらpackage.json
にて、yarn上で動かしているプラグインのコマンドを叩けるらしい。
ということでpackage.json
を書き直す。
...
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"base_login": "firebase login",
"base_init": "firebase init",
"base_deploy": "firebase deploy"
},
...
あとはもう目前である。時刻的には21:00頃であった。
$ yarn base_login
yarn run v1.21.1
$ firebase login
Already logged in as -@gmail.com
✨ Done in 3.16s.
ーーDone.
❯ yarn base_init
yarn run v1.21.1
$ firebase init
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########
You're about to initialize a Firebase project in this directory:
/{ディレクトリパス}
ーーFIREBASE.
******Firebaseで使用したものを選択。******
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Database: Configure Firebase Realtime Database and deploy rules
◯ Firestore: Deploy rules and create indexes for Firestore
◯ Functions: Configure and deploy Cloud Functions
◯ Hosting: Configure and deploy Firebase Hosting sites
◯ Storage: Deploy Cloud Storage security rules
◯ Emulators: Set up local emulators for Firebase features
◯ Remote Config: Get, deploy, and rollback configurations for Remote Config
ーーHostingのみだ。
******既にコードは書いたので一番上******
? Please select an option: (Use arrow keys)
❯ Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
ーー注意して選択。
******どのプロジェクトか選択、loginしているので自分のプロジェクトを選択******
? Select a default Firebase project for this directory: (Use arrow keys)
❯ {自分のプロジェクト名} (アプリ名)
ーー普段から使っている人は間違えないように。
******Nuxtはdistというファイルに書き出すので間違えないように。******
? What do you want to use as your public directory? dist
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
? Set up automatic builds and deploys with GitHub? No
ーーそして。
i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...
✔ Firebase initialization complete!
✨ Done in 394.01s.
ーーDone.
さあ、最後の戦いだ。
まず書いたコードをHTMLにパースする。
$ yarn build
yarn run v1.21.1
$ nuxt build
ℹ Production build 17:51:19
ℹ Bundling only for client side 17:51:19
ℹ Target: static 17:51:19
✔ Builder initialized
これでNuxtがdist
ディレクトリにパースしてくれる。
幾度筆者がpublic
と間違えただろうか。overwriteという英単語にも気をつけよう。
$ yarn base_deploy
yarn run v1.21.1
$ firebase deploy
=== Deploying to '{自分のURL}'...
i deploying hosting
i hosting[testapp-cd802]: beginning deploy...
i hosting[testapp-cd802]: found 15 files in dist
✔ hosting[testapp-cd802]: file upload complete
i hosting[testapp-cd802]: finalizing version...
✔ hosting[testapp-cd802]: version finalized
i hosting[testapp-cd802]: releasing new version...
✔ hosting[testapp-cd802]: release complete
✔ Deploy complete!
Project Console: https://console.firebase.google.com/{自分のURL}
Hosting URL: https://{自分のURL}
✨ Done in 13.44s.
ーーDone.
Hosting URLへアクセスすれば無事デプロイ完了。
最初はFirebase Hostingの設定完了画面が出るが、時間が経てば無事にデプロイされている。
(21:30ほどに達成したが、反映されたのは22:00頃だった)
長く長く続いた、苦難と絶望の繰り返し。
それを乗り越えてようやく得た、圧倒的達成感。
これはただ、この達成感を得るためだけの物語。
遠回りして、エラーを踏み続け、迷い続けてきた、それだけの物語。
ひとりの自信のない少年が、ふとした要望に全力で応える。
ただそれだけのために頑張った――それだけの物語。
最終話 「ゼロから始めるNoSQL生活」
圧倒的達成感とデプロイURLを手にした少年は自信満々に報告した。
「ーーできたよ。」
「ありがとうーー!!マジで感謝!!はや!すご!」
あれだけ苦労して、あれだけ疲労して、あれだけ奔走をして、
あれだけ命懸けで戦い抜いて、その報酬が感謝の言葉と笑顔(たぶん)ひとつ。
ああ、なんと――。
「ああ、まったく、わりに合わねぇ」
少年はずっとずっと噛み締めていた。
実際にかかった工数はざっと5時間ほど。
要件通りのものを無事作り上げたのだ。
※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※ ※
――と、ここで終わっていればいい話で終われたのだが。
「...ところでさ」
「ん?」
「これって投稿とか削除私にもできるようにできる?」
視界が大きく傾く。それは...
――ああ、疲れててもまだやれる気がするな、NoSQL生活。
そんな感想を最後に、眠気とショックが筆者の意識を波濤の如く押し流していった。
エピローグ 「結局やった。」
長かったですが、これで僕のRe: ゼロから始めるNoSQL生活を締めたいと思います。
自分なりに作品リスペクトが多すぎて、結局
「何やってんのかわからん」
になりそうかなあ、とも思いますが、怒涛の5時間の苦労は伝わったのではないかと思います。
読んでいる皆様は何回もこれくらいのギリギリのラインは超えてきた方々であると重々承知ですので、本当に軽い小説くらいの気持ちで読んでいただければ幸いです。(←最初に書け)
NoSQLですが、文中にも書いた通り、スピード実装、手軽く簡単、コスパがいい、ですが
本当はデメリットも多く、選定する際にはお気をつけください。
まあ流行りでもあるようなので、自分自身体験的には貴重なものだったと思っています。
ここまで読んでくださった方々の更なる飛躍を、僅かながら祈っております。
(ちなみに最後にあった通り、自分がFirebaseを触らなくてもCRUDを回せるまで実装は後日やり切りました。今では完全にマネージャーの方々の方に権限を譲っていますが、自分としてはエンジニアが関わらなくても自走できるものを作れた、んじゃないかな?!なんて思っています。ここにリポジトリを貼っておくので、実装してみたい方は参考までに見てみてください。というかここまでやってやっとNoSQLっていうんじゃな(ry...))
https://github.com/Shunya078/20LizardsSoul/tree/master/20lizards
参考文献
- Re: ゼロから始める異世界生活 https://ncode.syosetu.com/n2267be/
- Firebase Authentication 様 https://firebase.google.com/docs/auth?hl=ja
- Firebase Hosting 様 https://firebase.google.com/docs/hosting/quickstart?hl=ja
- Vuetify https://vuetifyjs.com/ja/
- SPAハンズオン! https://qiita.com/po3rin/items/d3e016d01162e9d9de80