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ść!