27-08-2020 | Programowanie
Serwer w Node.js – Firebase [część 3]
Tagi: 

Witam, dziś kolejna część przygód dzielnego frontend developera. Przypomnę w skrócie co się wydarzyło do tej pory. Otóż stanąłem przed misją postawienia serwera na node.js. Są już nawet pierwsze efekty, serwer stoi i działa a do tego napisany jest w typsciprcie. Dokonałem także małego odkrycia: mój serwer nie potrzebuje webpacka (tak wiem, mogło to być oczywiste, ale jakoś tak z rozpędu ….. itd). Wystarczy dobrze skonfigurowany typescript i wszystko pięknie się kompiluje. Więc usunąłem webpacka z projektu jak również wszystkie jego zależności loadery i typ podobne. Ogólnie jest pięknie 🙂

W poprzednim wpisie wspominałem, że w następnym kroku (czyli dziś) chcę dodać routing po stronie serwera. Ale jak to bywa w IT-owym życiu trzeba być agile i dostosować się do zmian. Zmiany te zachodzą głównie w mojej głowie. Bo w sumie, na ten moment routing mam napisany po stronie aplikacji i działa bardzo fajnie, więc temat ten na razie zostawię a skupię się na czymś innym. Zagadnienie które wziąłem na warsztat jest API oraz Firebase. Połączenie tych dwóch abstrakcji pozwoli mojemu serwerowi czytać dane z bazy (do tej pory robiła to sama aplikacja). Aplikacja będzie za pomocą API odpytywać o dane

 a serwer będzie zwracał selektywnie wybrane najważniejsze dane. Normlanie security +10. 

Ok to czas zacząć. Na początek dodanie Firebase SDK do projektu:

npm install firebase-admin --save

potem w pliku app.js dodaje

import admin from "firebase-admin";

na stronach Firebase jest bardzo fajnie opisane sposób instalacji i dodawania dostępów. Tam naprawde jest wszystko opisane i przedewszystkim aktualne, więc nie ma sensu abym tutaj kopiował opisy stamtąd, po prostu jeżeli ktoś chce się dowiedzieć więcej to link jest >tutaj< 

W rezultacie mam na moim serwerze działający pakiet Admin Firebase, można więc tworzyć endpointy. Pierwszy endpoint będzie ustawiał rezerwacje na książkę. Koncepcja jest taka, że jak aplikacja zrobi requesta pod adres /api/book/send to nastąpi rezerwacja książki na bazie. Ok, na początek dodałem do serwera routing obsługi requesta

import bookRouter from "./routes/book";
app.use('/api/user', bookRouter);

sam router na tem moment ma dodaną jedna ścieżką:

import {Router} from 'express';
import {reservation} from "../controllers/reservation";
 
const router = Router();
router.post('/send',reservation)
 
export default router;

czyli mam wszystko przygotowane, pozostaje tylko napisać funkcję która realnie obsłuży request. Ponieważ pisze w typescript to wszelkie funkcje itp muszą mieć zdefiniowane typy, powiem szczerze, że na początku sprawiło mi to trochę problemów, co z czym połączyć i jakim typem. Lecz po chwili to wszystko ułożyło mi się w głowie i mogłem swobodnie dalej pisać kod. W requeście wykorzystuje dane przekazane z aplikacji, więc dodałem body-parser oraz typy do niego, aby łatwiej było odczytywać informacje oraz budować odpowiedzi serwera.

npm install --save body-parser
npm i @types/body-parser

W następnym kroku utworzyłem funkcję “reservation”. Funkcja ta wyszukuje książkę o podanym id w bazie i dodaje ją do rezerwacji użytkownika. Tak to wygląda w teorii, a w praktyce? Utworzyłem interface książki dla danych przychodzących z bazy:

export interface Book{
 title:string,
 author:string,
 type:string,
 bookID:string,
 pages:number,
 issn:string
 }

następnie odpytałem bazę o książki, a w szczególności o tą jedną która mnie interesuje. Firebase nie wspiera zapytań “where” więc serwer odpytuje bazę o wszystkie książki a następnie stara się znaleźć tą właściwą (będę musiał to zoptymalizować jeszcze). Chciałem aby apka poczekała na rezultat tej operacji dlatego użyłem await / async. Moja funkcja pomocnicza zwraca Promise  typu bookType który został utworzony na podstawie typu interface’u “Book”

import {Book} from '../model/book;
 
type bookType = Book | undefined;
 
export  const getBookById = async  (id: string):Promise<bookType>  => {
 const db = admin.database();
 let book: bookType ;
 await db.ref("/book").once("value", function (snapshot) {
   const allBook: Book[] = []
   snapshot.forEach(childSnapshot => {
     allUsers.push({
       id: childSnapshot.key,
       ...childSnapshot.val()
     });
   });
   book = allBook.find(item => { return item.bookID === id; });
 
 })
 return book;
}

jeżeli książka istnieje to jest dodana do użytkownika za pomocą kolejnej funkcji pomocniczej, ta funkcja jest typu Promise<void> bo defacto nie zwraca żadnych danych.

export  const addReservationToUser = async  (userID:string,bookID:string):Promise<void>  => {
 const db = admin.database();
 await db.ref(`users/${userID}/reservations/${bookID}`)
   .set(status)
   .then(() => {
     return true;
   })
   .catch(error => {
     throw new Error("Can't set reservation");
   });
};

Teraz to wszystko trzeba ze sobą połączyć. @types/express zapewnia nam typowanie requestów. tj “RequestHandler”, co niezwykle usprawnia pracę:

type bookType = Book | undefined;
 
export const reservation:RequestHandler = async  (req, res, next) => {
 const bookID = (req.body as {bookID:string}).bookID;
 const userID = (req.body as {userID:string}).userID; 
 
     const  book:bookType = await  getBookById(bookID)
 
     if(book){
       await addReservationToUser(book.bookID,userID)
     }
     res.json({status:200, msg:'Done'})  
}

Szybkie testy za pomocą Postman pokazują, że rozwiązanie działa 🙂 No to pozostało mi tylko z aplikacji “strzelić” do mojego serwera. Za pomocą Axiosa stworzyłem zapytanie do serwera, odpaliłem serwer oraz aplikacje lokalnie. Kliknąłem magiczny guzik na interfejsie aby zobaczyć moje rozwiązanie w praktyce i ….. bang!!!! cors origin policy error!! Aaaaa, normalnie trochę mi ciśnienie skoczyło, biorąc pod uwagę, że był już środek nocy (a nawet już długo po środku nocy ) a ja chciałem tym testem zakończyć pracę na ten dzień, nie był to komunikat który chciałem zobaczyć 🙂

Na szczęście na stronie express znalazłem szybkie rozwiązanie, które pozwoliło mi przeskoczyć to issue. Wiem, że w późniejszym czasie będę musiał się bardziej pochylić na CORS’em, ale na ten moment to wystarczy.

app.use(function(req, res, next) {
 res.header("Access-Control-Allow-Origin", "http://localhost:8080");
 res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
 next();
});

Więc jeszcze raz odpaliłem serwer, na apce kliknąłem guzik i …. ta da!!! działa 🙂 zapytania wykonują się poprawnie, na bazie zachodzą przewidywane zmiany, ogólnie jest super 🙂

To był pierwszy z endpointów na tym serwerze, następne kilka dni poświęcę na optymalizację ich pod moją aplikację, ale globalny kontekst jest poprawny. Odkryłem też jedną jeszcze rzecz, zmiany w kodzie na serwerze wymagają za każdym razem restartu serwera, robiąc to ręcznie można szału dostać, dlatego polecam “nodemon” jest to paczka która “nasłuchuje” zmian w kodzie i automatycznie restartuje serwer za nas, bardzo pomocne 🙂

npm install nodemon --save-dev

wystarczy dodać jeden wpis w konfiguracji:

"start-dev": "nodemon ./dist/app.js"

teraz za pomocą npm run start-dev uruchamiamy nasz serwer w developerskim trybie automatycznego restartu, naprawdę super sprawa.

To tyle treści na dziś. Mam nadzieję, że komuś to kiedyś pomoże zawalczyć w własnym serwerem w node.js. W najbliższym czasie skupię się na optymalizujących w istniejącym kodzie, oraz na rozszerzeniu API. Za jakiś czas wrócę z wpisem o jeszcze paru funkcjonalnościach na serwerze a potem z wpisem o finalnym produkcie którego dotyczy ten serwer. Jeżeli podobała Ci się ta seria, lub chcesz dać mi jakiś feedback, to napisz komentarz pod wpisem, lub na linkedin lub w prywatnej wiadomości. Ja tymczasem udaję się na urlop 🙂 

Cześć!