Uvod u Nest.js

Šta je Nest.js?

Nest.js je Node.js framework za izradu server-side aplikacija. Koristi modularnu arhitekturu što podrazumeva da se aplikacija deli na module, gde svaki modul grupiše povezane funkcionalnost, tako da se lako mogu dodati nove funkcionalnosti bez narušavanja postojećih delova aplikacije.

Nest.js korisi TypeScript jezik (mada se može koristiti i JavaScript), uz korišćenje dekoratora (dekoratori su specijalne oznake koje se dodaju iznad koda sa kojima definišemo dodatna uputstva). Pored HTTP-a, Nest.js podržava WebSockets, GraphQL, gRPC i druge protokole.

nest.js

Moduli

U Nest.js, moduli su osnovni gradivni blokovi aplikacije i služe za organizaciju i strukturu koda. Oni grupišu povezane komponente kao što su kontroleri, provajderi (servisi), repozitorijumi i drugi moduli u logične jedinice. Moduli se definišu pomoću @Module() dekoratora.

Polja Modula:
  • imports: Lista modula koji su uvezeni u trenutni modul. Ovi moduli su dostupni unutar modula.
  • controllers: Lista kontrolera definisanih u modulu. Kontroleri su zaduženi za rukovanje HTTP zahtevima.
  • providers: Lista provajdera (servisa) definisanih u modulu. Provajderi obavljaju poslovnu logiku i mogu biti injektovani u kontrolere ili druge provajdere.
  • exports: Lista provajdera koji su eksportovani iz modula i mogu biti korišćeni u drugim modulima.

Kreiranje modula

Korišćenjem Nest CLI, možete brzo i jednostavno kreirati module u vašoj Nest.js aplikaciji. Da biste kreirali novi modul, možete koristiti nest generate (ili skraćeno nest g) komandu. Na primer, da biste kreirali modul pod nazivom cats, koristite sledeću komandu:

Ova komanda će generisati novu datoteku cats.module.ts u src/cats direktorijumu, koja izgleda ovako:

Primer Modula

Evo kako se definiše jednostavan modul u Nest.js:

Glavni Modul (App Module)

Svaka Nest.js aplikacija ima glavni modul, obično nazvan AppModule, koji je korenski modul aplikacije. On može uvoziti druge module i služi kao ulazna tačka za aplikaciju.

Controllers (Kontroleri)

Kontroleri su odgovorni za rukovanje HTTP zahtevima i vraćanje odgovarajućih odgovora klijentima. U Nest.js, kontroleri služe kao posrednici između klijenta i servisa. Oni definišu rute i metode koje se aktiviraju kada se određene rute pogode. Kontroleri se obeležavaju pomoću @Controller() dekoratora. Dekorartori se takodje koriste i u okviru samog kontrolera da obeleže specifične metode ili rute kao što su: @Get(), @Post(), @Put(), @Delete(), itd.

U ovom primeru, CatsController ima jednu rutu koja odgovara na GET zahteve na /cats i vraća string ‘This action returns all cats’.

Dodavanje kontrolera u modul

Nakon kreiranja kontrolera ili servisa, potrebno je ažurirati modul da ih uključi:

Primer

Services (Servisi)

Servisi predstavljaju sloj aplikacije koji obavlja poslovnu logiku i obezbeđuje podatke za kontrolere. Servisi obično sadrže metode koje se koriste za obavljanje različitih zadataka kao što su pristup bazi podataka, rad sa API-jevima trećih strana, obrada podataka i sl.

  • Definisanje servisa: Servisi se definišu kao klase i obično koriste @Injectable() dekorator kako bi ih Nest.js mogao injektovati u kontrolere ili druge servise.
  • Injekcija zavisnosti: Servisi se mogu ubaciti (injektovati) u kontrolere ili druge servise koristeći konstruktor.

Kreiranje servisa

Da biste dodali novi servis, koristite nest generate service (ili skraćeno nest g service) komandu.

Ova komanda će generisati cats.service.ts datoteku u src/cats direktorijumu sa osnovnom implementacijom servisa:

Dodavanje servisa u modul

Kada kreirate novi servis pomoću komande nest generate service cats, Nest CLI će automatski ažurirati odgovarajući modul da uključi novokreirani servis u providers polje tog modula. Ovo omogućava da servis bude dostupan u okviru modula bez dodatne manuelne intervencije.

Primer:

U ovom primeru, UsersService ima metodu findAll koja vraća listu korisnika.

Middleware

Middleware su funkcije koje se izvršavaju tokom obrade HTTP zahteva. Middleware funkcioniše u prostoru između primanja zahteva i povratka odgovora, omogućavajući modifikaciju zahteva i odgovora, preusmeravanje toka ili završavanje zahteva. U Nest.js, middleware se definiše kao klase koje implementiraju NestMiddleware interfejs ili kao obične funkcije. Middleware se može primeniti na određene rute ili globalno na sve rute.

Kreiranje middleware-a

Da biste dodali novi middleware, koristite nest generate middleware (ili skraćeno nest g middleware) komandu.

Ova komanda će generisati logger.middleware.ts datoteku u src direktorijumu sa osnovnom implementacijom middleware-a:

U ovom primeru, LoggerMiddleware loguje svaki zahtev koji prolazi kroz aplikaciju.

Registrovanje middleware-a u modul:

Da biste registrovali middleware, potrebno je da ga dodate u odgovarajući modul.

U ovom primeru, LoggerMiddleware će se primenjivati na sve zahteve koji idu na /users rutu.

Providers (Provajderi)

Nest.js ima ugrađen mehanizam za injektiranje zavisnosti, koji olakšava upravljanje zavisnostima i testiranje koda. Ovo znači da komponente kao što su usluge (services) mogu biti automatski ubačene (injected) tamo gde su potrebne, umesto da ih eksplicitno instancirate. Više o dependency injection-u pročitajte u članku “Šta je Dependency Injection”.

Provajderi su osnovni koncept u Nest.js koji omogućavaju kreiranje i deljenje instanci objekata. Obično se koriste za implementaciju servisa, ali mogu takođe obuhvatati bilo koju klasu koju želite da bude dostupna putem Dependency Injection (DI) mehanizma tj. provajderi mogu biti servisi, repozitorijumi, fabričke funkcije, itd.

Obležavanje provajdera

Provajderi se obeležavaju kao klase koje koriste @Injectable() dekorator.

Primer

U ovom primeru, CatsService je provajder definisan sa @Injectable() dekoratorom.

Registrovanje provajdera

U ovom primeru, CatsService je registrovan kao provajder u CatsModule.

Injectovanje provajdera

Provajderi se mogu ubaciti (injektovati) u druge klase ili provajdere koristeći konstruktor.

Primer

Repozitorijum

Repozitorijumi su sloj koji omogućava pristup podacima i upravljanje njima. Oni apstrahuju interakciju sa bazom podataka ili bilo kojim drugim izvorom podataka, čime omogućavaju čistiji i konzistentniji kod. Koriste se za obavljanje CRUD operacija (kreiranje, čitanje, ažuriranje, brisanje).

Primer

U ovom primeru, UserRepository koristi TypeORM repozitorijum za interakciju sa User entitetom.

Exception Filters

Exception filters su kao sigurnosne mreže u programu koje hvataju greške i odlučuju šta da urade s njima. Kada se nešto neočekivano desi u aplikaciji, exception filteri omogućavaju da se uhvate greške i vrati odgovarajuća poruka korisniku. Exception filters omogućavaju da centralizovano rukujemo greškama i ukidaju potrebu da se svuda po kodu pišu akcije koje bi hendlovale greške.

Korak 1: Definisanje Exception Filtera

Definišemo filter koristeći @Catch() dekorator. Ovo je jednostavan primer filtera koji hvata sve greške:

Korak 2: Korišćenje Exception Filtera

Možemo primeniti filter na različitim nivoima: globalno (za celu aplikaciju), na nivou kontrolera ili na nivou pojedinačnih ruta.

Primena na Globalnom Nivou

Da bi filter radio za celu aplikaciju, dodaćemo ga u glavni fajl aplikacije (npr. main.ts):

Primena na Nivou Kontrolera

Da bi filter radio samo za određeni kontroler, dodamo @UseFilters dekorator iznad kontrolera:

Primena na Nivou Pojedinačnih Ruta

Da bi filter radio samo za određenu rutu, dodamo @UseFilters dekorator iznad te rute:

Pipes

Pipes u Nest.js su moćan alat koji omogućava transformaciju i validaciju podataka. Pipes mogu da transformišu dolazne podatke, verifikuju ih i čak odbace nevažeće podatke. To omogućava da se logika za transformaciju i validaciju centralizuje i lako ponovo koristi.

Kreiranje Pipes-a

Pipe se kreira implementirajući PipeTransform interfejs i definišući metodu transform koja će obraditi podatke.

Primer

U ovome primeru pipe koji konvertuje dolazni string u broj:

Korišćenje Pipes-a

Pipes možete primeniti na globalnom nivou, nivou kontrolera ili nivou rute.

Globalna Primena

Globalno primenjivanje pipes-a koristi se u glavnom fajlu aplikacije (npr. main.ts):

Primena na Kontroleru

Možete primeniti pipes na specifičan kontroler koristeći @UsePipes() dekorator:

Primena na Ruti

Možete primeniti pipes na specifičnu rutu:



Node starter projekat (TypeScript + Docker)

Kostur projekta

Pretpostavićemo da će projekat imati više manjih projekata tj. kontejnera, npr. deo za mikroservise, deo za rad sa bazom… Te je dobro da se u root-u projekta naprave podfolderi za svaki manji projekat npr.:

Kreiranje package.json

Prvo ćemo kreirati package.json fajl u root folderu projekta. Ovaj fajl će sadržati sve potrebne informacije o projektu, kao što su naziv, verzija, zavisnosti… Za kreiranje osnovne verzije package.json koristićemo npm init naredbu sa flagom “YES” “-y” da sve sam popuni bez pitanja:

Nakon toga će package.json fajl izgledati ovako:

Za dalji rad je potrebno da kreiramo i definišemo početni JS fajl. Početni JS fajl ćemo krerati u sklopu “src” foldera kojeg ćemo smestiti u okviru “api” foldera (npr. app.js). Nakon toga je potrebno da prepravimo lokaciju i ime početnog fajla u sklopu package.json-a:

Typscript podrška

  1. TypeScript

    Prvo je potrebno da instaliramo sam TypeScript:

  2. tsconfig.json

    tsconfig.json fajl se koristi za konfiguraciju TypeScript kompajlera (tsc). Služi za definisanje različitih opcija kompajliranja koje upravljaju načinom na koji će TypeScript kod biti preveden u JavaScript. Ovaj fajl možete da inicijalizujete naredbom

    Primer

  3. ts-node

    Da bi pokrenuli TypeScript direktno iz Node okruženja bez potrebe za prethodnim kompajliranjem u JS, potrebno je instalirati ts-node paket:

    Zatim kreiramo jednostavnu “start” skriptu za pokretanje aplikacije u produkciji:

    Pozivanjem naredbe iz terminala se pokreće aplikacija:

  4. @types paketi

    Kada koristiš JavaScript biblioteke u TypeScript projektu, onda je potrebno da instaliraš odgovarajuće tipove kako bi otklonio greške na nivou kompajlacije. JavaScript-u je dinamički tipiziran jezik, što znači da nema ugrađene tipove a da bi TypeScript znao koje bi tipove trebo koristi za Node.js (pisan u JS-u), moramo listu tih definicija a koje možemo naći u paketu @types/node.

    Ako npr. koristimo “fs” modul, bez @types/node paketa, TypeScript kompajler ne bi znao ništa o njemu, što bi dovelo do grešaka tokom kompajlacije. Isto važi i za Express, TypeScript ne zna koje bi tipove trebao da koristi za promenjive pisane u Express-u, te je potrebno da mu pomognemo da ne bi izbacivao greške pri kompajliranju tako što ćemo instalirati paket @types/express

  5. Nodemon

    Nodemon paket nam omogućava automatski restart aplikaciju na serveru kada detektuje promene u datotekama u projektu, a instalira se na sledeći način:

Definisanje skripti

Ako imamo instaliran nodemon paket onda možemo da kreiramo skriptu sa kojom ćemo restartovati server svaki put ukoliko dodje do promene koda. Ta skripta se obično naziva “dev” jer se koristi u lokalu pri developmentu:

Ova skripta se poziva iz terminala na sledeći način:.

Nakon startovanja skripte nodemon prati promene u fajlovima unutar src direktorijuma i ako ih primeti nakong toga automatski ponovo pokreće src/app.ts fajl koristeći ts-node biblioteku, kompajlirajući TypeScript kod “u letu”.

NAPOMENA:
Za bildovanje JS projekta možemo kreirati i skriptu npr. "build": "tsc". Ova komanda će pokrenuti TypeScript kompajler koji će prevesti sve .ts fajlove u .js fajlove prema konfiguraciji definisanoj u tsconfig.json fajlu i smestiti ih u izlazni direktorijum (obično dist ili build). Skripta se poziva na sledeći način:.

ENV promenjive

U node.js okruženju kao i u drugim sistemima se koriste promenjive okruženja okupljene u jedan fajl, sa čijim korišćenjem se izbegavaju hardkodirani podaci razbacani po izvornom kod-u na različitim mestima, taj fajl se obeležava sa .env i kreiramo ga u root folderu projekta. Pa će struktura projekta izgledati ovako:

Za učitavanje (environment) promenljivih iz .env fajla u process.env je potrebno da instaliramo dotenv paket:

Potrebno je na početku izvršavanja koda (npr. u početnom fajlu) da se pozove metoda config() iz DotEnv paketa jer ona učitava sadržaj .env datoteke i dodaje svaku promenljivu u “process.env”objekat. Više o env promenjivama pročitajte u članku Promenjive okruženja.

NAPOMENA:
.env datoteku treba dodati u .gitignore fajl kako bi se izbeglo njeno deljenje sa verzionim kontrolama, čime se štite osetljive informacije. Obratite pažnju da se u produkciji ne zaboravi da se “ručno” napravi novi .env fajl jer se on ne šalje sa git-om !!!

Dockerizacija

Dockerfile

Dockerfile je tekstualna datoteka koja sadrži uputstva za kreiranje Docker slike (eng. image). Dockerfile sadrži niz uputstava (instrukcija) koje određuju kako će Docker slika biti kreirana.

Više o Docker-u i njegovom korišćenju pročitajte u članku “Docker: Pokretanje aplikacija svuda”

docker

Kreiranje slike (“build”)

Za kreiranje Docker image-a iz Dockerfile-a se koristi komanda docker “build”. Ova komanda ima nekoliko opcija koje omogućavaju prilagođavanje procesa build-ovanja. Detaljno objašnjenje opcija koje se često koriste sa komandom docker build je sledeće:

Gde je PATH direktorijum koji sadrži Dockerfile, URL može biti URL ka GIT repozitorijumu, a “-“ omogućava čitanje Dockerfile-a iz standardnog ulaza. Evo liste nekih opcija:

  • –tag skraćeno -t: Oznaka za image. Omogućava imenovanje image-a i dodeljivanje verzije (tag-a).
  • –file skraćeno -f: Specifikuje putanju do Dockerfile-a ako nije u trenutnom direktorijumu.
  • –build-arg: Prolaz promenljive kao build argument. Ove promenljive se mogu koristiti unutar Dockerfile-a.
  • –no-cache: Onemogućava korišćenje keša za build, osiguravajući da se svaki sloj ponovo izgradi.
  • –pull: Uvek povlači najnoviji osnovni image pre build-ovanja.
  • –label: Dodaje metapodatke (label-e) image-u.

NAPOMENA:
Kada Docker gradi image, potrebno mu je da ima pristup svim fajlovima koji su navedeni ili korišćeni unutar Dockerfile-a. Ovi fajlovi se nalaze u direktorijumu koji se naziva “kontekstualni direktorijum”. Tačka (“.”) na kraju komande docker build jednostavno znači “koristi trenutni direktorijum kao kontekstualni direktorijum”. To omogućava Docker-u da pročita Dockerfile i sve potrebne resurse iz tog direktorijuma kako bi kreirao image.
Primer:
Tačka (.) u naredbi docker build -f /path/to/Dockerfile .označava trenutni direktorijum kao kontekstualni direktorijum. To znači da Docker koristi sve fajlove iz tog direktorijuma u procesu build-ovanja, dok Dockerfile može biti specificiran bilo gde u sistemu pomoću -f opcije.

Primer

Ova komanda će:

  • Kreirati image pod nazivom my-image sa tag-om (verzijom slike) 1.0
  • Korisiti Dockerfile iz specificiranog puta
  • Postaviti build argument HTTP_PROXY
  • Izvršiti build bez korišćenja keša
  • Uvek povući najnoviji osnovni image
  • Dodati dve oznake (label-e) image-u
  • Kontekstualni direktorijum je trenutni direktorijum

Pre pokretanja naredbe “build” u Windows-u je potrebno da startujemo Docker desktop aplikaciju i poželjno je da u terminalu budete u root-u projekta u kome se nalazi i Dockerfile.
Nakon izvršene komande možete da proverite u “Docker deskop” aplikaciji image, ali to isto možete uraditi i kroz terminal naredbom:

Docker skripte

Ako želimo da ubrzamo rad sa Dockerom, možemo da pripremimo skripte u package.json kao npr.

Objašnjen

  1. Skripta docker:build kreira Docker sliku iz Dockerfile-a, pos imenonm “starter_node:latest”
  2. Skripta docker:run pokreće novi kontejner, dodeljuje mu ime “node_api” na osnovu slike “starter_node”
  3. Skripta docker:stop zaustavlja kontejner sa imenom “node_api”, zatim uklanja sve zaustavljene kontejnera bez traženja potvrde

Više o docker naredbama pogledajte u članku Docker: Pokretanje aplikacija svuda

REZIME

Ovako izgleda struktura projekta:

A ovako package.json fajl:

Pogledajte ceo kod startnog projekta na GitHub-u https://github.com/choslee/node_typescript_doceker_starter


Node.js EventEmitter

Razumevanje EventEmitter-a u Node.js

Node.js je popularna platforma koja omogućava izgradnju skalabilnih aplikacija kroz asinhroni, događajno vođeni pristup. Jedan od ključnih mehanizama koji omogućava ovaj pristup jeste EventEmitter. Da bismo razumeli njegovu važnost i funkcionalnost, zamislimo EventEmitter kao središte za komunikaciju unutar Node.js aplikacije, gde se događaji šalju, primaju i obrađuju.

Šta je EventEmitter?

EventEmitter je klasa unutar Node.js events modula koja omogućava objektima da emituju događaje i reaguju na njih. To je temelj na kojem Node.js gradi svoj asinhroni, događajno vođeni model programiranja. Možemo ga zamisliti kao sistem za razmenu poruka, gde se poruke (događaji) šalju i obrađuju u realnom vremenu.

megafon

Kako EventEmitter funkcioniše?

Predstavite EventEmitter kao radijsku stanicu koja emituje razne programe (događaje). Slušaoci (handlers ili listeners) se “prijavljuju” da slušaju određene programe registracijom funkcija koje će biti pozvane kada se određeni događaj emituje. Kada radio stanica emituje program, svi registrovani slušaoci za taj program automatski “reaguju” – u našem slučaju, funkcije se izvršavaju.

Primeri upotrebe

EventEmitter se široko koristi unutar Node.js ekosistema. Na primer, u web serverima kreiranim pomoću Node.js-a, EventEmitter se koristi za obradu HTTP zahteva. Kada server primi zahtev, emituje se događaj, a odgovarajući listeneri reaguju na taj događaj, omogućavajući asinhronu obradu zahteva.

Kreiranje i korišćenje EventEmitter-a

Da bismo koristili EventEmitter, prvo ga uvozimo iz events modula. Zatim kreiramo instancu EventEmitter-a. Na ovoj instanci možemo emitovati događaje i registrovati slušaoce koji će reagovati na te događaje. Primer koda ispod ilustruje osnovnu upotrebu:

Ovaj dogadjaj može da se “osluškuje” tako što će se “emitter objekat” prijaviti/registrovati na njega:

Ukoliko se “opaljuje” više istih dogadjaja jedan za drugim a nama je iz nekog razloga potrebno da se samo jednom reaguje na dogadjaj, to se postiže korišćenjem metode once():

Kada koristimo “once” metodu onda će naš sistem samo jednom reagovati na ovaj tip poruke:

Zaključak

EventEmitter je snažan alat u Node.js koji omogućava efikasnu komunikaciju i obradu događaja unutar aplikacija. Kroz događajno vođeni model, aplikacije mogu efikasno reagovati na različite ulazne podatke i signale, što doprinosi visokoj skalabilnosti i performansama. Razumevanje i efikasna upotreba EventEmitter-a ključni su za izgradnju robustnih Node.js aplikacija.


Kreiranje servera sa Node.js


Kreiranje servera bez biblioteke

node.js server

Za kreiranje servera je potreban http/https objekat koji je ugradjen u core node.js-a te ga možemo importovati kao modul. Postoje dve opcije modula:

  • http – Http je jedan od ugrađenih modula koji dolazi sa Node.js i omogućava kreiranje servera za prihvatanje zahteva od klijenta i vraćanje odgovora
  • https – Https modul uključuje sve osnovne karakteristike http-a, ali sa dodatnim opcijama za rukovanje potrebnim bezbednosnim razlikama poput sertifikata.

Kada importujemo jedan od ova dva modula, on će nam vratiti “http/https” objekat, kao u sledećem primeru:

Sada kada imamo http objekat iskoristićemo ga za kreiranje servera koristeći metodu createServer():

Da bi server započeo “osluškivanje” zahteva moramo ga startovati, to se radi pozivanjem metode listen():

Metoda listen() prihvata četri opciona parametra, a mi ćemo koristi samo prvi parametar koji definiše port:

Zahtev ka serveru se upućuje sa ove adrese "http://localhost:3000". Za zaustavljanje rada server možemo korisiti njegovu metodu close().

Callback za reagovanje na zahtev tzv. requestListener()

U prethodnom primeru je kreiran server ali nema nikakvu funkcionalnost, da bi ovaj server ipak reagovao na neki zahtev potrebno je da mu se kreira neki event listener koji će osluškivati zahteve ka serveru. To možemo uraditi na standardni tzv. event-based način sa metodom on():

Medjutim ovakav pristup se najčešće ne koristi (mada je potpuno validan), već se metodi createServer() kao parametar prosedjuje funkcija (tzv. requestListener()) koja reaguje na zahtev i koja sama automatski dodaje “request” event.

zbog jednostavnosti najbolje je koristi anonimnu funkciju:

Prihvatanje GET zahteva i odgovor

Prihvatanje zahteva

Uz zahtev serveru se šalje request objekat i njemu možemo da pristupimo kroz parametar “request”. To je prilično “veliki” objekat koji nosi puno informacija u svojim svojstvima i metodama, pa ih možemo iskoristi za sadržaj body-ja odgovora (npr. svojstva method, url, headers):

Parsiranje query-ja iz url adrese

Ukoliko je zahtev pošiljaoca specifičan on će ga proslediti kroz url (npr. http://host:8000/?name=Pera), te je stoga neophodno parsirati url i izvući podatke iz njega. Za parsiranje ćemo koristi node.js-ov modul “url”.

Odgovor servera

a) API based odgovor

Za kreiranje osnovnog odgovora (response) ćemo koristi svojstva response objekata: statusCode i njegove metode: setHeader(), write() i end():

Za odgovor servera možemo iskoristi neke od podatak koje dobijamo iz request objekta:

U prethodnom primeru je definisan najjednostavniji header odgovora, ovde možete pogledati kako može da izgleda standardni header.

NAPOMENA:
U prethodnom primeru možemo iskoristili prednosti koje donosi destrukturiranje objekta tako da veoma jednostavno kreiramo promenjive headers, method i url. Pogledajte ovde kako bi izgledao prethodni primer kada bi se koristilo destruktuiranje objekta, više o destrukturiranju objekta pogledajte u članku “Destruktuiranje u JavaScriptu”

b) HTML odgovor

U prethodnim primerima su odgovori (response) bili tzv. “API based”, ali ako hoćemo da vratimo html onda ćemo to uraditi slično sledećem primeru:

Ako je u prethodnom primeru url sa kojom pošiljalac šalje zahtev tipa http://localhost:3000/?name=Pera, onda će pošiljalac dobiti odgovor “Zdravo Pera” u suprotnom će dobiti odgovor “Hello World”.

Primanje podataka poslatih sa POST/PUT metodom

Pored get metode koja vidljivo šalje podatke u okviru url-a, možemo “nevidljivo” poslalti podatke serveru ukoliko koristimo POST metodu. Obično se ovaj metod koristi kada šaljemo prikupljene podatke iz forme. U ovome primeru formu ćemo generisati na serveru kada klijent pošalje zahtev za stranicu sa url-om: http://localhost:3000:

Rezult ovog zahteva na klijentu ovako izgleda:

forma POST

Kada korisnik unese podatke u formu i klikne na dugme “Save”, zbog definisanog HTML atributa “action” će se podaci iz forme poslati na stranicu sa url-om /message, i uz njega će se kreirati novi zahtev koji će proslediti podatke. Prikupljeni podaci iz forme se šalju kao “readable streams” i mogu izgledati ovako:

Šta su STREAMS?
Streams su način za efikasno rukovanje bilo kojom vrstom razmene informacija (koristi se takodje za čitanje/pisanje datoteka, mrežnom komunikacijom…). Streams nisu koncept jedinstven samo za Node.js, već postoje u okviru operativnog sistema Unix više decenija. Za razliku od tradicionalanog načina gde se datoteka cela učita u memoriju pa se tek onda obrađuje, ovde pri korišćenju streams program čita deo po deo podataka i simultano ga obrađuje, te na taj način uopšte ne opterećuje memoriju. Pored pomenute prednosti gde se efikasno koristi memorija jer ne trebate učitavati velike količine podataka u memoriju da biste mogli da ih obradite, postoji i druga prednost tzv. “vremenska efikasnost” koaj smanjuje potrebno vremena za početak obrade podataka, jer obradu možete započeti čim dobijete deo podataka, umesto da čekate da budu svi podaci budu dostupni. Pročitajte više o streams u članku Node.js Streams

Da bi znali u kome trenutku se šalju podaci i kada je prenos podataka završen za to postoje registrovani dogadjaji. “Readable streams” imaju registrovane sledeće tipove dogadjaja:

  • data (ovaj dogadjaj se emituje kad god stream prosledi deo podataka tzv. “chunk”)
  • end (ovaj dogadjaj se emituje kad god stream nema više podataka da prosledi)
  • error
  • close
  • readable

U sledećem primeru ćemo se prijaviti na “data” dogadjaj za obaveštenje u kome trenutku se šalju podaci, a da bi smo znali kada je kraj poruke takodje se moramo prijaviti i na “end” dogodjaj:

Promenjiva body bi po prijemu podataka mogla ovako izgledati:

Kada imamo podatke u ovome obliku potrebno je da ih parsiramo, a za to ćemo koristi node.js-ov modul “querystring” i njegovu metodu parse() koja će podatke u ovom obliku prebaciti u kolekciju “key/value” parova:

U konzoli će biti štampan sada nama čitljiv kod:

Ovde može te pogledati ceo prethodni primer objedinjen kao i primer u kome se concat-uje buffer (chunk) u niz.

Kreiranje servera sa Express bibliotekom

Express je minimalistički framework za Node.js koji pojednostavljuje proces kreiranja servera i rukovanja HTTP zahtevima. Da bismo započeli, prvo moramo instalirati Express pomoću npm:

Nakon što je Express instaliran, možemo ga importovati i koristiti za kreiranje servera. U sledećem primeru ćemo kreirati jednostavan server koji će odgovarati na GET zahtev:

U ovom primeru, Express aplikacija (app) koristi app.get() metodu za rukovanje GET zahtevima na osnovnom URL-u (“/”). Metoda res.send() šalje odgovor klijentu. Server se pokreće pomoću metode app.listen() koja osluškuje zahteve na definisanom portu (u ovom slučaju, 3000).

Prihvatanje POST zahteva i slanje odgovora

Pored GET zahteva, možemo koristiti Express za rukovanje POST zahtevima. U sledećem primeru, kreiraćemo rutu koja prihvata POST zahtev i šalje odgovor:

Ovde koristimo app.use(express.json()) middleware za parsiranje JSON podataka iz tela zahteva. Kada klijent pošalje POST zahtev na “/data” rutu, server će prihvatiti podatke i poslati JSON odgovor nazad klijentu.

Dodavanje ruta i rukovanje greškama

Express omogućava lako dodavanje različitih ruta i rukovanje greškama. Na primer, možemo dodati rutu za “/about” i kreirati middleware za rukovanje 404 greškama:

Ovim pristupom, naš server može rukovati različitim rutama i vratiti odgovarajuće odgovore. Middleware za 404 greške se koristi za hvatanje svih nepostojećih ruta i slanje odgovarajuće poruke klijentu.

Express framework nudi mnogo više mogućnosti i fleksibilnosti, ali ovo su osnovni koraci za kreiranje jednostavnog servera. Više informacija možete pronaći u zvaničnoj dokumentaciji.

×

×

Destruktuiranje objekta request:

×

Primer response header-a:

×

Primer concat-ovanja bufera

×

Primer


Uvod u Node.js

Šta je node.js?

JavaScript je programski jezik koji ne može da se samostalno koristi već je za njegovo izvršavanje potrebno specijalno okruženje tzv. “JavaScript runtime environment”. Najpoznatiji “JavaScript runtime environment” je browser, ali ukoliko želimo da izvršavamo JavaScript i van browser-a tu na scenu nastupa node.js kao drugi “JavaScript runtime environment”. Node.js je open-source, cross-platform JavaScript runtime environment koji se bazira na Chrome-ovom V8 JavaScript engine-u.
Pošto je Node.js tzv. “low-level” platforma za jednostavniju upotrebu je pametno koristiti neki od mnogobrojnih framework-a kao što su: Express, Koa, Total.js, Sails….

Node.js je JavaScript runtime environment i omogućava rad sa JavaScript-om i van browser-a!

Node.js teži neblokirajućem i asinhronom načinu programiranja i u isto vreme može da obavlja više stvari odjednom, za razliku od kombinacije PHP/Apache koja funkcioniše putem zahteva i odgovora. PHP/Apache server obrađuje zahtev za datotekom tako što šalje task u file system računara, tu sačeka dok se sistem datoteka otvori i pročita datoteku, a zatim kao rezultat vraća traženi sadržaj klijentu. Tek nakon ovoga je spreman da odgovori na sledeći zahtev.

Kada Node.js obrađuje zahtev za nekom datotekom, on šalje task u file system računara i odmah nakon toga je spreman da prihvati na sledeći zahtev. U medjuvremenu file system otvori i pročita datoteku, nakon čega server vraća sadržaj klijentu. Upravo ovo je najveća prednost Node.js servera, jer eliminiše čekanje i jednostavno nastavlja sa sledećim zahtevom. UI vođen-događajima ga čini savršenim za aplikacije koje rade u realnom vremenu i iziskuju veće količine prenosa podataka. Node.js je baziran na tome da uglavnom veći deo koda samo sedi u pozadini i čeka da se desi neki I/O (Ulaz/Izlaz), kao što su čekanje da se fajl upiše na disk ili da MySql upit vrati podatke. Kada zahtevamo da se otvori fajl, ne čekamo na rezulat nego kažemo kojoj funkciji da prosledimo podatke kad je citanje gotovo ili koji događaj da pozove, a izvršavanje drugog koda se nastavlja. Više o “JavaScript runtime environment” možete pogledati u članku “Simbioza JavaScripta i njegovog okruženja”.

Razlike izmedju Node.js i Browser-a

Pored to toga što obe platforme omogućavaju izvršavanje JavaScript-a takodje postoje i razlike medju njima:

  • Node.js može da kreira server – Node.js ima ugrađene biblioteke za HTTP i socketkomunikaciju sa kojima može da kreira web server i tako bude zamena za druge tipove servera kao npr. Apache, Nginx…
  • Node.js koristi CommonJS sintaksu sa kojom omogućava modularno programiranje – Iako u okviru ES6 postoji sitaksa za rad sa modulima tzv. import/export sintaksa, ona je u node.js-u tek odnedavno ubačena i to kao eksperimantalna opcija. Za rad sa modulima Node.js koristi CommonJS sintaksu (više o ovome možete pročitati u članku Modularno programiranje – eksterna sintaksa).
  • Node.js nema DOM, window objekat, document objekat, cookies… – Node.js nema API-je pretraživača vezane za DOM, CSS, performansame, document, kao i sve API-je povezane za “window” objekat. Upravo zbog nedostatka window objekta koji predstavlja globalni objekat dostupan svima, u Node.js-u takodje postoji novi globalni objekat koji je dostupan su u svim modulima i ne moramo da ga uključujemo specijalno u našu aplikaciju, već ga možemo direktno koristiti svuda. Ovaj globalni objekat sadrži mnoštvo korisnih svojstava okruženja kao: setImmediate(), setInterval(), clearTimeout(), console i process. Ovaj objekat možemo da pregledamo jednostavnim izlistavanjem u konzoli sa:

  • Napomena:
    Sledeće promenljive (__dirname, __filename, exports, require(), module) izgledaju kao da su deo globalne promenjive, ali nisu, one postoje kao deo modula.

  • Global namespace object – U Browseru, opseg najvišeg nivoa je globalni opseg, što znači da će ključna reč “var” unutar browser-a definisati novu globalnu promenljivu. U Node.js-u je ovo drugačije, jer opseg najvišeg nivoa nije globalni opseg, pa će ključna reč “var” unutar nekog modula Node.js-a biti dostupna samo za taj modul.
  • Node.js ima pristup fajlovima sistema u kome se nalazi – Node.js ima puni pristup sistemu kao i bilo koja druga izvorna aplikacija. To znači da može čitati i pisati direktno u ili iz sistema datoteka, takodje može imati neograničen pristup mreži, i može izvršavati softver…
  • Moderan JS u okviru Node.js – Budući da se razvoj JavaScript-a kreće veoma brzo, pregledači često kaskaju sa implementacijuom novih mogućnosti JavaScript-a tako da u Browser-u morate da koristite stariju verziju JavaScript-a, ali to ne važi za Node.js u njemu možete koristiti sav moderni ES6-7-8-9 JavaScript koji vaša verzija Node.js podržava.

Nedostaci Node.js-a

Node.js izvršava kod na jednom thread-u (niti), i to je ustvari jedna od najvećih prednosti u odnosu na druge opcije koje podržavaju multi-threading, jer upravo jedno-nitna priroda čini njegove programe izuzetno skalabilnim.

Šta znači da je aplikacija skalabilna?

Skalabilana aplikacija je ona koja sa istim kodom može obraditi mnogo veći broja nekih zahteva nego što je prvobitno planirano a bez trošenja dodatnih resursa, što nije slučaj kod multi-thread tipa aplikacije jer pri povećavanju obima posla dolazi do povećavanja potrošnje sistemskih resursa (RAM…). Više o radu Event loop-a možete pročitati u članku “Princip rada asinhronog JavaScript-a”

Upravo pomenuta prednost je i najveći nedostatak Node.js-a jer zbog takve prirode node.js-a, on nema mogućnost da neblokirajući obrađuje zadatke vezane za CPU. Objasnićemo ovaj problem kroz primer:
Kad Node.js primi zadatak vezan za CPU, on sav CPU resurs isporučuje tom zadatku i uzima da ga odmah obradi, a tek po završetku odgovara na druge zahteve u loop-u što dovodi do spore obrade i kašnjenja u “event loop-u”.

Da li postoji rešenje za ovaj problem?

U novijim Node.js verzijama (>10.5.0) se pojavio novi modul pod nazivom “Worker threads” koji omogućava postojanje više paralelnih niti za izvršavaje JS-a, što pomaže u izvođenju CPU intenzivnih JS operacija. Međutim i dalje postoje odredjeni uslovi jer se “Worker threads” mogu izvoditi samo na mašinama sa više jezgara (jer Node.js omogućava upotrebu samo jedne niti po jezgru), a ne može se preskočiti ni činjenica da je ova funkcija i dalje eksperimentalna u trenutno aktivnim Node.js verzijama.


Instalacija node.js

Da bi instalirali node.js na lokalnoj mašini neophodno da se imamo instaliran Python 2.7, pa tek onda možemo download-ovati node.js (npr. odgovarajuću windows installer verziju “.msi”) sa https://nodejs.org/download stranice.
Instalacija je standardna a po završetku se dobijaju sledeće stvari:

  • node.js
  • dokumentacija za node.js
  • node.js command prompt
  • npm package manager

Provera instaliranih verzija node.js i njegovog package manager-a npm može da se izvrši u command promptu sa naredbama:

node -v

npm -v

Pored instalacije na lokalnoj mašini koja se koristi za razvoj aplikacje, po završetku aplikacije je potrebno prebaciti aplikaciju na neki hosting koji ima podršku za node.js. Ovaj postupak zavisi od same konfiguracije hosting-a i opcionog sofrvera koji je instaliran na njemu (C-panel, Plesk…). Ukoliko imate na hosting mašini instaliran Plesk ovde možete pročitati kako se obezbedjuje rad node.js na takvom serveru.