Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!

JSON Server – asynchroniczny JavaScript z lokalnym API

Twórz imponujące projekty z lokalnym serwerem!

Praca z API nie polega tylko na pobieraniu informacji z serwera. Jeśli jeszcze nie próbowałeś tworzyć, aktualizować i usuwać danych, to czas na JSON Server. Znasz już to narzędzie? Czytaj dalej – w artykule zapoznasz się z dodatkowymi możliwościami, jakie daje.

Spis treści

 Co to jest JSON Server

To narzędzie, dzięki któremu możesz zasymulować back end dla aplikacji czy strony internetowej. Wykorzystamy go do stworzenia serwera lokalnego (choć można go postawić też na serwerze zdalnym, lecz na potrzeby nauki wystarczy nam pierwsza opcja). Mówiąc prościej: w zakresie nauki programowania JSON Server pozwoli Ci przećwiczyć asynchroniczny kod (np. JavaScript) i komunikację z serwerem przez API, czyli: pobieranie, tworzenie, modyfikowanie i usuwanie danych.

Przykładowo możesz dzięki niemu stworzyć projekt do portfolio, który będzie np. panelem administratora strony restauracji. Umożliwisz w nim dodawanie i usuwanie pozycji z menu oraz ich edycję, a informacje zapiszesz na „serwerze”. Drugim elementem projektu mogłaby być strona restauracji, która wyświetla menu: czyli stosuje pobieranie danych z serwera.

JSON Server daje więc większe możliwości nauki obsługi API niż korzystanie z rozwiązań ogólnodostępnych (np. API pogodowego), gdyż takie API publiczne umożliwiają jedynie pobieranie danych.

 Jak zaprezentować projekt, który korzysta z JSON Servera?

W tej chwili GitHub Pages nie umożliwiają podglądu na żywo działania takich stron (nie ma tam możliwości uruchomienia JSON Servera), dlatego w opisie projektu w README powinieneś zawrzeć informacje o instalacji niezbędnych paczek potrzebnych do jego uruchomienia.

Jednak czy rekruter będzie chciał ściągać projekt na swój dysk? Aby uniknąć ryzyka, że w ogóle nie zapozna się z projektem, polecam nagrać filmik prezentujący działanie aplikacji lub strony, o czym pisałem w artykule „Jak ulepszyć README projektu”.

 Instalacja JSON Servera i przygotowanie bazy danych

 Instalacja Node.js, npm i JSON Servera

JSON Server to moduł (paczka) npm, który zainstalujesz bez problemu, jeśli masz już na swoim dysku Node.js i manager pakietów npm – instaluje je się „za jednym zamachem” ze strony Node.js.

Przejdźmy teraz do repozytorium i dokumentacji JSON Servera. Gdy masz już zainstalowany Node.js i npm, stwórz nowy katalog projektu i otwórz go w edytorze kodu (polecam VS Code). Uruchom wbudowany terminal (w VSC wybierz View -> Terminal lub skrót klawiszowy Ctrl+`) i wpisz w nim komendę instalacji JSON Servera:

npm install -g json-server

Dzięki fladze -g zainstalujesz JSON Server globalnie, czyli będziesz mieć do niego dostęp we wszystkich innych projektach. Zdecydowanie polecam więc to rozwiązanie.

 Baza danych, czyli plik JSON

Format JSON doskonale sprawdza się jako magazyn informacji i jest powszechnie wykorzystywany do zapisu i wymiany danych. Przypomina on obiekt, toteż nie powinieneś mieć problemu z odwoływaniem się do konkretnych wartości. Do operacji na danych przyda się też znajomość metod tablicowych.

Uwaga: cały kod omawianego rozwiązania znajdziesz w tym repozytorium.

W katalogu projektu w VS Code utwórz plik data.json – to będzie nasza baza danych (nazwa pliku .json może być inna, lecz pamiętaj, by potem użyć tej nazwy w komendzie poniżej).

Wklej do powyższego pliku kod:

{
    "products": [
        {
            "id": 1,
            "title": "Jaką drogę trzeba przejść, by zostać programistą (od zera)",
            "description": "W czasach nowoczesnych technologii i wszechobecnej automatyzacji wiele osób decyduje się na naukę programowania w celu utrzymania się na rynku pracy lub osiągnięcia wyższych dochodów. Niestety spora część z nich nie jest przygotowana na to co czeka je po drodze, a inna część nie jest pewna, od czego zacząć.",
            "authorId": 2,
            "categoriesId": [1, 2],
            "price": 0
        },
        {
            "id": 2,
            "title": "HTML i CSS: Podstawy",
            "description": "Podstawowa znajomość HTML-a i CSS-a jest niezbędna do rozpoczęcia nauki programowania w JavaScript. Dlatego poznamy solidne podstawy semantycznego kodu HTML, który jest niezbędny w prawidłowym budowania struktury strony internetowej.",
            "authorId": 1,
            "categoriesId": [1],
            "price": 69
        },
        {
            "id": 3,
            "title": "HTML i CSS: Responsywność",
            "description": "Obecnie ruch na stronach internetowych w większości generują urządzenia mobilne. Dla wybranych witryn wspomniana miara może sięgać nawet 90%. Dlatego nauka RWD (ang. Responsive Web Design) powinna być dla Ciebie priorytetem.",
            "authorId": 1,
            "categoriesId": [1],
            "price": 69
        },
        {
            "id": 4,
            "title": "JavaScript: Podstawy",
            "description": "Znasz już HTML i CSS? Chcesz zrobić następny krok? Chcesz poznać zmienne, typy danych - tablice, obiekty, operatory, instrukcje warunkowe, funkcje, pętle for czy while, konstruktory itd.? Ten materiał pozwoli Ci poznać teorię JavaScript wraz z praktycznymi przykładami, które potem będziesz mógł wykorzystać w dalszej nauce.",
            "authorId": 1,
            "categoriesId": [2],
            "price": 69
        },
        {
            "id": 5,
            "title": "Kurs JavaScript od podstaw",
            "description": "Jeśli nie wiesz jakich tematów się uczyć, aby nie marnować czasu na elementy niepotrzebne, to znajdziesz odpowiedź w tym kursie. Ta seria e-booków zawiera zagadnienia dla osób początkujących takie jak HTML i CSS oraz bardziej zaawansowane z JavaScript jak np. obsługa formularzy czy API. Docelowo będziesz potrafił tworzyć rozbudowane strony internetowe!",
            "authorId": 1,
            "categoriesId": [1, 2],
            "price": 499
        }
    ],
    "authors": [
        {
            "id": 1,
            "name": "Mateusz Bogolubow",
            "description": "Programista i mentor w DevMentor.pl"
        },
        {
            "id": 2,
            "name": "Mateusz Choma",
            "description": "Senior Software Engineer, mentor w CodeRoad.pl"
        }
    ],
    "categories": [
        {
            "id": 1,
            "name": "HTML i CSS",
            "description": "Choć ani HTML, ani CSS nie są uznawane za języki programowania to mają kilka cech wspólnych, które pozwalają sprawdzić czy pisanie kodu daje Ci satysfakcję."
        },
        {
            "id": 2,
            "name": "JavaScript",
            "description": "Chcesz zostać programistą front-end i pisać zaawansowane aplikacje internetowe (programy)? Zacznij od JavaScriptu!"
        }
    ]
}

Zwróć uwagę, że stosuję płaską strukturę danych, a relacje między elementami określam przez identyfikatory (ID) – jest to o wiele lepsze rozwiązanie niż zagnieżdżanie, bo ułatwia dostęp do wartości (zagnieżdżanie wartości powoduje, że program staje się trudniejszy w rozwijaniu i mniej odporny na modyfikacje). Zagadnienie to omawiałem w innym moim artykule na przykładzie magazynu.

 Uruchomienie JSON Servera

Teraz w terminalu wpisz komendę uruchamiającą JSON Server:

json-server --watch data.json

Jeśli wszystko się powiodło, w terminalu powinieneś zobaczyć podobną informację:

\{^_^}/ hi!

 Loading data.json
 Done

 Resources
 http://localhost:3000/products
 http://localhost:3000/authors
 http://localhost:3000/categories

 Home
 http://localhost:3000

Wejdź pod wyświetlony adres, by przyjąć gratulacje uruchomienia JSON Servera 😉 U mnie to http://localhost:3000. Jeśli u Ciebie port 3000 jest zajęty, to JSON Server zależnie od wersji może się nie uruchomić lub zajmie kolejny dostępny port. Następnie obejrzyj zasoby: produkty, autorów, kategorie. Możesz też pobawić się w modyfikację URL, korzystając z dokumentacji JSON Servera.

Przykładowo adres http://localhost:3000/products?price=0 pokaże produkty bezpłatne. W ten sposób jesteśmy w stanie pobierać tylko te informacje, które nas interesują (przesyłamy zatem znacznie mniej danych, niż gdybyśmy pobierali informacje o wszystkich produktach).

 Metoda fetch() oraz CRUD

Metoda fetch() to metoda globalna zapewniana przez tzw. Fetch API, czyli interfejs do asynchronicznego przesyłania odpowiedzi i zapytań o zasoby. Dzięki niej spod wskazanego adresu URL możemy uzyskać dane lub pod taki adres je wysłać, a także usunąć lub edytować. Te operacje umożliwiają funkcje CRUD: create, read, update, delete (czyli właśnie twórz, odczytuj, aktualizuj, usuwaj).

UWAGA: cały kod prezentowanego rozwiązania znajdziesz w repozytorium. Aby podejrzeć jego działanie, musisz pobrać projekt i uruchomić JSON Server.

Przyjrzyjmy się teraz funkcji asynchronicznej, dzięki której pobierzemy dane z naszej bazy.

async function fetchData(path = '', options = { method: 'GET' }) {
    const url = 'http://localhost:3000' + path;
    const promise = await fetch(url, options);
    try {
        return promise.json();
    } catch (error) {
        console.error(error);
        return null;
    }
}

Funkcja ta domyślnie korzysta z metody GET i tym (czyli pobieraniem danych) w następnej części artykułu się zajmiemy. O pozostałych metodach napiszę na końcu.

 Pobranie i zaprezentowanie zasobów

Po wejściu użytkownika na stronę, chcę zaprezentować mu wszystkie produkty. Lecz… co zrobić z imieniem i nazwiskiem autora? Przecież obiekt produktu nie zawiera takiej wartości (posiada jedynie ID autora). Mamy pobierać z bazy i filtrować wszystkich autorów?

Nie, tu z pomocą przychodzi nam pierwsze udogodnienie!

Skorzystamy z tego, że nasza baza danych potrafi rozpoznać relację między właściwością produktu o nazwie authorId i właściwością autora o nazwie id.

Adres URL będzie prezentował się w następujący sposób:

http://localhost:3000/products?_expand=author

Dzięki temu w każdym dziecku – produkcie – zostanie zagnieżdżony obiekt z danymi rodzica – autora – w taki sposób:

{
    "id": 2,
    "title": "HTML i CSS: Podstawy",
    "description": "Podstawowa znajomość HTML-a i CSS-a jest niezbędna do rozpoczęcia nauki programowania w JavaScript. Dlatego poznamy solidne podstawy semantycznego kodu HTML, który jest niezbędny w prawidłowym budowania struktury strony internetowej.",
    "authorId": 1,
    "categoriesId": [
      1
    ],
    "price": 69,
    "author": {
      "id": 1,
      "name": "Mateusz Bogolubow",
      "description": "Programista i mentor w DevMentor.pl"
    }
  },

Tak oto zyskujemy łatwy dostęp do imienia i nazwiska przez zapis product.author.name.

Nasze produkty będziemy pobierać w funkcji loadProducts(), a prezentować je dzięki funkcji displayProduct():

async function loadProducts(resource, container) {
    // pobieram informacje o produktach
    const products = await fetchData(resource);
    // pobieram informacje o kategoriach
    const categoriesData = await getCategories();

    // jeśli zasoby istnieją, wyświetlam produkty
    if (products.length !== 0 && categoriesData.length !== 0) {
        products.forEach(function (product) {
            displayProduct(product, categoriesData, container);
        });
    }
}
async function displayProduct(product, categoriesData, container) {
    const productContainer = document.createElement('article');

    const titleEl = document.createElement('h2');
    titleEl.innerHTML = product.title;

    const authorEl = document.createElement('small');
    authorEl.innerHTML = 'Autor: ' + product.author.name;

    const categoriesContainer = document.createElement('div');

    const categoryEl = document.createElement('p');
    categoryEl.innerHTML = 'Kategoria: ';

    product.categoriesId.forEach(function (categoryId) {
        const category = categoriesData.find(
            element => categoryId === element.id
        );
        const categoryName = category.name;
        categoryEl.innerHTML = categoryEl.innerHTML + categoryName + ' ';

        categoriesContainer.appendChild(categoryEl);
    });

    const descriptionEl = document.createElement('p');
    descriptionEl.innerHTML = product.description;

    const priceEl = document.createElement('strong');
    priceEl.innerHTML = 'Cena: ' + product.price + ' zł';

    productContainer.appendChild(titleEl);
    productContainer.appendChild(authorEl);
    productContainer.appendChild(categoriesContainer);
    productContainer.appendChild(descriptionEl);
    productContainer.appendChild(priceEl);

    container.appendChild(productContainer);
}

 Relacja many-to-many na przykładzie kategorii i problem JSON Servera

Many-to-may, czyli wiele-do-wielu. W naszym przypadku produkt może mieć kilka kategorii, a kategoria może zawierać wiele produktów. Podczas korzystania z prawdziwej bazy danych, np. SQL, nie mielibyśmy problemu z szybkim dostaniem się do przypisanej do produktu kategorii po jej numerze ID. Niestety JSON Server w tej chwili nie wspiera relacji many-to-many, dlatego w funkcji loadProducts() musiałem pobrać wszystkie informacje o kategoriach i przefiltrować je pod kątem pasujących ID w funkcji displayProduct().

product.categoriesId.forEach(function (categoryId) {
    // szukam kategorii odpowiadającej ID przypisanemu w produkcie we właściwości "categoriesId"
    const category = categoriesData.find(
        element => categoryId === element.id
    );
    const categoryName = category.name;
    categoryEl.innerHTML = categoryEl.innerHTML + categoryName + ' ';

    categoriesContainer.appendChild(categoryEl);
});

 Wyszukiwanie po słowie kluczowym

Zwróć uwagę, że naszą aktualną ścieżkę '/products?_expand=author' będziemy wykorzystywać za każdym razem, gdy zachcemy załadować produkty. Dlatego przeniesiemy ją do osobnej zmiennej productsUrl i wykorzystamy w reszcie zapytań.

W pliku index.html mamy prosty formularz do odbierania poszukiwanych słów:

<form class="shop__searchForm" action="">
    <label for="keyword">Słowo kluczowe: </label>
    <input name="keyword" type="text" />
    <button type="submit">Szukaj</button>
</form>

Nasza ścieżka do produktów zawierających konkretne frazy kluczowe wygląda w następujący sposób:

`${productsUrl}&q=${keyword}`

czyli, gdy pod zmienne podstawimy wartości (załóżmy, że w formularzu wpisaliśmy słowo „podstawy”):

/products?_expand=author&q=podstawy

W dokumentacji JSON Servera znajdziesz ją pod nazwą Full-text search.

Następnie na formularzu nastawiamy nasłuchiwanie na event submit i tworzymy funkcję, która załaduje produkty pasujące do zapytania.

searchForm.addEventListener('submit', function (e) {
    e.preventDefault();
    shopContainer.innerHTML = '';
    const keyword = searchForm.keyword.value;
    loadProducts(`${productsUrl}&q=${keyword}`);
});

 Filtrowanie po autorze

Korzystamy z gotowego elementu <select /> z pliku index.html.

<label for="authors">Autor: </label>
<select
    class="shop__authorsList"
    name="authors"
    id="authors"
></select>

Tworzymy listę rozwijaną z nazwami autorów pobranymi z API, aby użytkownik mógł samodzielnie ich wybierać:

const authorsData = await getAuthors();
// jeśli zasób istnieje, tworzę listę rozwijaną
if (authorsData.length !== 0) {
    createDropList(authorsData, authorsList);
}
function createDropList(elements, dropList) {
    // dla każdego elementu (autora) tworzę opcję dla listy rozwijanej
    elements.forEach(function (element) {
        const optionEl = document.createElement('option');
        optionEl.value = element.id;
        optionEl.innerText = element.name;

        dropList.appendChild(optionEl);
    });
}

Tutaj pytamy o produkty przyporządkowane konkretnemu autorowi. Nasza ścieżka wygląda tak:

`/authors/${authorId}${productsUrl}`

A po wybraniu autora (używamy jego ID, np. 1) i podstawieniu wartości:

/authors/1/products?_expand=author

Korzystamy z supermocy bazy danych – rozpoznawania relacji. Nasza ścieżka komunikuje: znajdź autorów, wybierz autora o ID równym 1 i wyszukaj produkty, które posiadają właściwość authorId z wartością 1.

Nasłuchiwanie na event change nastawione na liście rozwijanej prezentuje się następująco:

authorsList.addEventListener('change', function (e) {
    shopContainer.innerHTML = '';
    const authorId = e.target.value;
    loadProducts(`/authors/${authorId}${productsUrl}`);
});

 Łączenie zapytań: filtrowanie i sortowanie po cenie

Tu ponownie skorzystamy z gotowego formularza w pliku index.html:

<form class="shop__priceForm" action="">
    <label for="pricing">Cena: </label>
    <input
        name="min"
        type="number"
        value="0"
        min="0"
        max="500"
    />
    <span>-</span>
    <input
        name="max"
        type="number"
        value="500"
        min="0"
        max="500"
    />

    <fieldset class="shop__fieldset" id="sorting">
        <legend>Sortowanie</legend>
        <input
            id="asc"
            name="sorting"
            type="radio"
            value="asc"
            checked
        />
        <label for="asc">rosnąco </label>
        <input
            id="desc"
            name="sorting"
            type="radio"
            value="desc"
        />
        <label for="desc">malejąco </label>
    </fieldset>

    <button type="submit">Szukaj</button>
</form>

Łączenie zapytań jest naprawdę proste – zwykle wystarczy dodać &, lecz szczegółowych informacji należy szukać w dokumentacji.

Nasze łączone zapytanie o cenę i kolejność pobranych elementów będzie wyglądać tak:

`${productsUrl}&price_gte=${min}&price_lte=${max}&_sort=price&_order=${sorting}`

A po podstawieniu wartości (załóżmy, że szukamy produktu do 70 zł z sortowaniem od najtańszych):

/products?_expand=author&price_gte=0&price_lte=70&_sort=price&_order=asc

W dokumentacji JSON Servera informację o zakresach znajdziesz pod hasłem Operators, a o sortowaniu pod Sort.

Nasłuchiwanie na event submit na formularzu prezentuje się w ten sposób:

priceForm.addEventListener('submit', function (e) {
    e.preventDefault();
    shopContainer.innerHTML = '';
    const min = priceForm.min.value;
    const max = priceForm.max.value;
    const sorting = priceForm.sorting.value;
    loadProducts(
        `${productsUrl}&price_gte=${min}&price_lte=${max}&_sort=price&_order=${sorting}`
    );
});

 CRUD – metody DELETE, POST, PATCH

Można powiedzieć, że do tej pory poruszaliśmy się po stronie przeznaczonej dla użytkownika. Teraz wykorzystamy część stworzonego kodu, by zasymulować platformę administratora, mającego uprawnienia do usuwania, dodawania i edytowania danych.

Uwaga: zmiany w naszej „bazie” możesz obserwować, odświeżając plik data.json.

Utworzymy plik admin.js oraz admin.html i przekopiujemy do nich kod z pliku app.js i index.html. Dla uproszczenia na razie pozbędziemy się formularzy z filtrami i kodu je obsługującego. Jeśli prawidłowo podepniesz pliki, to pod adresem http://localhost:5500/admin.html (numer portu może się u Ciebie różnić) zobaczysz stronę admina. Jeśli coś nie działa, możesz skopiować kod z repozytorium.

 DELETE

Zaczniemy od usuwania. Do kontenera przechowującego dane o produkcie dodamy atrybut data-id z informacją o ID produktu oraz przycisk „usuń”.

async function displayProduct(product, categoriesData, container) {
    const productContainer = document.createElement('article');
    // 1) dodajemy id do atrybutu dataset
    productContainer.setAttribute('data-id', product.id);

    // 2) tworzymy przycisk usuwania
    const deleteBtn = document.createElement('button');
    deleteBtn.innerText = 'usuń';
    deleteBtn.name = 'deleteBtn';

    const titleEl = document.createElement('h2');
    titleEl.innerHTML = product.title;

    const authorEl = document.createElement('small');
    authorEl.innerHTML = 'Autor: ' + product.author.name;

    const categoriesContainer = document.createElement('div');

    const categoryEl = document.createElement('p');
    categoryEl.innerHTML = 'Kategoria: ';

    product.categoriesId.forEach(function (categoryId) {
        const category = categoriesData.find(
            element => categoryId === element.id
        );
        const categoryName = category.name;
        categoryEl.innerHTML = categoryEl.innerHTML + categoryName + ' ';

        categoriesContainer.appendChild(categoryEl);
    });

    const descriptionEl = document.createElement('p');
    descriptionEl.innerHTML = product.description;

    const priceEl = document.createElement('strong');
    priceEl.innerHTML = 'Cena: ' + product.price + ' zł';

    productContainer.appendChild(titleEl);
    productContainer.appendChild(authorEl);
    productContainer.appendChild(categoriesContainer);
    productContainer.appendChild(descriptionEl);
    productContainer.appendChild(priceEl);
    // 2a) dodajemy przycisk do rodzica
    productContainer.appendChild(deleteBtn);

    container.appendChild(productContainer);
}

Nasłuchiwanie na kliknięcie ustawimy na kontenerze przechowującym wszystkie produkty (shopContainer). Dlaczego nie ustawiamy tego na pojedynczym produkcie lub przycisku „usuń”? Bo ich pojawienie się na stronie jest zależne od uzyskania odpowiedzi z API (tworzymy je w momencie, gdy mamy już dane produktu). Gdyby kod JavaScript z eventListenerem wczytał się zanim otrzymamy odpowiedź z API, to próbowalibyśmy ustawić nasłuchiwanie na elemencie, który jeszcze nie istnieje.

Wykorzystujemy to, że mamy gotową funkcję fetchData(), w której wystarczy podmienić ścieżkę i metodę. W ścieżce zawieramy informację o zasobie, z którego element chcemy usunąć, i po ukośniku dodajemy ID tego elementu. Funkcja, która uruchomi się po kliknięciu przycisku „usuń” prezentuje się następująco:

// 3) dodajemy listener do całego kontenera
shopContainer.addEventListener('click', async function (e) {
    if (e.target.name === 'deleteBtn') {
        const productId = e.target.parentElement.getAttribute('data-id');
        await fetchData(
            `/products/${productId}`,
            (options = { method: 'DELETE' })
        );
        shopContainer.innerText = '';
        loadProducts(productsUrl);
    }
});

 POST

Tutaj mamy dwa zagadnienia: brak ID w ścieżce (utworzy się ono samoistnie po dodaniu elementu do bazy) oraz dodatkowe opcje: headers (nagłówki) i body. W nagłówkach informujemy serwer, jaki typ danych wysyłamy (u nas to JSON, stąd adekwatna wartość: 'application/json'). W body przekazujemy dane, które mają trafić do bazy pod wskazany adres zasobów. Funkcja, która doda produkt do bazy wygląda tak:

await fetchData(
    `/products`,
    (options = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            "title": "Przykładowa książka",
            "description": "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Accusantium nobis quis aliquam pariatur odit maxime. Dignissimos eius voluptate minus molestias porro, id beatae tenetur sit, dicta, consequuntur atque ab iste.",
            "authorId": 1,
            "categoriesId": [
              1
            ],
            "price": 20
          }),
    })
);

Nie wykorzystuję jej w kodzie. Będziesz mógł jej użyć w zadaniu domowym 😉 Podobnie z poniższą metodą PATCH.

 PATCH

Dzięki metodzie PATCH możemy edytować wybrane części elementu z bazy, czyli tylko te właściwości, które wskażemy w opcjach w body. Gdybyśmy korzystali z metody PUT, musielibyśmy liczyć się z tym, że nadpiszemy np. cały produkt.

Tym razem musimy podać ID, by namierzyć element do edycji. W body zaś przekazujemy tylko te właściwości, których wartości chcemy zmienić. Funkcja będzie prezentować się następująco:

await fetchData(
    `/products/${productId}`,
    (options = {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title: 'Próbny tytuł' }),
    })
);

 JSON Server i CRUD – zadania dodatkowe

 Panel administratora

Kod, który stworzyliśmy można by pod wieloma względami jeszcze ulepszyć. Przede wszystkim nie mamy elementów interfejsu admina, które umożliwiałyby dodawanie (POST) i edycję (PATCH) elementów.

Formularz dodawania produktów

Stwórz formularz, z inputami odpowiadającymi kolejnym właściwościom produktu: tytuł, opis, ID autora (docelowo dobrze by było wyszukiwać ID po podaniu nazwy autora), kategorię i cenę.

Dodaj do formularza event submit. Funkcję fetchData() z opcją POST uruchamiaj po zatwierdzeniu formularza. Pobrane z formularza dane umieść w odpowiednich właściwościach w body.

Kod JavaScript obsługujący dodawanie elementów do bazy mógłby się prezentować mniej więcej tak:

productForm.addEventListener('submit', async function (e) {
    const title = e.target.title.value;
    // pozostałe wartości z formularza
    await fetchData(
        `/products`,
        (options = {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                title: title,
                // pozostałe właściwości z wartościami
            }),
        })
    );
    shopContainer.innerText = '';
    loadProducts(productsUrl);
});

Z kodem HTML musisz poradzić sobie sam 😉

Edycja wartości produktu

Aby administrator mógł edytować elementy w panelu, wykorzystaj atrybut contenteditable. Do nasłuchiwania zmian użyj eventu input. Pamiętaj, że będziesz potrzebować ID produktu – na szczęście znajduje się on już w atrybucie data-id, musisz jedynie się do niego dostać 🙂

 Panel użytkownika

Tutaj przydałaby się refaktoryzacja, kilka ulepszeń i dodatków.

Spróbuj:

  • rozbić funkcję displayProduct() na mniejsze funkcje
  • zapytanie o słowo kluczowe, autora i cenę przenieść do jednego formularza i łączyć zapytania (w tej chwili włączenie jednego filtra kasuje ustawienia poprzedniego)
  • dodać kod, który pozwoli na zapis kategorii obok siebie z rozdzieleniem, np. HTML i CSS | JavaScript
  • przenieść obsługę API do osobnego pliku – wskazówki znajdziesz w artykule „CRUD w osobnym pliku – lepszy kod dla API” (pamiętaj, że przy korzystaniu z importu i eksportu do elementu <script> w pliku HTML należy dodać atrybut type="module")
  • stworzyć podstronę z informacjami o autorach (wykorzystaj do tego kod dla API przeniesiony w zadaniu powyżej do osobnego pliku).

Kod asynchroniczny to codzienność programisty, a JSON Server umożliwia zgłębienie tego tematu. Twórz, odczytuj, aktualizuj i usuwaj dane. Sprawdź, czy masz projekty, w których tylko imitujesz np. odbieranie danych od użytkownika lub zapisujesz je w LocalSotrage. Może warto zmodyfikować je tak, by pokazać, że radzisz sobie z kodem asynchronicznym? Powodzenia!

Udostępnij ten artykuł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.