Dawid Panfil

Komunikacja z bazą danych mongoDb w node.js

Trzeci temat związany z node.js. Dzisiaj zajmiemy się komunikacją z MongoDb. Czyli troszkę liźniemy model.

W ramach przypomnienia, poniżej macie listę poprzednich wpisów:

W pierwszej części serii artykułów traktujących o Node.js już instalowaliśmy paczkę Mongoose – która odpowiada z komunikację z bazą danych.

Łączymy się z mongoDb

Pierwsze co to należy połączyć się z bazą danych, więc zaczynajmy. W pliku index.ts doklejamy coś takiego:

/**
 * ### DATABASE
 */
mongoose.connect('mongodb://localhost:27017/hairstyling').then(
    () => {
        console.log('Udało się!');
    },
).catch((err) => {
    console.log('MongoDB connection error. Please make sure MongoDB is running. ' + err);
    process.exit();
});

Oraz dodajemy import u góry pliku:

import mongoose from "mongoose";

Po pierwsze – to jest bardzo brzydki sposób na łącznie się z bazą danych – przerobimy go niedługo na plik konfiguracyjny – no ale tak widać wszystko jak na tacy. Interesuje nas parametr funkcji connect – musimy tam podać ścieżkę do naszej bazy danych – czyli host:port/nazwa_bazy.

Dla naszego ułatwienia wrzuciłem tam console.log – aby było wiadomo, że już się połączyliśmy z bazą. No i następnie łapiemy ewentualny błąd. Jakoże bez bazy nic nie zrobimy to zabijamy naszą aplikację – takimi to brutalami będziemy 😉

Jeśli się nie połączysz – to sprawdź czy masz uruchomione mongoDb i skonfigurowane tak jak podałem w pierwszym linku. Jeśli postępowałeś od samego początku wg moich wskazówek to na 99% wystarczy, aby uruchomić mongo:

sudo service mongod start
sudo service mongod status

Tworzymy pierwszą kolekcje.

Jak pamiętasz – do kolekcji wrzucamy dokumenty – obiekty json, które mogą mieć dowolną zawartość. Jednak, wypadałoby jakąś strukturę zachować i modele nam w tym pomogą. Dokładniej mówiąc schema i interface. Także stwórz sobie dwa nowe foldery w src: schemas oraz interfaces.

Czym jest interface?

Interfejs jest zbiorem nazw, których istnienia wymagamy od implementującej go klasy. Brzmi skomplikowanie co nie? Tutaj wchodzimy powoli w obiektowość. Zerknij sobie na kod naszych kontrolerów. Mamy tam pojedyncze funkcje index, update, delete i tak dalej. I następnie w naszym głównym pliku index.ts udostępniamy te funkcje w jakiejś przestrzeni nazw np. homeController. Możemy to opakować w klasę (zrobimy to w kolejnych częściach) i upraszczamy sobie kod. Nasza klasa – czyli obiekt zawiera metody (funkcje), które odpowiadają za wyświetlanie odpowiedniej strony.

Wyobraź sobie, że tworzysz sobie panel administratora – taki podstawowy masz w nim zawsze listę, formularz zapisu/edycji, możliwość usuwania bądź podglądu jakiegoś elementu – przykładowo usługi. Oprócz usługi może to być klient i zamówienie. Napisałeś sobie kontroler w ten sposób, że masz jedną akcje do listy, drugą do zapisu, edycji i kolejne do usuwania i podglądu – tak jak wyżej napisałem.

I zajmijmy się (abstrakcyjnie) listą. Każda lista potrzebuje od nas 3 rzeczy: kolumn, elementów i akcji. No to tworzymy sobie odpowiednio klasy: service, order oraz contractor. Każda z nich ma w sobie funkcje: getColumns, getForm, save, edit, delete. No i nasz kontroler na podstawie adresu wyszukuje sobie odpowiedniej klasy i pobiera dane z odpowiedniej funkcji.

Co się stanie jak zapomnę w klasie contractor zrobić funkcji getColumns? Przy próbie uruchomienia przez użytkownika strony z listą kontrahentów będzie błąd. Jak go uniknąć? Stworzyć interfejs.

W tym interfejsie będziesz wymuszał na klasie jakie ma mieć w sobie metody/funkcje oraz w jakim typie one mają być (tekst/liczba/tablica obiektów i tak dalej) – wtedy jak zapomnisz stworzyć funkcji getColumns to edytor od razu Ci o tym przypomni i nawet nie uruchomisz aplikacji.

Czym jest schema?

Schema – schemat. Tutaj informujemy mongoose jak będzie wyglądać każdy nasz dokument (rekord) w kolekcji. Także to akurat jest proste. Więc do dzieła.

Nasze usługi

Zastanów się jakie pola powinny mieć usługi przez nas oferowane. Moim zdaniem usługa będzie zawierać: cenę (najlepiej netto i brutto – ale póki co się ograniczymy tylko do zwykłej ceny), nazwę usługi i jej opis. Także stwórzmy sobie interfejs:

export interface IService {
    name?: string;
    price?: number;
    description?: string;
}

Przyjęło się, że interfejsy zaczynają się od literki I. Informacyjnie dodam, że znak zapytania oznacza tutaj, że dany parametr jest opcjonalny. U nas teoretycznie wszystkie są wymagane, no ale potem sobie poprawimy – póki co w ten sposób prościej, łatwiej i przyjemniej do nauki.

Jak za każdym razem powtarzam, sporo rzeczy nie robimy tak jak należy- po to, aby prościej było zrozumieć.

Plik oczywiście zapisz w src/interfaces/IService.ts

Tworzymy schemat

import {Document, Model, model, Schema } from 'mongoose';
import {IService} from '../interfaces/IService';

type ServiceDocument = IService & Document;

export const ServiceSchema = new Schema({
    name: String,
    price: Number,
    description: String,
});

export const ServiceModel: Model = model('services', ServiceSchema);

Tworzymy nową schemę z naszymi polami, następnie tak jak pisałem na podstawie naszego interfejsu tworzymy model (no i typ – ale o tym później) i wskazujemy jak będzie nazywała się nasza kolekcja.

Mówiąc prościej w naszej kolekcji o nazwie services będą dokumenty, które wyglądają jak nasz interfejs IService.

Ten plik zapisz w src/schemas/service.ts

Czas uzupełnić bazę MongoDb

Moglibyśmy to zrobić przez klienta mongoDb, ale w ten sposób mniej rzeczy poznamy. Także stwórz sobie nowy kontroler, a w nim dwie metody. Jedną, która uzupełni nam listę, a druga nam ją wyświetli. Oczywiście, jeszcze to nie będzie obiektowy kontroler(tym zajmiemy się w następnej części).

import {Request, Response} from 'express';
import {ServiceModel} from '../schemas/service';


export let createData = (req: Request, res: Response) => {
    console.log('create data');

    const service = new ServiceModel();
    service.name = "Strzyżenie włosów - mężczyźni";
    service.description = "Cena obejmuje strzyżenie oraz ułożenie włosów krótkich";
    service.price = 20;

    service.save();


    const service2 = new ServiceModel();
    service2.name = "Scinanie końcówek";
    service2.description = "Cena obejmuje umycie oraz ścięcie zniszczonych końcówek włosów";
    service2.price = 100;

    service2.save();

    const service3 = new ServiceModel();
    service3.name = "Farbowanie";
    service3.description = "Cena obejmuje pofarbowanie włosów wraz z jednym opakowaniem farby. Za dodatkową farbę należy dopłacić.";
    service3.price = 75;

    service3.save();

    res.send('Zapisano usługi');
    return;
};

Plik nazwij: src/controllers/serviceController.ts

Dodaj w odpowiednich miejscach poniższe linijki w pliku index.ts i uruchom aplikacje pod linkiem: http://localhost:3000/service/create

import * as serviceController from './controllers/serviceController';

//service
app.get('/service/create', serviceController.createData);

Mam nadzieję, że nie zapomniałeś uruchomić aplikacji 😉 Teraz sprawdź czy dodały się zmiany – ale to już zrobimy przez terminal:

mongo
> use hairstyling;
> db.services.find();

W odpowiedzi dostaniesz 3 rekordy.

Czas na wyjaśnienia:

Kodu było dużo, ale jest prosty do zrozumienia. W naszej funkcji createData stworzyliśmy sobie 3 modele: service, service2, service3. To jest zrozumiałe – w końcu przed chwilą stworzyliśmy sobie model w folderze schema na podstawie interfejsu.

