Comment utiliser Firebase Firestore comme base de données

Un tutoriel pour configurer Firestore en tant que base de données, une solution très pratique à vos problèmes de stockage!

J'ai eu le besoin de créer un stockage pour certaines données pour monClub d'adhésion, l'endroit où j'enseigne la programmation.

Je voulais que mes utilisateurs puissent dire manuellement «J'ai terminé ce cours», en cliquant sur un bouton.

En gros, je voulais stocker un objet particulier pour chaque utilisateur.

Configurer Firebase

J'ai décidé d'utiliserFirebasepour cela, et en particulier leBase de données Firestoreils fournissent.

Le niveau gratuit est généreux, avec jusqu'à 1 Go de données stockées et 10 Go de transfert réseau par mois. Bien au-delà de mes estimations pour ce dont j'ai besoin!

Ouvrez le site Web de Firebase à l'adressehttps://firebase.google.com/

Firebase est un produit Google, donc une fois que vous êtes connecté à Google, vous êtes essentiellement également connecté à Firebase.

J'ai créé un nouveau projet Firebase en cliquant sur "Créer un projet"

Je lui ai donné un nom:

Et c'était ça:

J'ai cliqué sur l'icône "Web" à côté d'iOS et d'Android, et j'ai saisi le nom de l'application:

Et Firebase m'a immédiatement donné les clés d'accès dont j'avais besoin, ainsi qu'un exemple de code:

Juste après cela, Firebase m'a invité à ajouter des règles de sécurité pour la base de données.

Vous pouvez choisir 2 choses par défaut: ouvert à tous, ou fermé à tout le monde. J'ai commencé à m'ouvrir à tout le monde, quelque chose qu'ils appellentmode d'essai.

C'est tout! J'étais prêt à partir, en créant une collection.

Qu'est-ce qu'une collection? Dans la terminologie Firestore, nous pouvons créer de nombreuses collections différentes et attribuer des documents à chaque collection.

Un document peut alors contenir des champs et d'autres collections.

Ce n'est pas très différent des autres bases de données NoSQL, commeMongoDB.

Je recommande fortement de regarderla playlist YouTube sur le sujet, c'est très bien fait.

Alors j'ai ajouté ma collection, que j'ai appeléeusers.

Je voulais identifier chaque utilisateur à l'aide d'une chaîne spéciale, que j'appelleid.

Le code frontend

Nous arrivons maintenant à la partie JavaScript.

Dans le pied de page, j'ai inclus ces 2 fichiers, fournis par Firebase:

<script src="https://www.gstatic.com/
firebasejs/7.2.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/
firebasejs/7.2.1/firebase-firestore.js"></script>

puis j'ai ajouté unÉcouteur d'événement DOMContentLoaded, pour m'assurer que j'ai exécuté le code lorsque le DOM était prêt:

<script>
document.addEventListener('DOMContentLoaded', event => {

}) </script>

Là-dedans, j'ai ajouté la configuration Firebase:

const firebaseConfig = {
  apiKey: "MY-API-KEY",
  authDomain: "MY-AUTH-DOMAIN",
  projectId: "MY-PROJECT-ID"
}

J'ai passé cet objet àfirebase.initializeApp(), puis j'ai appeléfirebase.firestore()pour obtenir la référence à l'objet de base de données:

firebase.initializeApp(firebaseConfig)
const db = firebase.firestore()

Maintenant, j'ai créé un script pour remplir les ID utilisateur à partir d'une liste que j'avais dans mon backend, en utilisant une simple boucle:

const list = [/*...my list...*/]

list.forEach(item => { db.collection(‘users’).doc(item).set({}) })

..et je l'ai exécuté une fois, pour remplir la base de données. Je essentiellement par programmationcréé un document pour chaque utilisateur.

C'est très important, car une fois que j'ai créé un document, cela signifiait que je pouvais restreindre les autorisations pour ne mettre à jour que ces documents, et interdire d'en ajouter de nouveaux ou de les supprimer (ce que nous ferons plus tard)

Ok, donc maintenant, j'avais une logique complexe pour identifier l'ID utilisateur et l'ID du cours, dans lesquels je n'entrerai pas car ce n'est pas lié à notre tâche ici.

Une fois que j'ai compris cela, j'ai pu obtenir une référence à l'objet:

const id = /* the user ID */
const course = /* the course ID */
const docRef = db.doc(`membership/${id}`)

Génial! Maintenant, je pourrais obtenir la référence du document de Firebase:

docRef.get().then(function(doc) {
  if (doc.exists) {
    const data = doc.data()
    document.querySelector('button')
      .addEventListener('click', () => {
      data[course] = true
      docRef.update(data)
    })
  } else {
    //user does not exist..
  }
})

Ma logique était en fait beaucoup plus compliquée car j'ai d'autres pièces mobiles, mais vous voyez l'idée!

J'initialise les données du document en appelantdoc.data()et quand on clique sur le bouton, ce que je suppose que c'est le bouton pour dire «J'ai terminé le cours», nous avons associé letruevaleur booléenne de l'identifiant du club.

Plus tard, lors des chargements ultérieurs de la page de liste des cours, je peux initialiser la page et attribuer un cours si le cours a été terminé, comme ceci:

for (const [key, value] of Object.entries(data[course])) {
  const element = document.querySelector('.course-' + course)
  if (element) {
    element.classList.add('completed')
  }
}

Le problème des permissions

J'ai démarré Firebase en mode test, vous vous souvenez? Ce qui rend la base de données ouverte à tout le monde - tout le monde avec les clés d'accès, qui sont publiques et publiées dans le code livré au frontend.

Je devais donc faire une chose: décider du niveau d'autorisation autorisé.

Et je suis tombé sur un problème assez important.

Utilisation de la console Firebase, sousDes règles, nous pouvons couper la permission. Au départ, c'était la règle par défaut:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}

J'ai changé les règles enread, update, donc on ne peut mettre à jour qu'un document, pas en créer de nouveaux:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, update;
    }
  }
}

Mais je n'ai pas pu empêcher les gens d'utiliser l'API Firebase, désormais disponible gratuitement dans le navigateur, pour jouer et répertorier tous les autres documents de la collection - accéder aux fichiers d'autres personnes.

Bien que cela n'ait pas traité de données sensibles, il n'a pas été possible d'envoyer ce code.

Déplacer le code du frontend vers le backend via une API personnalisée

Le problème de l'autorisation était un bloqueur de route.

J'ai pensé à supprimer tout le code que j'avais, mais j'ai finalement compris que je pouvais masquer complètement tous les accès à l'API du navigateur et utiliser un service Node.js pour exécuter l'API Firebase.

Il s'agit également d'une méthode courante pour masquer les clés privées / secrètes requises par les services: cachez-les derrière un serveur que vous contrôlez.

Au lieu d'appeler Firebase depuis le navigateur, j'ai créé un ensemble de points de terminaison sur mon propre serveur, par exemple:

  • Poster à/coursepour définir un cours comme terminé
  • Poster à/datapour récupérer les données associées à l'utilisateur

et j'y accède en utilisant leRécupérer l'API:

const options = {
    method: 'POST',
    headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
    },
    body: JSON.stringify({ id, course, lesson })
}
const url = BASE_URL + '/lesson'
fetch(url, options).catch(err => {
    console.error('Request failed', err)
})

Toute la logique avec les clics de bouton et ainsi de suite reste dans le code côté client, bien sûr, je viens de déplacer la logique Firebase.

Du côté du serveur Node.js, j'ai installé lefirebasepaquet utilisantnpm install firebaseet en avait besoin:

const firebase = require('firebase')

J'ai mis en place unExpressserveur à utiliserCORSet j'ai initialisé Firebase:

const firebaseConfig = {
  apiKey: process.env.APIKEY,
  authDomain: process.env.AUTHDOMAIN,
  projectId: process.env.PROJECTID
}

firebase.initializeApp(firebaseConfig) const db = firebase.firestore()

Ensuite, le code est exactement comme celui que j'ai utilisé dans le frontend, sauf qu'il se déclenche maintenant sur les appels de point de terminaison HTTP. C'est le code qui renvoie un document spécifique de notre collection

const getData = async (id) =>  {
  const doc = await db.doc(`membership/${id}`).get()
  const data = doc.data()
  if (!data) {
    console.error('member does not exist')
    return
  }
  return data
}

app.post(’/data’, cors(), async (req, res) => { const id = req.body.id if (id) { res.json(await getData(id)) return } res.end() })

et voici l'API pour définir un cours comme terminé:

const setCourseAsCompleted = async (id, course) => {
  const doc = await db.doc(`membership/${id}`).get()
  const data = doc.data()
  if (!data) {
    console.error('member does not exist')
    return
  }
  if (!data[course]) {
      data[course] = {}
  }
  data[course]['done'] = true
  db.doc(`membership/${id}`).update(data)
}

app.post(’/course’, cors(), (req, res) => { const id = req.body.id const course = req.body.course if (id && course) { setCourseAsCompleted(id, course) res.end(‘ok’) return } res.end() })

C'est fondamentalement ça. Il y a plus de code requis, pour gérer une autre logique, mais l'essentiel de Firebase est celui que j'ai publié. Maintenant, je suis également en mesure d'ajouter un utilisateur pour mon service côté serveur, de limiter tous les autres accès à l'API Firebase et d'en renforcer la sécurité.


Plus de tutoriels sur les services: