Pierwsze kroki z frameworkiem Express
Na tej lekcji użyjemy po raz pierwszy frameworku Express i zaczniemy tworzyć prostą aplikację, która będzie oferować użytkownikom zestawy fiszek do nauki np. słówek.
Wideo towarzyszące
Frameworki backendowe
Dla bardzo prostych aplikacji korzystanie z narzędzi wbudowanych w Node.js może byłoby wystarczające, ale jeżeli zależy nam na szybszym rozwoju aplikacji, dobrze jest sięgnąć po narzędzia przygotowane przez innych developerów, którzy już nie jedną aplikację stworzyli sami i wypracowali sobie wygodne metody pracy. Dodatkową zaletą jest fakt, że jest spora szansa, że inni programiści tworząc swoje programy już napotkali wiele bugów, na które my byśmy się natknęli i rozwiązali je za nas.
Takie narzędzia czy zestawy narzędzi są często nazywane frameworkami (ang. rusztowaniami) i jest to bardzo adekwatna nazwa, bo o ile takie rusztowania nie rozwiązują za nas wszystkich problemów i nie realizują potrzeb użytkowników naszej aplikacji, o tyle pozwalają nam samym wygodnie pracować nad tymi zadaniami.
Framework Express
Przez resztę zajęć będziemy pracować z frameworkiem Express, więc będziemy mieli sporo czasu, żeby się z nim zaznajomić.
Express jest już niemłodym rozwiązaniem, ale to nie powód, żeby go przekreślać. To nadal narzędzie, które pozwala na bardzo szybkie tworzenie backendowych API. Prócz tego sam framework jest dość prosty w konstrukcji i nie stara się narzucać programistom rozwiązań, ale za to ma bogatą bibliotekę dodatkowych narzędzi, z których możemy korzystać przy pomocy narzędzia npm.
Pierwszy serwer Express
Zaczniemy stworzenia nowego folderu projektowego i zainstalowania w nim framworku Express
> mkdir ~/express-flashcards # tworzymy katalog "express-flashcards w katalogu domowym"
> cd ~/express-flashcards # wchodzimy do nowoutworzonego katalogu
> npm init --yes # stworzenie świeżego pliku package.json
> code -r . # otwórz obecny folder w VSCode (można też w VS Code wybrać File > Open Folder)Nie zapomnij dodać w package.json pola "type": "module"
1{
2 "name": "first-server",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "type": "module",
7 "scripts": {
8 "test": "echo \"Error: no test specified\" && exit 1"
9 },
10 "author": "",
11 "license": "ISC"
12}Następnie instalujemy framework Express komendą:
> npm install expressZaczniemy dość standardowo od stworzenia serwera typu “hello world” przy pomocy frameworku Express. W tym celu stwórz nowy plik index.js i umieść w nim następujący kod.
import express from "express";
const port = 8000;
const app = express(); // Tworzymy obiekt aplikacji/serwera Express
app.get("/", (req, res) => { // Definiujemy handler dla metody GET ścieżki "/"
res.set("Content-Type", "text/plain"); // Zwracamy czysty tekst
res.send("Hello world"); // Wysyłamy tekst hello world
});
app.listen(port, () => { // Startujemy serwer na porcie 8000
console.log(`Server listening on http://localhost:${port}`);
});Funkcje obsługujące żądania dla danej ścieżki/metody najczęściej nazywamy handlerami od angielskiego handle (obsługiwać). Express oferuje nam dość wygodny interfejs definiowania handlerów w postaci metod app.method('path', (req, res) => { /* handler function */ }, gdzie method może być dowolną wspieraną metodą HTTP, czyli get, post, put, delete, itd.
Uruchom serwer i upewnij się że działa, jak należy. Możesz w tym celu użyć przeglądarki i wbudowanych w nią narzędzi deweloperskich albo programu curl. Czy serwer zwraca odpowiednie błędy dla nieznanych ścieżek bądź nieobsługiwanych metod?
Odtworzenie funkcjonalności pierwszego projektu
W poprzedniej lekcji w ramach samodzielnej pracy stworzyliśmy prosty serwer WWW obsługujący ścieżkę główną i ścieżkę domyślnie zawierającą ikonę nasze strony.
Odtworzenie tych funkcjonalności w frameworku może wyglądać bardzo podobnie do naszej implementacji, albo możemy wykorzystać dodatkowe funkcjonalności oferowane nam przez framework Express. Najpierw stwórz odpowiednie pliki w podkatalogu public. Po tej operacji Twój katalog roboczy powinien wyglądać następująco:
> tree -I node_modules
.
├── index.js
├── package.json
├── package-lock.json
└── public
├── favicon.ico
└── index.htmlNa maszynie wirtualnej możesz użyć w terminalu polecenia tree aby szybko zweryfikować zawartość folderu w którym się znajdujesz. To jedno z dostępnych narzędzi pozwalających na szybką wizualizację plików i folderów, na których pracujemy.
Następnie w pliku index.js zmodyfikuj kod usuwając handler ścieżki głównej i zastępując go odpowiednio tak, aby Twój kod wyglądał następująco:
import express from "express";
const port = 8000;
const app = express();
app.use(express.static("public")); // "magiczna" obsługa ścieżek dla plików z katalogu public
app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`);
});Metoda express.static() pozwala na wysyłanie do klientów tzw. plików statycznych, czyli zawartości która jest zależna tylko od ścieżki żądanej przez klienta i nie zmienia się dla różnych użytkowników aplikacji. Są to zazwyczaj zdjęcia, pliki CSS, pliki z kodem JS, itd.
Uruchom serwer i sprawdź, czy serwer zachowuje się tak, jak się tego spodziewasz, czyli czy realizuje te same funkcjonalności, co Twój pierwszy projekt.
Dodawanie pliku CSS do aplikacji
Warstwa wizualna naszych aplikacji ma duże znaczenie dla użytkowników. Nawet bardzo przydatna, szybka aplikacja nie będzie nic warta, jeżeli przez swój wygląd będzie utrudniać życie swoim użytkownikom. Nie będę udawać, że jestem grafikiem albo specjalistą od user experience (UX) i projektowanie interfejsów użytkownika jest dla mnie łatwe czy naturalne. Jednocześnie stworzenie czytelnego interfejsu dla prostej aplikacji powinno być w naszym zasięgu.
Aby przeglądarka załadowała arkusz stylów dla naszej strony, dodamy do pliku HTML odpowiedni element link, wskazując na nieistniejący jeszcze plik CSS.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fiszki</title>
<link rel="stylesheet" href="/css/style.css" />
</head>
<body>
<main>
<h1>Fiszki do nauki!</h1>
<p><a href="/cards/categories">Tutaj</a> umieszczę moje fiszki</p>
</main>
</body>
</html>A następnie dodamy prosty plik CSS, żeby sprawdzić, że rzeczywiście nasza aplikacja robi to, co powinna.
html {
background-color: #830;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 11pt;
}
body {
display: flex;
justify-content: center;
min-height: 100vh;
}
main {
background-color: #ded;
width: 70rem;
padding: 1rem;
border-radius: 0.5rem;
}Uruchom serwer i sprawdź, czy strona ładuje się poprawnie i czy faktycznie strona jest wystylizowana. Jeżeli masz ochotę, dostosuj arkusz stylów do swoich preferencji, ostatecznie to Ty tworzysz tę aplikację, więc możesz równie dobrze zadbać o to, żeby podobała się ona Tobie.
Widoki dynamiczne
Kolejnym krokiem rozwoju naszej aplikacji będzie dynamiczne generowanie strony HTML w odpowiedzi na żądanie klienta. Tak wygenerowana odpowiedź musi odzwierciedlać obecny stan naszej aplikacji, co oznacza, że nie możemy po prostu zwrócić pliku HTML odczytanego z dysku. Moglibyśmy “na piechotę” tworzyć w pamięci naszej aplikacji długi string, który przedstawiałby tekst odpowiedzi naszego serwera, ale lepiej będzie użyć w tym celu wyspecjalizowanego narzędzia.
Framework Express ma wbudowaną obsługę generowania dynamicznie odpowiedzi w formacie HTML. Jest to realizowane przez tak zwany silnik widoków (ang. view engine). Użycie view engine wymaga dodatkowej konfiguracji i instalacji zewnętrznego silnika, który pozwoli na definiowanie szablonów plików HTML, który następnie wypełnimy danymi. Proces wypełniania szablonu danymi nazywamy “renderowaniem widoku”.
Użyjemy w naszej aplikacji narzędzia ejs który pozwala definiować pliki-szablony w języku bardzo zbliżonym do HTML, zawierające elementy, które następnie podczas generowania odpowiedzi będziemy mogli podmienić na wartości z naszej aplikacji. Może łatwiej będzie nam pokazać ten proces, niż o nim opowiadać.
Aby użyć ejs w naszej aplikacji, musimy wykonać kilka kroków.
- Zainstalować EJS w naszej aplikacji używając komendy
npm install ejs - Dodać kod konfigurujący Express aby ten korzystał z
ejs - Stworzyć plik z szablonem
ejs - Napisać handler obsługujący odpowiednią ścieżkę korzystający z szablonu.
Po zainstalowaniu pakietu ejs przejdźmy do pliku index.js:
import express from "express";
const port = 8000;
const card_categories = ["j. angielski - food", "stolice europejskie"];
const app = express();
app.set("view engine", "ejs");
app.use(express.static("public"));
app.get("/cards/categories/", (req, res) => {
res.render("categories", {
title: "Kategorie",
categories: card_categories,
});
});
app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`);
});Omówienie zmian w pliku:
- W linii 8 informujemy Express, że jeżeli będziemy chcieli renderować odpowiedź HTML powinien użyć narzędzia EJS
- W linii 13 wywołujemy funkcję renderującą widok HTML.
- Pierwszy argument określa nazwę widoku, co bezpośrednio przekłada się na nazwę pliku, w którym powinien być szablon EJS. Domyślnie Express szuka plików widoków w folderze
views. W tym przypadku wskazujemy na plikviews/categories.esj. - Drugi argument to obiekt JS zawierający dane, które powinny się znaleźć w szablonie.
- Pierwszy argument określa nazwę widoku, co bezpośrednio przekłada się na nazwę pliku, w którym powinien być szablon EJS. Domyślnie Express szuka plików widoków w folderze
- W linii 4 dodaliśmy tablicę, która chwilowo będzie udawać bazę danych dla naszej aplikacji.
Ostatnim krokiem będzie stworzenie szablonu w formacie ejs w domyślnym dla Express folderze views.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css" />
</head>
<body>
<main>
<h1><%= title %></h1>
<ul id="categories">
<% categories.forEach(function(category){ %>
<li><%= category %></li>
<% }) %>
</ul>
</main>
</body>
</html>Pliki EJS są bardzo podobne do plików HTML, definiują natomiast dodatkowe elementy. W tym przypadku użyliśmy dwóch tagów definiowanych przez EJS
- W liniach 6, 11 i 14 użyliśmy tagów
<%= %>, który uruchamia kod JS w środku tagu i wstawia uzyskaną wartość w miejsce tagu - W liniach 13 i 15 użyliśmy tagów
<% %>które wykonują zawarty w środku kod JS, ale nie generują żadnego wyjścia. W tych miejscach w wynikowym HTMLu nie powinny pojawić się żadne znaki.
Podział szablonów na mniejsze fragmenty
Nasza aplikacja będzie się niebawem rozrastała i na pewno przybędzie nam więcej widoków do obsłużenia. Mimo że widoki są osobne, będziemy chcieli zachować spójność pod względem funkcjonalności i wyglądu dla naszej całej aplikacji. W przeciwnym wypadku, jeżeli na jakimś etapie postanowimy coś zmienić w częściach wspólnych plików HTML, będziemy musieli powtórzyć tę pracę wielokrotnie. Aby tego uniknąć możemy już na tym etapie podzielić nasz plik na mniejsze części.
<%- include("head.partial.ejs") %>
<main>
<h1><%= title %></h1>
<ul id="categories">
<% categories.forEach(function(category){ %>
<li><%= category %></li>
<% }) %>
</ul>
</main>
<%- include("foot.partial.ejs") %>W liniach 1 i 12 użyliśmy kolejnego rodzaju tagu EJS <%- %>, który zachowuje się bardzo podobnie do tagu <%= %>, z tą różnicą, że nie podmienia znaków zarezerwowanych dla języka HTML na bezpieczne odpowiedniki, jak np. ‘>’ na ‘>’. Użyta w tych tagach funkcja include odczytuje zawartość pliku w formacie EJS i renderuje go jako szablon HTML.
A nasze fragmentaryczne szablony po prostu będą zawierały resztę oryginalnego szablonu EJS.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css" />
</head>
<body> </body>
</html>Dzieląc nasze widoki w ten sposób, jeżeli postanowimy na przykład dołożyć dodatkowy plik CSS do wszystkich naszych widoków, będziemy musieli zmodyfikować tylko jeden plik.
Podsumowanie
Dziś postawiliśmy pierwszy serwer używająć frameworka Express i dodaliśmy do niego obsługę generowanej dynamicznie zawartości. Na następnych lekcjach nauczymy się korzystać z dodatkowych bibliotek typu middleware i rozbudujemy naszą aplikację o dodawanie i edycję elementów po stronie serwera.