Dzisiaj chciałbym przedstawić czym jest platforma Firebase, a dokładniej jedno z możliwych jej zastosowań. Wyobraźcie sobie sytuacje, że macie pomysł na aplikację. Radzicie sobie całkiem nieźle w jednym z JS-owych frameworków, więc siadacie do kodu, popijacie kawę i zaczynacie przelewać swój genialny pomysł w coś rzeczywistego. Środowisko postawione, draft aplikacji gotowy, komponenty przygotowane czas więc przejść do szczegółów implementacji. Jest jednak mały problem… skąd brać jakieś dane i gdzie je zapisywać. Nie macie czasu (lub ochoty) na pisanie części backendowej swojej aplikacji – chcecie po prostu zrealizować swój pomysł lub pobawić się nowym frameworkiem bo zaczął się kolejny tydzień i właśnie pojawił się „nowy gracz” na rynku 😉
W związku z tym, przydałoby się jakieś rozwiązanie, które pozwoli  skupić się tylko na części frontendowej i mało tego – to właśnie z tej części chcecie utworzyć strukturę bazy danych. Właśnie jednym z takich rozwiązań jest platforma Firebase od Google.

Czym jest Firebase?

Firebase jest platformą developerską działającą w chmurze. Projekt został założony w 2011 roku przez Firebace, Inc a w 2014 roku został przejęty przez Google. Nie będę wchodził w szczegóły samej platformy, ze względu na jej ogromne możliwości i w związku z tym idę na łatwiznę, i odsyłam do: https://en.wikipedia.org/wiki/Firebase oraz https://firebase.google.com/docs/.

Nasz projekt

Czas przejść do szczegółów – nasz projekt. Co chcemy osiągnąć? Chcemy utworzyć prosty system blogowy z możliwością podglądu listy wszystkich postów, wyświetleniem szczegółów postu oraz możliwością dodania nowego postu.

Zakładamy bazę danych

Firebase udostępnia bazę NoSQL i ją właśnie wykorzystamy w tym prostym projekcie. Na szczęście założenie konta w Firebase oraz wykorzystanie części możliwości platformy jest całkowicie darmowe – wystarczy konto w Google. Założenie konta w Firebase jest banalnie proste. Wchodzimy na stronę https://firebase.google.com/ i klikamy „Otwórz konsole”, a następnie wybieramy jedno z naszych kont Google (zakładam, że macie konto na gmail). Po zalogowaniu się do konsoli Firebase możemy utworzyć nasz pierwszy projekt. Klikamy zatem: „Dodaj projekt”, wpisujemy nazwę projektu i wybieramy kraj.

 

firebase1
firebase2

Po utworzeniu konta zostajemy od razu przekierowani do konsoli Firebase. Ze względu, że nas interesuje tylko baza danych – wybieramy z lewego menu „Database”. Po wybraniu tej opcji pokaże nam się struktura bazy – drzewo kolekcji danych (póki co puste). Możemy oczywiście z tego poziomu utworzyć strukturę bazy i wypełnić ją danymi – jednak nie to jest naszym docelowym rozwiązaniem. Nas interesuje tylko adres URL, który znajduje się nad strukturą bazy. To właśnie po tym adresem będzie nasz „backend” i wszystkie endpointy, które utworzymy „w locie” z poziomu aplikacji webowej.

 

firebase3

Zgodnie z powyższym zrzutem – interesuje nas link: https://moj-projekt-3e564.firebaseio.com/. Zanim jednak przejdziemy dalej musimy zmodyfikować uprawnienia do naszej bazy danych. Klikamy zatem zakładkę „Reguły”. Domyślnie reguły dla nowego projektu to:

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

 

 

Oznacza to, że zapisywanie i odczytywanie z bazy danych może się odbywać tylko i wyłącznie dla autoryzowanych użytkowników. Ze względu na to, że autoryzacja w Firebase nie jest tematem tego postu (chcemy tylko wykorzystać bazę danych), zmienimy reguły tak aby każdy mógł zapisywać i odczytywać dane. Zatem nowe ustawienia powinny wyglądać następująco:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

 

 

Polecam zapoznać się z regułami bazy danych Firebase i sposobem autoryzacji – platforma ma naprawdę ogromne możliwości.

Nasza aplikacja

Przejdźmy teraz do ciekawszej części tego wpisu – samej aplikacji. Aplikację napiszemy w Vue.js z wykorzystaniem vue-cli. Narzędzie vue-cli utworzy za nas całą strukturę aplikacji, co pozwoli nam przejść od razu do samej implementacji i realizacji genialnego pomysłu 😉

Zainstalujmy zatem vue-cli korzystając z NPM, wpisując komendę:

npm install -g vue-cli

Następnie wygenerujmy aplikację korzystając z jednego z szablonów, które udostępnia vue-cli (wszystkie możliwe szablony znajdują się tutaj -> Click!). Wygenerujemy aplikację korzystając z szablon dla „pwa”.

vue init pwa my-awesome-blog

Generator zada nam kilka podstawowych pytań. Nie będę ich wszystkich szczegółowo omawiał. Zatrzymamy się tylko na jednym pytaniu – „Vue build”

vue-cli

Czego on od nas chce? Vue 2.0 wykorzystuje VirtualDOM do generowania komponentów. W związku z tym musi konwertować HTML korzystając z funkcji renderującej. Kompilator odpowiedzialny za to znajduje się w tzw. wersji „standalone” ale nie w wersji „runtime-only”. Jeżeli nie chcemy korzystać z kompilatora, czyli wszystkie komponenty będą w plikach z rozszerzeniem *.vue a nie jako pliki *.js, gdzie do komponentu musimy dodać szablon widoku jako HTML – możemy wybrać wersję „Runtime-only” i tym samym zaoszczędzić 6KB po wygenerowaniu aplikacji w wersji gotowej na produkcję. W naszym przypadku nie ma to znaczenia, wybieramy zatem „Runtime + Compiler”.

Po zakończeniu generowania przechodzimy do folderu aplikacji i wydajemy komendę npm install w celu zainstalowania wszystkich zależności.

Nie będę opisywał całej struktury aplikacji, która została wyrenderowana z vue-cli oraz nie będę się skupiał na opisaniu jak działa Vue.js. Biblioteka ma bardzo przyjazną dokumentację – do której odsyłam tutaj -> Vue.js

Na potrzeby tego prostego przykładu aplikacji wykorzystajmy bibliotekę bootstrap. Zatem w pliku index.html doklejamy:

<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">    
<!-- Add to home screen for Windows -->

 

 

Teraz zróbmy porządek po tym co wygenerował vue-cli. Wyczyśćmy plik src/App.vue i wpiszmy:

<template>
  <div id="app">
    <header>
      <span>My awesome blog</span>
      <nav>
        <ul class="menu">
          <li class="menu__item"><router-link to="/">All posts</router-link></li>
          <li class="menu__item"><router-link to="/add-post">Add post</router-link></li>
        </ul>
      </nav>
    </header>
    <main>
      <div class="container">
         <router-view></router-view>
      </div>
    </main>
  </div>
</template>

<script>
export default {
  name: 'app',
};
</script>

<style>
body {
  margin: 0;
}

#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}

header {
  margin: 0;
  height: 56px;
  padding: 0 16px 0 24px;
  line-height: 25px;
  background-color: #35495E;
  color: #ffffff;
  padding-top: 5px;
}

header span {
  position: relative;
  font-size: 20px;
  line-height: 1;
  letter-spacing: .02em;
  font-weight: 400;
  box-sizing: border-box;
  padding-top: 16px;
}

.menu {
  float: left;
  width: 100%;
  padding: 0;
  margin: 0;
  list-style: none;
}

.menu__item {
  display: inline-block;
}

.menu__item a {
  display: block;
  color: #FFFFFF;
  font-size: 12px;
}

a {
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

</style>

 

 

Następnie w folderze src/components usuńmy plik Hello.vue i utwórzmy komponenty, które będą nam niezbędne. Czyli:
PostsList.vue – komponent odpowiedzialny za listę wszystkich postów
PostDetails.vue – komponent odpowiedzialny za wyświetlenie szczegółów danego postu
AddNewPost.vue – komponent odpowiedzialny za dodanie nowego postu

W celu uruchomienia projektu w wersji deweloperskiej wydajemy komendę npm run dev. Niestety – zaraz po uruchomienia serwera, konsola krzyczy do nas błędem, że plik Hello.vue nie istnieje – i słusznie, bo właśnie go usunęliśmy. Przechodzimy zatem do pliku src/router/index.js i ustawiamy ścieżki zgodnie z tym czego potrzebujemy w naszej aplikacji:

import Vue from 'vue';
import Router from 'vue-router';
import PostsList from '@/components/PostsList';
import PostDetails from '@/components/PostDetails';
import AddNewPost from '@/components/AddNewPost';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Posts',
      component: PostsList,
    },
    {
      path: '/post/:id',
      name: 'Post',
      component: PostDetails,
    },
    {
      path: '/add-post',
      name: 'NewPost',
      component: AddNewPost,
    },
  ],
});

 

 

 

