w Praktyce
10 godz. 44 min · Node.js · Full-stack i Programowanie
Piotr PalarzWeb DeveloperW kursie tym poznasz platformę Node.js od podstaw, aż po bardziej zaawansowane koncepcje. Zaczniemy od omówienia czym jest Node, a także jakie może być zastosowanie tej technologii. Następnie przejdziemy przez proces instalacji i napisanie swojego pierwszego skryptu. Już na samym początku dokładnie omówimy tworzenie własnych modułów, gdyż jest to wiedza niezbędna, by dobrze zrozumieć funkcjonowanie Node. Następnie dowiesz się czym są zdarzenia, jak działa model “publish / subscribe”, a także czym jest “event-driven development”. Chwilę później omówimy pracę z buforami, stream’ami, a także ze standardowym wejściem i wyjściem. Ta wiedza pozwoli Ci zrozumieć jak tworzone aplikacje mogą otrzymywać od użytkownika dane oraz jak je wyświetlają.
Niezwykle ważnym rozdziałem będzie ten, poświęcony pracy z systemem plików. Szczególnie dla dewelopera, pracującego dotychczas z językiem JavaScript wyłącznie w środowisku przeglądarki internetowej. Node pozwala bowiem tworzyć nowe pliki, zapisywać w nich dane oraz odczytywać i usuwać pliki i katalogi. Przy wszystkich tych operacjach, niezwykle ważna jest jednak asynchroniczna natura Node. Dowiesz się jak działa, a także jakie są jej zalety. Na tym etapie nie zabraknie oczywiście praktycznego projektu, którym będzie aplikacja typu CLI (Command Line Interface). Pozwoli nam ona grupowo zmienić nazwy wielu plików, według podanego wzorca.
W kolejnym rozdziale poruszymy fundamentalną dla Node kwestię, mianowicie tworzenie serwerów sieciowych. Zaczniemy od najniższego poziomu, tworząc prosty serwer bazujący na protokole TCP/IP. Wiedza w jaki sposób Node radzi sobie z takim zadaniem, pozwoli później lepiej zrozumieć wyższe warstwy, takie jak np. HTTP czy WebSockets. Chwilę później stworzymy swój pierwszy serwer HTTP, do którego będzie można wysyłać zapytania wprost z przeglądarki internetowej. Omówimy również jak w Node wykorzystać bezpieczne szyfrowanie HTTPS.Na tym etapie będziesz już miał solidną wiedzę jak pracować z Node, jednak zanim przejdziemy dalej, zatrzymamy się, by omówić jak dokładnie działa Node.js, a także jak ta platforma została zbudowana. Dwie dość obszerne lekcje, będą być może jednymi z najważniejszych w tym kursie. Dowiesz się w nich czym jest, a także jak działa jednowątkowa pętla zdarzeń, czym jest proces i wątek, a także zobaczysz kod źródłowy Node.js i wszystkie bloki budulcowe, z jakich Node się składa. To naszym zdaniem niezwykle ważny temat, często jednak pomijany w wielu materiałach. Dzięki dogłębnemu zrozumieniu jednowątkowej natury języka JavaScript i asynchroniczności Node, będziesz mógł tworzyć dużo lepsze aplikacje.
Node.js posiada kilkanaście wbudowanych modułów, z których w dowolnej chwili można skorzystać. Wszystkie jednak dają nam dużą, niskopoziomową kontrolę, ale czasami sporo kodu należy napisać, by zrealizować jakieś zadanie. Jest to jednak świadomy wybór twórców Node, gdyż całą resztę świetnych rozwiązań, dopisuje nieustannie ogólnoświatowa społeczność deweloperów. Zdecydowana większość z modułów ląduje w rejestrze npm. Jest to genialny package manager, który pozwoli nam wyszukiwać, pobierać i aktualizować niezliczoną ilość modułów. Dowiesz się zatem jak korzystać z npm, a chwilę później zaczniemy wykorzystywać zgromadzone tam moduły w dalszej pracy.Jednym z najpopularniejszych modułów jest express.js, który pozwala w bardzo prosty sposób tworzyć serwery HTTP. Grzechem byłoby zatem z niego nie skorzystać. Dowiesz się wszystkiego co niezbędne, by zacząć tworzyć własne aplikacje bazujące na protokole HTTP z użyciem express.js, a także kilku dodatków do tego systemu. Zobaczysz jak routować zapytania, jak korzystać z middleware czy serwować statyczne pliki. Następnie omówimy współpracę z bazą danych MongoDB i z jej wykorzystaniem stworzymy API typu REST. Stworzona aplikacja pozwoli odczytywać dane z bazy i przesyłać je do klienta w formacie JSON, a także dodawać nowe dane, aktualizować i usuwać je. Wszystko to zgodnie z dobrymi praktykami pracy z REST.
Chwilę później czeka Cię kolejny praktyczny projekt - skracacz linków. Stworzymy wspólnie aplikację, która pozwoli skrócić dowolny adres URL do krótkiej formy. Oczywiście będzie działać również w drugą stronę! Kiedy przejdziemy pod skrócony adres, przekieruje nas do odpowiedniej witryny. Node.js to platforma wręcz stworzona do tworzenia aplikacji typu real-time, dlatego w kolejnym rodziale podejmiemy temat technologii WebSockets. Dowiesz się jakie są jej zalety, a także jak pracować z nią po stronie serwera i klienta. Tutaj czeka nas kolejny praktyczny, niezwykle ciekawy projekt. Wykonamy bowiem czat grupowy. Aplikacja ta pozwoli dowolnej ilości użytkowników podłączyć się do czatu podając swój nick, a następnie wysyłać widoczne dla wszystkich wiadomości. Zaimplementujemy nawet takie rozwiązania jak wyświetlanie statusów o dołączeniu kogoś do czatu, a także o jego opuszczeniu.W przedostatnim rodziale tego kursu podejmiemy tematykę dobrych praktyk pracy z Node.js. Na początku omówimy najważniejsze konstrukcje nowej specyfikacji EcmaScript 2015, które są znakomicie wspierane w Node. Chwilę później omówimy jak korzystanie z Promises uprości, a także ulepszy nasz kod. Wśród dobrych praktyk nie zabraknie również informacji o debugowaniu aplikacji. Zobaczysz sprawdzone sposoby, by znaleźć błędy lub lepiej, krok po kroku, zrozumieć jak działa napisany wcześniej kod. Dowiesz się również jak pracować z błędami, by Twoje aplikacje działały w sposób przewidywalny.
Ostatni rozdział w całości został poświęcony temu, co zwykle jest pomijane w innych materiałach, mianowicie wdrażaniu aplikacji do produkcji. Przez cały ten kurs pracować będziemy lokalnie, lecz kiedy aplikacje są już gotowe, wypadałoby udostępnić je światu. Wdrażanie aplikacji napisanych z użyciem Node.js nie jest jednak tak oczywiste, jak np. wgrywanie WordPress’a u wybranego hostingodawcy. Ty będziesz miał jednak możliwość zobaczyć, jak wdrożyć napisany przez nas grupowy czat na platformie Heroku. Jest to bardzo popularny serwis działający jako PaaS (Platform as a Service). Za darmo będziesz mógł w ciągu kilku chwil uruchomić swoją aplikację.Zobaczysz o co należy zadbać, by wszystko poszło gładko. Serwisy typu PaaS dbają o bardzo wiele aspektów wdrażania i serwowania naszych aplikacji, takich jak bezpieczeństwo oraz nieustanną dostępność. Mają jednak pewne ograniczenia. Z tego powodu, dowiesz się również jak wdrożyć swoją aplikację na serwerze wirtualnym VPS z systemem Ubuntu Server. Takie rozwiązanie daje nam całkowitą kontrolę, ale także obarczone jest większą odpowiedzialnością. Zaczniemy od instalacji na serwerze platformy Node.js, systemu kontroli wersji GIT, a także innych niezbędnych modułów
Następnie zobaczysz jak skonfigurować swoje lokalne środowisko tak, by za pomocą GIT’a wysyłać kod do zdalnego serwera, a potem jednym poleceniem wdrażać go do produkcji. Dowiesz się również jak jednocześnie serwować wersję produkcyjną oraz developmencką. W rodziale tym poruszymy również inne kwestie, takie jak procesy potomne czy tworzenie klastrów. Dzięki tej wiedzy, będziesz mógł maksymalnie wykorzystać dostępne zasoby serwera. Na sam koniec tego kursu rzucimy okiem również na inne, nie przedstawione wcześniej zastosowania Node, a także nakreślimy dalszą drogę nauki w tym zakresie.
Kurs ten jest dla wszystkich osób, które dobrze czują się w technologiach frontendowych, tj. HTML, CSS i JavaScript, a teraz chcą rozpocząć swoją przygodę z back-endem. Im zatem lepiej znasz język JavaScript, tym więcej wyciśniesz z Node, natomiast nie jest wymagana bardzo zaawansowana wiedza z zakresu tego języka.
W tej lekcji omówimy obiekt proces, który już widziałeś. Jest on bardzo ciekawy i zawiera różne metody oraz
informacje. Możesz w dokumentacji zobaczyć go tutaj pod "Process".
Będę chciał przejść sobie do interfejsu REPL, który już znamy. W ten sposób. I wpisać process. Możesz zobaczyć, że od razu
pod tym obiektem bardzo wiele informacji i metod się znajduje.
Natomiast my na początek wyświetlimy sobie process ID, czyli PID.
Jest to identyfikator procesu przypisany mu przez system operacyjny. I gdybym sobie przeszedł do Activity Monitora
czy na Windowsie do Menedżera Zadań i tutaj byśmy sobie wyszukali node, to okaże się, że faktycznie taki proces
się tutaj znajduje i ma on dokładnie taki PID, jak zostało nam zwrócone w tym miejscu. I mógłbym sobie go można
powiedzieć "zabić". Otworzę nowe okno terminala i na Macu można wpisać takie polecenie jak kill i podać
numer procesu, który chcemy ubić. Więc wpiszemy sobie taki oto numer. Zobaczymy.
I teraz jeżeli przejdziemy tutaj, to widzimy, że ten proces się zakończył i mamy tutaj komunikat.
Więc jest to również ciekawa rzecz, że możemy sobie podejrzeć taki PID procesu. To może być przydatne później,
kiedy będziemy mówić o klastrowaniu na serwerze.
Ale chciałem ci pokazać, że możesz sobie to tutaj pod procesem sprawdzić.
Teraz przejdziemy już do pliku, który tutaj się znajduje, czyli index.js. I będę chciał do niego wstawić
taki oto kod.
Nie ma tutaj nic super zaawansowanego. W poprzedniej lekcji mówiłem ci, że stdout jest to stream, a skoro
jest to stream, to możemy na nim wpisać sobie coś. Więc napiszemy: "Podaj wiadomość" i tutaj nową linię.
Następnie do stdin, czyli standard input - w tym przypadku również nasz terminal - będziemy chcieli skorzystać
ze zdarzenia.
Znowu, jak widzisz, streamy dziedziczą z EventEmittera, ze zdarzenia readable.
Ono się wywołuje wtedy na streamie, kiedy już możemy zacząć odczytywać z niego jakieś informacje,
kiedy stream jest gotowy. Za każdym takim razem chcemy wywołać funkcję, która do zmiennej line zapisze nam to,
co odczytamy z terminala.
Czyli znowu tutaj odwołujemy się do process.stdin i do read. Ta metoda read jest metodą streamu, bo stdin jest
streamem. Czyli będziemy chcieli to odczytać. Jeżeli linijka nie będzie miała wartości null, to przejdziemy dalej.
Jeżeli nie, to zakończymy. I teraz to, co jest ciekawe, to line zwrócony dla nas to będzie buffer.
Po to właśnie mówiłem ci wcześniej o streamach czy bufferach, żebyś teraz na każdym etapie mógł je widzieć.
Dlatego skoro jest to buffer, to możemy metodą toString zamienić go na string, który my wpiszemy i następnie
metodą trim będę chciał usunąć jakieś spacje, które mogłyby być na początku czy na końcu i porównać
to sobie do close.
Po prostu mówimy tutaj, że jeżeli wpiszemy close, zaraz to zobaczysz, to chcę odwołać się do process.exit.
I process.exit jest o tyle ciekawy, gdybyśmy przeszli do tego interfejsu REPL,
że mogę odwołać się tutaj do process.exit i taką metodą zakończyć ten proces. Wcześniej pokazywałem ci,
że możemy w tym REPL-u wyjść w ten sposób. Jest to specjalna komenda, ale za pomocą process.exit również.
Natomiast jeżeli nie wpiszemy close, to chcę wyświetlić to, co wpisaliśmy jako buffer i następnie chcę to
wyświetlić za pomocą takiego oto komunikatu. Zauważ, że nie robimy console.log, chociaż moglibyśmy, ale
odwołały się do process.stdout.write. Console.log domyślnie też pod spodem korzysta z process.stdout, tylko
różni się tym, że na końcu dodaje nam jeszcze nowe linie.
A jak widzisz, w przypadku write na tym stdout musimy taką nową linię dodać tutaj sami.
Zobaczmy, jak to wszystko będzie wyglądać.
Tłumaczyłem, ale może zastanawiasz się, o co chodzi. Wpiszmy node index.
No i widzimy: "Podaj wiadomość", czyli wykonała się ta linijka i następnie na readable przypisaliśmy zdarzenie,
więc tutaj proces czeka, aż coś wpiszemy. Napiszemy "cokolwiek". Wciskam Enter i w tym momencie jakby przejdziemy
dalej w tym kodzie, czyli będziemy tutaj mieli zmienną line.
Ok.
I, jak widzisz, nie wpisaliśmy close, czyli przeszliśmy dalej. Line wyświetliło nam buffer, a pod spodem widzimy
komunikat "Wpisałeś: cokolwiek".
Jeszcze jedna ciekawostka.
Otóż tutaj przekazałem buffer. Jak widzisz, tu był buffer i on wygląda tak, a pod spodem przekazałem ten
sam buffer i został zamieniony na "cokolwiek".
Otóż w JavaScript jeżeli korzystamy z obiektów w takim kontekście jakby ze stringiem, czyli mieliśmy
string i próbujemy tutaj dodać jakiś obiekt, to JavaScript domyślnie będzie na takim obiekcie szukał
metody toString.
Jeżeli ją znajdzie, to ją wywoła. I to, co ona zwróci, zostanie w takim kontekście tutaj dodane.
A że metoda toString
wiemy - w tym miejscu to np. widzimy - że zwraca nam string odkodowany, bo kiedy ja go wpisałem, został zamieniony
na buffer, czyli na dane binarne,
to dlatego nam to działa.
I teraz znowu nam się pojawiła kolejna tutaj prośba o wpisanie czegoś. Czyli wpiszemy "coś" i znowu tak w nieskończoność
możemy coś z tymi danymi robić.
Natomiast jeżeli wpiszę close, to pojawimy się w tym miejscu
I ten proces się zakończy. Jak możesz zobaczyć, z niego wyskoczyliśmy.
W ten sposób możesz sobie stworzyć jakieś proste aplikacje, które będą np. zadawać jakieś pytania.
Ale jest to jeszcze prostsze z modułem readline, który omówimy w lekcji kolejnej. Chciałem ci jednak na tym
najniższym poziomie pokazać, jak możesz z tego korzystać i że znowu tutaj mamy streamy czy buffery, i że
process z tego korzysta.
Teraz chciałbym wykomentować kod, który tutaj mamy. I wstawić inny fragment, mianowicie będzie on wyglądał
w ten sposób.
Zauważ, że - może zrobię trochę miejsca - że mamy tutaj dodawanie zmiennej, która nie istnieje, czyli zmiennej
b nie ma. A tutaj zrobiłem console.log
"Program nadal działa".
Zobaczmy, czy tak się stanie. I jak widzimy - nie. Mamy błąd:
"b is not defined" i console.log się już nie pojawił.
Przy okazji widzisz jedną ważną rzecz, która jest w Node.
Otóż jeżeli pojawi nam się jakikolwiek błąd w naszym kodzie, to proces jest kończony.
Są tego poważne konsekwencje, szczególnie na serwerze, ale o tym będziemy mówić później.
Natomiast chciałem ci pokazać, że proces dziedziczy z EventEmittera, a więc możemy do niego przypisywać różne
zdarzenia. I te zdarzenia oczywiście znajdziesz w dokumentacji, tutaj, pod "Process".
Ale ja jedno z nich ci teraz tutaj wkleję.
Otóż za pomocą znanego nam już on przypisuję zdarzenie uncaughtException, czyli niewyłapany błąd, taki jak pojawił się przed
momentem. I przypisuję funkcję do obsługi takiego zdarzenia, która przyjmie tutaj error. Klasa error ma
zawsze w sobie message, więc możemy się do tego odwołać i zobaczyć, jaki błąd wystąpił.
Czyli zobaczmy, co teraz się stanie. Wywołajmy błąd.
I widzimy.
Wystąpił błąd:
"b is not defined".
W tym momencie nie mamy już tego komunikatu, który był przed momentem, ale mamy funkcję własną, która nam
pozwoliła obsłużyć taki oto błąd.
Nie zmienia to jednak faktu, że jak możesz zobaczyć, kolejna linijka tak czy siak się nie wykonała i proces
się zakończył.
W ten jednak sposób możemy ładnie pewne rzeczy wyczyścić. Np. gdybyśmy mieli jakieś otwarte połączenia
do bazy danych, to moglibyśmy je tutaj wyczyścić, zanim byśmy ten proces zamknęli.
Teraz pokażę ci jeszcze jedno ciekawe zdarzenie, które wywoła się zaraz przed tym, jak proces będzie kończony.
Czyli chciałbym sobie ten błąd, który jest tutaj, usunąć.
I wkleję teraz taki kod, odkomentuję. Jak możesz zobaczyć, zdarzenie nazywa się beforeExit i przypisujemy mu funkcję.
Co jest ciekawe, to ono wywoła się zaraz przed tym, jak proces będzie chciał zakończyć wykonywanie kodu, czyli
może zróbmy sobie najpierw taką postać i wstawię tutaj console.log. Wstawiłem dwa console.log.
Program wykonuje kod i program nie ma już nic do wykonania. Więc wykonajmy sobie teraz nasz skrypt. I widzimy
program wykonuje kod i drugi console.log. Czyli po tym console.log
program nie miał już nic do wykonania, dlatego proces się zakończył.
A zatem jeżeli wcześniej sobie przypiszemy zdarzenie beforeExit, to możemy opóźnić wywołanie się właśnie takiego
zdarzenia czy zakończenie procesu.
Dlatego tutaj wystawimy sobie console.log, że opóźniamy zamknięcie procesu o dwie sekundy i wykonam sobie
setTimeout, który po dwóch sekundach dopiero się wykona.
Więc zobaczmy. I widzimy, że program nie miał już nic do wykonania, ale opóźniliśmy jeszcze jego zamknięcie
w taki oto sposób.
I to jest właśnie też ciekawe, że możemy pewne rzeczy tutaj jeszcze zrobić, na przykład tak jak w przypadku
tego uncaughtException pozamykać jakieś połączenia z bazami danych. Natomiast mamy jeszcze drugie zdarzenie.
Tutaj to zanotowałem - takie, które nazywa się exit i ono różni się tym, że pozwala na wykonanie tylko synchronicznych
operacji. Czyli setTimeout nam tutaj nie zadziała, dlatego że jest to operacja asynchroniczna, a więc
taka, która odkłada wykonanie kodu na później. Asynchroniczne operacje poznasz jeszcze w tym kursie,
będzie to np. odczytywanie zawartości jakiegoś pliku. Więc exit nam tu nie zadziała, ale jeżeli będziemy
z beforeExit korzystać, to możemy. I to, co jest ważne,
tutaj nie skorzystałem z metody on, bo wyobraź sobie, że proces chce się zakończyć i ja mówię na zakończenie,
czyli przed zakończeniem, wykonaj tę funkcję.
Ona po dwóch sekundach zrobi console.log i znowu wykonałaby się ponownie, bo znowu proces chce się
zakończyć. Więc on nigdy by się nie zakończył, ale EventEmitter ma taką metodę jak once, która pozwala wykonać
pewne zdarzenie wyłącznie raz. Na sam koniec pokażę ci jeszcze jedną ciekawą właściwość procesu. To sobie
wykomentujemy. Ona będzie nam przydatna później, dlatego tylko krótko ją pokażę. Mianowicie zrobimy sobie
console.log i tutaj process.argv. Będziemy mogli tutaj zobaczyć wszystkie argumenty uruchomionego procesu.
I zawsze pierwsze dwa to będzie ścieżka do pliku wykonywalnego node i następnie ścieżka do pliku, w którym
w tym momencie się znajdujemy.
Natomiast jeżeli tutaj podałbym sobie jeszcze inne, mogę je dowolnie wpisać, np. często dodaje się to
z takimi pauzami, param - powiedzmy - jeden, to zauważ, że one się tutaj pojawią w tej tablicy.
Nie muszą one być z kreskami, możesz wpisać tutaj dowolne rzeczy i można z nich skorzystać.
Czyli wiedząc, że te dwa pierwsze nie są nam potrzebne, to możemy tutaj,
skoro jest to tablica, skorzystać z metody slice i dwa pierwsze wyciąć, a zwrócić resztę.
Zobaczmy. Jak widzisz, teraz będziemy mieli tablicę, w której będą parametry, które przekazaliśmy właśnie
do takiego node.
Mogę tutaj napisać param dwa równa się np. dwa. I z nich będziemy jeszcze później w tym kursie korzystać.
Ostatnia ciekawostką, którą mam jeszcze w zanadrzu, jest zdarzenie, które pozwoli nam przechwycić tzw.
signal interrupt. Teraz go wkleję i ci pokażę. Do procesu przypisuję sobie zdarzenie sigint.
Listę tych zdarzeń znajdziesz tutaj, na stronie procesu w dokumentacji. I mamy tutaj console.log("Naciśnij
CTRL+C, by zakończyć"). Czyli jak to będzie działać? Żeby nam to zadziałało, to musimy sobie opóźnić zakończenie
tego procesu. Bo jeżeli teraz go wywołam, może już bez tych parametrów,
to zauważ, że on się od razu zakończył. Ale aby go opóźnić, to będziemy chcieli tutaj zrobić jakiś
timeout. Zróbmy sobie pusty setTimeout.
Będzie to funkcja, która nic nie będzie robić, ale chciałbym, aby ona wykonała się po dziesięciu sekundach,
czyli w ten sposób.
I w tym czasie będziemy mogli proces zakończyć.
Czyli, jak widzisz, proces wisi, bo czeka.
I za pomocą Ctrl+C możemy taki proces normalnie zakończyć.
Natomiast gdybyśmy tego nie mieli, zapomniałem to wykomentować.
Ok.
Proces wisi, wciskam Ctrl+C i on się nam od razu zakończył. A jeżeli przypiszemy takie zdarzenie jak sigint,
to możemy zareagować na to, jak użytkownik wciśnie Ctrl+C w naszym programie, który właśnie byśmy napisali
w takim command line.
Czyli zauważ, że jeżeli to będzie, to w momencie jak ja wcisnę Ctrl+C, to wyświetli nam się console.log. Wciskam
Ctrl+C i mamy console.log. Jeżeli jeszcze raz wcisnę Ctrl+C, to dopiero wtedy to wszystko się zakończy.
Dlatego, że tutaj znowu nie przypisałem on, ale once, czyli tylko raz nam się taki console.log pojawi.
To może być przydatne, ale warto po prostu również wiedzieć, że coś takiego istnieje. A inne zdarzenia
tego typu, czyli takie systemowe wywołania, możesz znaleźć na stronie dokumentacji procesu.