kleindan.dev

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"

> package.json
 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 express

Zaczniemy 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.

> index.js
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.html

Na 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:

> index.js
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.

> public/index.html
<!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.

> public/css/style.css
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.

  1. Zainstalować EJS w naszej aplikacji używając komendy npm install ejs
  2. Dodać kod konfigurujący Express aby ten korzystał z ejs
  3. Stworzyć plik z szablonem ejs
  4. Napisać handler obsługujący odpowiednią ścieżkę korzystający z szablonu.

Po zainstalowaniu pakietu ejs przejdźmy do pliku index.js:

> 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:

Ostatnim krokiem będzie stworzenie szablonu w formacie ejs w domyślnym dla Express folderze views.

> views/categories.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>
    <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

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.

> views/categories.ejs
<%- 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.

> views/head.partial.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>
> views/foot.partial.ejs
  </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.