見出し画像

Vue.jsとFirebaseで、noteライクなSNSアプリを5時間で作ろう!

コーヒー1杯の値段で、ウェブアプリ開発の必須スキルをマスターしましょう。

こんにちは!フロントエンドエンジニアのSIです。Techpitなどで教材販売の実績があります。

今回はプログラミング初級者向けに、Vue.js&Firebaseという鉄板の組み合わせを使って、noteライクなSNSアプリを作れるチュートリアルを公開しました。

ProgateでHTML, CSS, Javascriptあたりまでを学び終えたくらいの知識で取り組める内容であり、かつWebアプリ開発のための必須スキルを盛り込んであるので、更に一歩前進したい方、ぜひ取り組んでみて下さい!

このチュートリアルは、Vue.js初心者が、アプリ開発に必要な基本機能の実装スキルを最短で身につけることを目的に作られたものです。

このチュートリアルではVue.jsとFirebaseを使って、「note」のように各ユーザーが自分の書いた文章を投稿・編集・削除できるSNSアプリ「words」を作っていきます。デモサイトはこちら

このチュートリアルを一通り進めることで、ログイン機能+投稿の閲覧・新規投稿・編集・削除という必要最低限の機能をそなえたWebアプリを誰でも1から最短で作ることができます。

「Tailwind」という人気のデザインフレームワークを使って開発していくので、デザインに割く時間を最小化しつつオシャレなアプリを作りたいという方にもおすすめです。

前提知識

HTML, CSS, Vue.jsに関する基本的な理解を持っていること:ProgateレベルのHTML, CSSを一通り終え、Vue.js公式のチュートリアルに目を通した程度で十分です。

それでは、早速やっていきましょう!

もくじ

STEP 0: VS Code, Node.jsをインストールしよう

STEP 1: Vue.jsのプロジェクトを準備しよう

1-1: Vueプロジェクトを作成する
1-2: Tailwindをインストールする
1-3: firebase, vuefireをインストールする

STEP 2: Firestoreを準備しよう
2-1: Firebaseプロジェクトを作成する
2-2: Firestore(データベース)を作成する
2-3: Vue.jsアプリとFirestoreを連携させる

STEP 3: ヘッダーとトップページを作成しよう
3-1: ヘッダーを作成する
3-2: トップページ(投稿一覧)を作成する
3-3: リストアイテムをコンポーネント化する

STEP 4: ユーザー機能を実装しよう
4-1: ソーシャル・ログイン機能を実装する
4-2: 初回サインイン時にユーザー情報をFirestoreに保存する

STEP 5: CRUD機能を実装しよう
5-1: 投稿機能を実装する
5-2: 編集機能を実装する
5-3: 削除機能を実装する

STEP 6: アプリをデプロイして公開しよう

付録:コード全体へのリンク


STEP. 0: VS Code, Node.jsをインストールしよう

0-1: VS Codeをインストールしよう

VS Codeは、プログラマが一般的に使っているコードエディタです。ターミナル(コマンドを打つ場所)をエディタと一体的に使うことができ、便利です。VS Codeホームページから無料でダウンロードできるので、まだ持っていない方は、ぜひこの際にご用意ください。なお、本チュートリアルを進める上で、VS Codeが必須であるわけでは必ずしもないので、すでにAtomなど他のエディタに慣れている場合は、わざわざ乗り換えなくて構いません。

インストール後、VS Codeを開いて、メニューバーの「Terminal」から「New Terminal」を選択すると、エディタの下部にターミナルが現れます。これ以降、下のように「$」で始まるコマンドが沢山出てきますが、これは「$に続くコードをターミナルで実行して下さい」という印です。「$」自体は元々ターミナルの入力箇所にある記号なので、コマンドを実行する際には記入しないように注意して下さい。

$ command~~

0-2: Node.jsをインストールしよう

Node.jsとは、サーバサイドでJavaScriptを実行させるためのプラットフォームです。Node.jsをインストールすることで、自動的にnpmもインストールされます。

npmとは、Node Package Managerの略で、その名の通り、Node.jsのパッケージを管理するためのツールです。

<Macでのインストール方法>

Node.jsをインストールするには、Homebrewというパッケージ管理ソフトを使う方法が最も簡単です。Homebrewを持っていない場合、ターミナルで次のコマンドを実行して、Homebrewをインストールして下さい。

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

続けて、Homebrewを使ってNode.jsとnpmをインストールします。ターミナルで次のコマンドを実行して下さい。

$ brew install node

<Windowsでのインストール方法>

一般的なソフトウェアと同様、Node.jsホームページからインストーラをダウンロードして、実行して下さい。

インストールが終わったら、ターミナルで次のコマンドを順番に実行して下さい。

$ node -v
$ npm -v

それぞれ、バージョン情報が表示されていれば、インストール成功です。


STEP.1: Vue.jsのプロジェクトを準備しよう

1-1: Vue.jsアプリのプロジェクトフォルダを作成する

ターミナルで次のコマンドを実行し、Vue CLI をインストールしてください。

$ npm i vue-cli -g

Vue CLIの`vue create`コマンドを使うことで、プロジェクトフォルダを用意するのが一番簡単です。ターミナルにてフォルダを作成したいディレクトリに移動した上で、

$ vue create アプリ名

を実行してみましょう(「アプリ名」の部分は自由です)。

ここから続けていくつかの質問に答えていきます。下に特記するもの以外は、自由に答えて構いません。

①デフォルト設定かマニュアル設定かを選ぶ:マニュアルを選びます。

? Please pick a preset:
 default (babel, eslint)
> Manually select features

②必要なツールを選ぶ:Babel, PWA, Router, CSS Pre-processorの4つを選びます。

? Check the features needed for your project:
>(*) Babel
( ) TypeScript
(*) Progressive Web App (PWA) Support
(*) Router
( ) Vuex
(*) CSS Pre-processors
( ) Linter / Formatter
( ) Unit Testing
( ) E2E Testing

③ヒストリーモード(URLから#を抜く設定)の是非を選ぶ:Yesを選んでください。

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)

ヒストリーモードにした場合、URLが改変されるので、そのままでは、各ページに直接URLを指定して移動することができなくなります(つまり、外部サイト内のリンクからアプリにアクセスしたりできなくなる)。これを解決するには、デプロイ時に「フォールバック」という、ルーター上にないURLが指定された時にindex.htmlを表示させるように設定する必要があります(詳細は、Vue.jsアプリのデプロイに関するmoduleを参照)。

④CSS Pre-processorの種類を選ぶ:今回はStylusを選びます。

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
 Sass/SCSS (with dart-sass)
 Sass/SCSS (with node-sass)
 Less
> Stylus

⑤設定ファイルの作成方法を選ぶ:In dedicated config filesを選んでください。

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
> In dedicated config files
 In package.json

質問が終わると、プロジェクトフォルダが作成され、アプリを開発モードで起動できるようになります。

アプリを開発モードで起動するには、ターミナルでプロジェクトフォルダのディレクトリに移動して、次のコマンドを実行してください。

$ npm run serve

プロセス完了後、ブラウザでlocalhost:8080にアクセスすると、Vue.jsアプリのデフォルト画面が表示されます。

画像1

1-2: tailwindをインストールする

ここから、追加で2つの機能をアプリにインストールしていきます。

ひとつめは、tailwindです。tailwindは、人気のデザインフレームワークです。これを利用することで、CSSを書く手間を大きく省くことができます。

$ npm install tailwindcss --save-dev

続けて、下のコマンドを実行し、設定用ファイルを作成します。

./node_modules/.bin/tailwind init tailwind.js

プロジェクトフォルダからsrcフォルダを開いて、assetsフォルダ内にcssというフォルダを新規作成してください。さらにその中に、tailwind.cssというファイルを新規作成してください。そして、そのファイルを下のように編集してください。

/* src/assets/css/tailwind.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

src/postcss.config.jsonを開き、中身を次のように変更してください。

// postcss.config.json

module.exports = {

 // ここから削除
 plugins: {
   autoprefixer: {}
 }
 // ここまで削除

 // ここから追加
 "plugins": [
   require('tailwindcss')('tailwind.js'),
   require('autoprefixer')(),
 ]
 // ここまで追加

}

1-3: Firebase, vuefireをインストールする

次のパートで、アプリをFirebaseと連携させるのですが、連携の際に必要なFirebaseとvuefireという2つのプラグインをインストールしておきます。

$ npm install vuefire@next firebase --save

1-4: vue-date-fnsをインストールする

後ほど、投稿データに日付をつけるのですが、javascriptでは時刻データの表示設定が面倒なので、簡略化のためにこちらのプラグインを利用することになります。ターミナルで次のコマンドを実行して下さい。

$ npm i vue-date-fns --save

最後に、main.jsを次のように編集し、アプリに3つのプラグインをインポートします。

// src/main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './registerServiceWorker'

// ここから追加
import '@/assets/css/tailwind.css'
import { firestorePlugin } from 'vuefire'          
import firebase from 'firebase'     
import 'firebase/firestore'
import VueDateFns from "vue-date-fns";

Vue.use(VueDateFns);

Vue.use(firestorePlugin)
firebase.initializeApp({              
 apiKey: "",
 authDomain: "",
 databaseURL: "",
 projectId: "",
 storageBucket: "",
 messagingSenderId: "",
 appId: ""
})
export const db = firebase.firestore()
export const auth = firebase.auth()
// ここまで追加

Vue.config.productionTip = false

new Vue({
 router,
 render: h => h(App)
}).$mount('#app')
firebase.initializeApp({ }) の部分では、firebaseの初期設定を行います。中身の各要素の値は空欄となっています。次のパートでFirebaseとの連携を行った際に、連携のためのコードが発行されます。そのコードをこちらに記入することになります。

その下では、`db`という名前でアプリ内でFirestoreデータベースを使えるようにしています。同様に、`auth`という名前でユーザー管理のためのサービスを使えるようにしています。


STEP.2: Firestoreを準備しよう

2-1: Firebaseプロジェクトを作成する

次のパート以降でアプリ内の各ページ・各機能を実装していきますが、その前にまず、Google Firebaseとアプリの連携を済ませてしまいましょう。

アプリとFirebaseを接続するには、まずFirebaseプロジェクトを新規作成する必要があります。Googleにログインしている状態で、Firebase公式ページの右上にある「コンソールへ移動」ボタンから、ユーザーページに移動して下さい。

「プロジェクトを追加」から新規プロジェクトを作成します。プロジェクト名は「words」とします。

画像2

これでFirebaseのプロジェクトが作成されました。

2-2: Firestoreを作成する

続けて、FirestoreというFirebaseプロジェクト内で使うことのできるデータベースを作成します。プロジェクトメインページ左のメニューバーから「Database」を選び、「データベースの作成」に進んでください。

画像3

「テストモードで開始」を選択し、「有効にする」をクリックすると、データベースが作成されます。

画像4

以上で、FirebaseとFirestoreの用意が完了しました。

2-3: Firestoreにデータを追加する

Firestoreでは、データの入れ物として「コレクション」を用意し、個々のデータを「ドキュメント」としてコレクションのなかに保存していきます。

各ドキュメントには任意の「フィールド」を持たせることができます。フィールドを作成する際には、そのフィールドの型を指定する必要があります。

今回は「posts」というコレクションを作成しましょう。

個々のドキュメントについて、①投稿ユーザーのID、②投稿日時、③タイトル、④投稿の内容、⑤タグ、⑥サムネイル画像URLの6つを保存したいので、

画像5

というフィールドを作成していきましょう。

まず、データベースのトップページから、「+コレクションを追加」をクリックしてください。

すると、「コレクションの追加」というポップアップが現れます。「コレクションID」は「posts」とし、「ドキュメントID」「自動ID」で自動生成させます。

画像6

「フィールド」は上の通り6つ作成します。サンプルデータなので、値は自由で構いません。ただし、uidフィールドだけは全て「0001」としておいて下さい。

画像7

3つほど仮の投稿を作成しておきましょう。

画像8

同様にして、サンプルのユーザーデータを1つだけ作成します。まず、「users」というコレクションを作成します。この中に、ドキュメントを作成します。こちらは、IDを「0001」とした上で、「displayName(ユーザー名)」と「photoURL(アイコン画像のURL)」の2つのフィールドを設定して下さい。

画像9

以上で、Firestoreの用意は完了です。

2-4: Vue.jsアプリとFirebaseを連携させよう

Firebaseのプロジェクトのメインページから、「アプリを追加」→「ウェブ」に進んでください。そこで任意のアプリ名を入力し、「アプリを登録」をクリックすると、連携に必要なコードが表示されます。こちらを、main.js内で先ほど空欄にしておいた箇所にコピペして下さい。

画像10

画像11

これで、Vue.jsとFirebaseの連携が完了しました。


STEP.3: ヘッダーとトップページを作成しよう

3-1: ヘッダーを作成する

いよいよ、アプリの中身を作っていきます。まず、全ページに共通して表示させたいヘッダー部分を作成します。全ページ共通の要素は、src/App.vue内にコードを書いていきます。

もちろん、App.vue内に<header>要素を直接書いていっても良いのですが、今回は、Vue.jsの「コンポーネント」という仕組みを理解するために、あえてヘッダー用のコンポーネントを作成することから始めましょう。

Vue.jsでは、ヘッダー、フッター、リスト内の各項目といったページ内の各要素や、各個別ページをコンポーネントファイルという単位に分けて管理することができます。各要素ごとに異なるファイルで管理できるので、コードの繁雑化を防ぐことができ、便利です。

ページ内要素用のコンポーネントを新規作成する場合は、src/componentsフォルダ内にvueファイルを作成します。今回は、ヘッダー用のコンポーネントを作成したいので、componentsフォルダ内にHeader.vueというファイルを作成して下さい(コンポーネントファイルの名前は、大文字から始めるのが慣例です)。

Header.vueを次のように編集します。ロゴとユーザー情報を含む簡単なヘッダーのテンプレートおよびデザインのコードです。

src/components/Header.vue

<template>
 <header class="fixed top-0 w-full p-3">
   <div class="flex justify-between">
     <router-link to="/">
       <h1 class="text-3xl font-bold">W.</h1>
     </router-link>
     <div class="flex">
       <div class="w-10 h-10 rounded-full bg-black mr-2"></div>
       <div>
         <p>Pen Write</p>
         <p class="-mt-1 text-sm">Log Out</p>
       </div>
     </div>
   </div>
 </header>
</template>
各要素にクラスが設定されています。Tailwindをインストールすると、このようにクラスを指定するだけでページをデザインすることができます。具体的な設定方法については、Tailwind公式ドキュメントを参照して下さい。
<h1>タグを<router-link to="/">タグで囲うことで、ロゴにトップページへのリンクを設定しています。

このヘッダーテンプレートを、App.vueにインポートし、テンプレートに挿入することで、全てのページにヘッダーを表示させることができます。

App.vueを開いて、次のように編集して下さい。

src/App.vue

<template>
 <div id="app">

   <!-- ここから削除 -->
   <div id="nav">
     <router-link to="/">Home</router-link> |
     <router-link to="/about">About</router-link>
   </div>
   <!-- ここまで削除 -->

   <Header /> <!-- ここを追加 -->

   <main class="my-16 mx-auto"> <!-- ここを追加 -->
     <router-view/>
   </main> <!-- ここを追加 -->
 </div>
</template>

<script>
import Header from '@/components/Header' // ここを追加
export default {

 // ここから追加
 components: {
   Header
 }
 // ここまで追加

}
</script>

<style lang="stylus">

/* 元のコードを全て削除し、以下を追加 */

main
  width: 95%
  max-width: 500px

</style>
スクリプト内でヘッダーコンポーネントをインポートしたのち、テンプレート内に<Header />を挿入しています。
<router-view />部分が、各ページ用のコンポーネントファイル(トップページの場合はsrc/views/Home.vue)のコードを受けています。これを<main>要素で囲い、これにスタイルを設定することで、各ページのメイン要素に対して共通の設定を効かせることができます。

変更を保存して、ブラウザでアプリを表示させると、下のようにヘッダーが表示されているはずです。

画像12

3-2: トップページ(投稿一覧)を作成する

次に、トップページを作成しましょう。トップページには、各ユーザーの投稿記事を一覧で表示させたいです。各ページ用のコンポーネントは、src/viewsフォルダ内にまとめられており、トップページ用のコンポーネントとしてHome.vueがデフォルトで用意されています。

<!-- src/views/Home.vue -->

<template>
 <div class="home">

   <!-- ここから削除 -->
   <img alt="Vue logo" src="../assets/logo.png">
   <HelloWorld msg="Welcome to Your Vue.js App"/>
   <!-- ここまで削除 -->

   <!-- ここから追加 -->
   <li 
     v-for="post in posts"
     :key="post.id"
     class="list-none my-5"
   >
     <h2 class="text-xl">{{ post.title }}</h2>
     <p class="text-sm">{{ post.createdAt }}</p>
   </li>
   <!-- ここまで追加 -->

 </div>
</template>

<script>
import HelloWorld from '@/components/HelloWorld.vue' // ここを削除
import { db } from '@/main' // ここを追加
export default {
  name: 'home',

  // ここから削除
  components: {
    HelloWorld
  }
  // ここまで削除

  // ここから追加
  data() {
    return {
      posts: []
    }
  },
  firestore() {
    return {
      posts: db.collection('posts')
    }
  }
  // ここまで追加

}
</script>
スクリプト内では、main.jsから`db`(Firestore)をインポートすると同時に、全投稿データを格納するための配列変数`posts: []`をdata内に用意しています。その上で、vurefireの関数`firestore()`を使って、postsコレクションの全データを変数`posts`に格納しています。
関数`firestore()`内でコレクション全体を取得したい場合は、
db.collection('コレクション名')
とします。
テンプレート内では、配列データに対する繰り返し処理を行うためのv-forディレクティブを用いて、postsデータを一覧表示させています。

v-for="post in posts"とすることで、postsデータを順番にpostに代入することができます。v-forを使う際には、代入される各データを判別するための一意のキーを:keyプロパティとして設定する必要がある点に注意が必要です。Firestoreから取得したデータに関しては、idフィールドをキーとして用いることができます(つまり、:key="post.id"のようにすれば良い)。

配列内の各データのフィールド値を取得したい場合は、キャメルケース「{{ }}」で囲って、{{ post.フィールド名 }}のようにします。

変更を保存して、ブラウザを確認してみましょう。

画像13

Firebaseとの連携がうまくいっていれば、サンプルデータがリスト表示されているはずです。

3-3: リストアイテムをコンポーネント化する

次に、今作成したリストの各要素をコンポーネント化しましょう。そうする理由としては、もちろん管理のしやすさという点もありますが、最も重要なのは、そうすることで、投稿者情報の表示が簡単にできるようになることです。実際にやってみましょう。

まず、リスト要素用のコンポーネントとして、Item.vueをcomponentsフォルダ内に作成し、次のように編集します。

<!-- src/components/Item.vue -->

<template>
 <div class="list-none my-8">
   <h2 class="text-xl">{{ post.title }}</h2>
   <p class="text-sm">{{ post.createdAt }}</p>
 </div>
</template>

<script>
export default {
  props: ['post']
}
</script>
テンプレート部分は、トップページのリスト要素のコードをそのままペーストしただけです。スクリプトでは、postというプロパティを設定しています。下で、Home.vueのテンプレート内でItem.vueを挿入する際に、各投稿データをpostプロパティに渡すように設定します。

このようにすることで、Home.vueで取得した投稿データを、Item.vueに送ることができるようになるのです。

Home.vueを次のように編集します。

<!-- src/views/Home.vue -->

<template>
 <div class="home">

   <!-- ここから削除 -->
   <li 
     v-for="post in posts"
     :key="post.id"
     class="list-none my-5"
   >
     <h2 class="text-xl">{{ post.title }}</h2>
     <p class="text-sm">{{ post.createdAt }}</p>
   </li>
   <!-- ここまで削除 -->

   <!-- ここから追加 -->
   <Item v-for="post in posts"
     :key="post.id"
     :post="post"
     class="list-none my-5"
   />
   <!-- ここまで追加 -->

 </div>
</template>

<script>
import Item from '@/components/Item' // ここを追加
import { db } from '@/main'

export default {
  name: 'home',

  // ここから追加
  components: {
    Item
  },
  // ここまで追加

  data() {
    return {
      posts: []
    }
  },
  firestore() {
    return {
      posts: db.collection('posts')
    }
  }
}
</script>
まず、スクリプト内でItem.vueをインポートしています。

テンプレート内にItem.vueを挿入する際には、上で説明した通り、:post="post"としてpostプロパティを追加し、値としてposts配列の各要素postを与えます。

変更を保存してブラウザで表示を確認して下さい。先ほどと変わっていなければ、成功です。

最後に、投稿ユーザーの情報を追加表示させます。Item.vueを次のように変更して下さい。

<!-- src/components/Item.vue -->

<template>
 <div class="list-none my-8">
   <h2 class="text-xl">{{ post.title }}</h2>

   <p class="text-sm">{{ post.createdAt }}</p> <!-- ここを削除 -->

   <!-- ここから追加 -->
   <div class="flex mt-2">
     <div 
       class="w-10 h-10 bg-cover bg-center rounded-full border border-black mr-2"
       :style=" 'background-image: url(' + user.photoURL + ')' "
     ></div>
     <div>
       <p>{{ user.displayName }}</p>
       <p class="text-xs">{{ post.createdAt }}</p>
     </div>
   </div>
   <!-- ここまで追加 -->

 </div>
</template>

<!-- ここから追加 -->
<script>
import { db } from '@/main'
export default {
  props: ['post'],
  data() {
    return {
      user: {}
    }
  },
  firestore() {
    return {
      user: db.collection('users').doc(this.$props.post.uid)
    }
  }
}
</script>
<!-- ここまで追加 -->
スクリプト内では、dbをインポートすると同時に、data内に変数userを追加します。次に、関数firestore()を用いて、usersコレクションからpostプロパティのuidフィールドの値と等しいIDを持つドキュメント(つまり、各記事を投稿したユーザーのデータ)を取得し、これをuserに格納します。

テンプレート内では、user.displayName, user.photoURLを用いて投稿ユーザーの名前とアイコンを表示させます。

変更を保存し、ブラウザを確認して下さい。次のようになっていれば成功です。

画像14

ここまでのプロジェクトフォルダをダウンロード


STEP 4: ユーザー機能を実装しよう

4-1: Firebaseでソーシャル・ログイン機能を設定する

ここから先は

23,592字 / 9画像

¥ 500

頂いたサポートは、書籍購入をはじめとする研究活動費として使用させて頂きます。