Ze względu na to, że nie mamy jeszcze żadnych postów w pierwszej kolejności skupimy się na komponencie, który jest odpowiedzialny za dodawanie nowego postu – AddNewPost.vue. Komponent powinien wyglądać tak:

<template>
  <form class="add-new-post" name="add-new-post" :model="newPost" @submit.prevent="saveNewPost">
    <div class="form-group">
      <label for="post-title">Post title</label>
      <input type="text" class="form-control" name="post-title" v-model="newPost.title"></input>
    </div>
    <div class="form-group">
      <label for="post-content">Post content</label>
      <textarea name="post-content"class="form-control" v-model="newPost.content" rows="25"></textarea>
    </div>    
    <button class="btn btn-primary btn-block">Save</button>
  </form> 
</template>

<script>
export default {
  name: 'AddNewPost',
  data() {
    return {
      newPost: {
        title: '',
        content: '',
      },
    };
  },
  methods: {
    saveNewPost() {

    },
  },
};
</script>
<style>
  .add-new-post {
    margin-top: 20px;  
  }
</style>

 

 

Póki co komponent nie robi nic, oprócz wyświetlenia formularza dodania nowego postu. Za dodanie nowego postu do bazy danych w Firebase będzie odpowiedzialna metoda saveNewPost(). Co zatem chcemy zrobić? Chcemy:

  • pobrać z formularza tytuł i treść nowego postu
  • dodać datę dodania nowego postu
  • zapisać post w bazie danych
  • wyczyścić formularz

Do sformatowania daty na format bardziej czytelny wykorzystamy bibliotekę dateformat (Click!). Natomiast do komunikacji z Firebase bibliotekę axios (Click!). Obie oczywiście instalujemy korzystając z NPM.

Ze względu na to, że do komunikacji z Firebase wykorzystamy API oparte REST, do zapisu nowego postu wykorzystamy request typu POST.

Zatem logika naszego komponentu powinna wyglądać tak:

import axios from 'axios';
import dateformat from 'dateformat';

export default {
  name: 'AddNewPost',
  data() {
    return {
      newPost: {
        title: '',
        content: '',
      },
    };
  },
  methods: {
    saveNewPost() {
      this.newPost.addedAt = dateformat(new Date(), 'dddd, mmmm dS, yyyy, HH:MM');
      axios.post('https://moj-projekt-3e564.firebaseio.com/posts.json', this.newPost)
        .then(() => {
          this.newPost = {
            title: '',
            content: '',
          };
        });
    },
  },
};

 

 

Zgodnie z projektem założonym w Firebase – link do bazy danych to: https://moj-projekt-3e564.firebaseio.com. Skąd jednak /posts.json? Jest to nasza kolekcja postów. Ze względu na to, że w bazie danych nie mamy kolekcji, która przechowuje posty – Firebase utworzy ją automatycznie (pod nazwą posts) i umieści obiekt, który wysłaliśmy, nadając mu unikalny klucz. Dodajmy zatem dwa posty korzystając z naszego komponentu i zajrzyjmy do konsoli Firebase.

 

firebase4

 

Działa! W naszej bazie danych są dokładnie dwa posty, które wysłaliśmy z aplikacji. Połowa sukcesu za nami. Teraz musimy je wyświetlić. Przygotujmy zatem komponent PostsList.vue:

<template>
  <div class="posts-list">
    <div class="panel panel-default post" v-for="post in posts" :key="post.id">
      <div class="panel-heading post__header">
        <h3 class="panel-title header__title">{{ post.title }}</h3>
        <small><span class="glyphicon glyphicon-calendar" aria-hidden="true"></span>{{ post.addedAt }}</small>
      </div>
      <div class="panel-body post__content">
        {{ post.content }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'PostList',
  data() {
    return {
      posts: [],
    };
  },
};
</script>

<style>
  .posts-list {
    margin-top: 25px;
  }

  .header__title {
    font-size: 28px;
    font-weight: bold;
  }
</style>

 

 

Póki co jeszcze nic się nie wyświetla. Musimy pobrać listę postów z bazy danych. Do tego celu wykorzystamy request typu GET. Co chcemy osiągnąć?

  • pobrać listę postów zanim wyświetlimy komponent
  • uzupełnić tablicę posts danymi pobranymi z bazy danych

Do pobrania listy postów przed wyświetleniem komponentu wykorzystamy właściwość komponentu Vue beforeMount. Ta metoda wywoływana jest po utworzeniu komponentu ale przed „zainstalowaniem” go w drzewie DOM. Cykl życia komponentu w Vue przedstawia poniższy schemat:

lifecycle

 

Logika komponentu powinna wyglądać tak:

import axios from 'axios';

export default {
  name: 'PostList',
  data() {
    return {
      posts: [],
    };
  },
  beforeMount() {
    axios.get('https://moj-projekt-3e564.firebaseio.com/posts.json')
      .then((response) => {
        Object.keys(response.data).forEach((key) => {
          const post = response.data[key];
          post.id = key;
          this.posts.push(post);
        });
      });
  },
};

 

Przejdźmy zatem na główną ścieżkę naszej aplikacji, czyli „/”:

posts

 

Sukces! Wyświetla się dokładnie to, co mamy w bazie danych. Pozostał ostatni komponent – PostDetails.vue. Komponent odpowiedzialny za wyświetlenie szczegółów danego postu. Zanim jednak to zrobimy, musimy zmodyfikować komponent PostsList.vue tak aby po kliknięciu w tytuł postu aplikacja przekierowała użytkownika do szczegółów postu.

Zmodyfikujmy szablon widoku:

<template>
  <div class="posts-list">
    <div class="panel panel-default post" v-for="post in posts" :key="post.id">
      <div class="panel-heading post__header">
        <h3 class="panel-title header__title" @click="goToPostDetails(post.id)">{{ post.title }}</h3>
        <small><span class="glyphicon glyphicon-calendar" aria-hidden="true"></span>{{ post.addedAt }}</small>
      </div>
      <div class="panel-body post__content">
        {{ post.content }}
      </div>
    </div>
  </div>
</template>

 

 

Metoda odpowiedzialna za przekierowanie do szczegółów postu to goToPostDetails(postId), która jako parametr przyjmuje unikalne id postu (klucz nadany przez Firebase).

methods: {
  goToPostDetails(postId) {
    this.$router.push({ name: 'Post', params: { id: postId } });
  },
},

 

 

Teraz przygotujmy szablon komponentu PostDetails.vue:

<template>
  <div class="post-details">
    <div class="panel panel-default post">
      <div class="panel-heading post__header">
        <h3 class="panel-title header__title">{{ post.title }}</h3>
        <small><span class="glyphicon glyphicon-calendar" aria-hidden="true"></span>{{ post.addedAt }}</small>
      </div>
      <div class="panel-body post__content">
        {{ post.content }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'PostList',
  data() {
    return {
      post: {},
    };
  },
};
</script>

<style>
  .posts-list {
    margin-top: 25px;
  }

  .header__title {
    font-size: 28px;
    font-weight: bold;
  }
</style>

 

 

Jeszcze nie wyświetlają się nam szczegóły postu. Musimy zatem dodać taką funkcjonalność. Podobnie jak w komponencie PostsList.vue wykorzystamy w tym celu metodę beforeMount z komponentu Vue. Jak wspomniałem wyżej, do pobrania danych z Firebase w tym przypadku wykorzystujemy API oparte o REST, zatem aby pobrać informację o pojedynczym poście z bazy danych musimy wywołać metodę typu GET dla https://moj-projekt-3e564.firebaseio.com/posts/{key}.json gdzie {key} to unikalny klucz nadany przez Firebase.

Logika komponentu PostDetails.vue wygląda tak:

import axios from 'axios';

export default {
  name: 'PostList',
  data() {
    return {
      post: {},
    };
  },
  beforeMount() {
    const postId = this.$route.params.id;
    axios.get(`https://moj-projekt-3e564.firebaseio.com/posts/${postId}.json`)
      .then((response) => {
        this.post = response.data;
      });
  },
};

 

 

 

Teraz po kliknięciu na dowolny tytuł postu zostaniemy przekierowani do jego szczegółów.

 

To już koniec na dzisiaj. Napisaliśmy prostą aplikację w stylu blog’a nie pisząc nawet jednej linijki dla części backendowej. Dodatkowo struktura bazy danych została zadeklarowana bezpośrednio z poziomu apikacji w Vue. Całość oczywiście znajdziecie na moim githubie tutaj -> vue-blog-app-example

To tylko mały fragment tego co potrafi Firebase. Wykorzystaliśmy jedynie bazę danych i API w oparciu o REST. Prawdziwa zabawa zaczyna się dopiero wtedy, gdy korzystamy z Firebase jako biblioteki po stronie frontendowej i łączymy się do platformy bezpośrednio. Dzięki temu dane z bazy są wysyłane do użytkownika zaraz po ich modyfikacji. O tym jednak kiedy indziej…

Miłej zabawy

 

PS. OK… trochę mnie poniosło z tym tytułem 😉