Następnie każdy element sobie uzupełniamy odpowiednimi danymi czyli nazwa, opis i cena. Też proste.

Kolejny krok to zapis. Zapis odbywa się asynchronicznie. Nie czekamy, aż funkcja save() zwróci wynik. Ważne jest, że save() jest tak zwanym Promise (obietnicą). Co w dużym skrócie oznacza, że kiedyś zwróci wynik (lub błąd). Przykład użycia promise masz chociażby przy próbie łączenia się z mongoDb.

Jednak promise nie jest tematem tej serii artykułów – to polecam samemu zapoznać się z tym tematem, bo jeszcze nie raz on się nam pojawi.

Podsumowując stworzenie nowego dokumentu w bazie to:

  1. Stworzenie obiektu na podstawie modelu
  2. Uzupełnienie wartości
  3. Zapis przez wywołanie funkcji save()

Wyświetlamy nasze usługi – pobieranie danych z MongoDb

Zapisaliśmy dane w bazie MongoDb – teraz trzeba je wyświetlić. Chyba prostszej rzeczy nie ma (okej zapis jest prostszy). Stwórz sobie w kontrolerze funkcje index – która wyświetli listę usług jakie oferujemy:

export let index =  (req: Request, res: Response) => {
    let query = ServiceModel.find();
    let items =  query.exec();
    console.log(items);

    let renderData = {
        items: items,
    };
    return res.render('service/index', renderData);
};
// in index.ts
app.get('/services', serviceController.index);

oraz widok:

<h1>Witaj w naszym salonie kosmetycznym!</h1>

<p>Zobacz co możemy Tobie zaoferować</p>

<ul class="people_list">
    {{#each items}}
    <li>
        <div>
            <h3> {{this.name}} za jedyne {{this.price}} zł</h3>
            <em>{{this.description}}</em>
        </div>
    </li>
    {{/each}}
</ul>

Przebuduj aplikację i odpal odpowiednią stronę. Ups… nic się nie wyświetliło, natomiast w konsoli dostałem:

Promise { <pending> }

Znowu trafiliśmy na Promise. Napisałem – poczytaj 😉 Można to załatwić na dwa sposoby – pierwszy znasz i polega na wykorzystaniu then – przyjrzyj się jak to wygląda w wypadku łączenia się z bazą danych mongoDb. Drugi polega na wykorzystaniu słówka await, które informuje aplikacje, że musimy poczekać za wynikiem. Pierwszą opcje znasz, także pokaże jak użyć tej drugiej:

export let index = async (req: Request, res: Response) => {
    let query = ServiceModel.find();
    let items =  await query.exec();
    console.log(items);

    let renderData = {
        items: items,
    };
    return res.render('service/index', renderData);
};

No i teraz wszystko działa 😉 Proste, prawda?

Kilka słów wyjaśnienia – czyli co znajdziemy w modelu?

Jak zauważyłeś wyciągniecie wszystkich danych polega na wykorzystaniu metody find(). i następnie wykonania zapytania. Możemy dorzucić tutaj obiekt z naszymi warunkami, sortować, ustawić limit, usunąć dane albo znaleźć i edytować. Opcji jest mnóstwo. Zresztą sam zerknij:

Wszystkie metody jakie podpowiada IDE

Sporo co nie? I jak widzisz to nie ma tutaj wszystkiego, bo wspominałem chociażby o limicie, który dopiero zobaczysz gdy wywołasz go na find().

Na zakończenie

Podsumowując. Dzisiaj połączyłeś się z bazą danych, zobaczyłeś czym jest Model – czyli kolejną literkę z MVC poznałeś. Oraz wiesz jak pokazać wszystkie dane. Teraz masz zadanie domowe: zapoznaj się z wszystkimi powyższymi metodami i spróbuj ich użyć. Dodatkowo zrób podstronę dla jednej konkretnej usługi😉

Wszystko to co pokazałem, staram się pokazywać w najprostszy sposób – nie zawsze zgodny z ogólnymi praktykami. Także pamiętaj, aby uzupełniać wiedzę, którą Ci przekazuje w moich artykułach. Jeśli będziesz miał z czymś problem to zachęcam do komentowania.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *