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.:
1 2 3 |
node_project/ ├── api/ # Ovaj za mikroservise ├── data/ # Ovaj folder za bazu podataka |
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:
1 2 |
cd api npm init -y |
Nakon toga će package.json fajl izgledati ovako:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "name": "api", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } |
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:
1 |
"main": "src/app.js" |
Typscript podrška
-
TypeScript
Prvo je potrebno da instaliramo sam TypeScript:
1npm install typescript --save-dev -
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
1npx tsc --initPrimer
12345678910111213141516171819202122{"compilerOptions": {"target": "ES6", // Ciljni ECMAScript standard"module": "commonjs", // Modul sistem koji se koristi"strict": true, // Omogućava striktan mod"esModuleInterop": true, // Pomaže u interoperabilnosti sa ECMAScript modula"skipLibCheck": true, // Preskače proveru tipova u fajlovima definicija"outDir": "./dist", // Izlazni direktorijum za kompajlirane fajlove"rootDir": "./src", // Korenski direktorijum za ulazne fajlove"resolveJsonModule": true, // Omogućava import JSON fajlova kao module"sourceMap": true, // Generiše sourcemap fajlove"noImplicitAny": true, // Onemogućava neizričit "any" tip"moduleResolution": "node", // Modul rezolucija strategija koja se koristi"baseUrl": ".", // Osnovna putanja za rezoluciju modula"paths": { // Mape za rezoluciju modula"@app/*": ["src/app/*"],"@config/*": ["src/config/*"]}},"include": ["src"], // Direktorijumi/fajlovi koji se uključuju u kompajliranje"exclude": ["node_modules", "dist", "test"] // Direktorijumi/fajlovi koji se isključuju iz kompajliranja} -
ts-node
Da bi pokrenuli TypeScript direktno iz Node okruženja bez potrebe za prethodnim kompajliranjem u JS, potrebno je instalirati ts-node paket:
1npm install ts-node --save-devZatim kreiramo jednostavnu “start” skriptu za pokretanje aplikacije u produkciji:
123"scripts": {"start": "ts-node src/app.ts"}Pozivanjem naredbe iz terminala se pokreće aplikacija:
1npm start -
@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.
1npm install @types/node --save-devAko 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
1npm install @types/express --save-dev -
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:
1npm install --save-dev nodemon
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:
1 2 3 |
"scripts": { dev": "nodemon --watch src --exec ts-node src/app.ts" } |
Ova skripta se poziva iz terminala na sledeći način:.
1 |
npm run dev |
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:.
1 |
npm run build |
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:
1 2 3 4 5 6 |
node_project/ ├── api/ │ ├── src/ │ │ └── app.ts │ ├── .env │ ├── package.json |
Za učitavanje (environment) promenljivih iz .env fajla u process.env je potrebno da instaliramo dotenv paket:
1 |
npm install dotenv |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# Osnova slika za kontejner preuzima se iz DockerHub-a FROM node:20-alpine # Setujemo radni direktorijum u kontejneru WORKDIR /user/src/app # Kopiramo package.json i package-lock.json u radni direktorijum # (./ je radni direktorijum tj. app) COPY package*.json ./ # Instaliramo zavisnosti iz package.json fajla samo kada je produkcija # koristeći "clean install" (npm ci) koji prvo obriše node_modules folder RUN npm ci --only=production # Kopiramo sve fajlove iz trenutnog direktorijuma u radni direktorijum COPY . . CMD [ "npm", "run", "dev" ] |
Više o Docker-u i njegovom korišćenju pročitajte u članku “Docker: Pokretanje aplikacija svuda”
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:
1 |
docker build [OPTIONS] PATH | URL | - |
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).
1docker build -t my-image:latest . - –file skraćeno -f: Specifikuje putanju do Dockerfile-a ako nije u trenutnom direktorijumu.
1docker build -f /path/to/Dockerfile . - –build-arg: Prolaz promenljive kao build argument. Ove promenljive se mogu koristiti unutar Dockerfile-a.
1docker build --build-arg HTTP_PROXY=http://proxy.example.com . - –no-cache: Onemogućava korišćenje keša za build, osiguravajući da se svaki sloj ponovo izgradi.
1docker build --no-cache -t my-image:latest . - –pull: Uvek povlači najnoviji osnovni image pre build-ovanja.
1docker build --pull -t my-image:latest . - –label: Dodaje metapodatke (label-e) image-u.
1
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
1 |
docker build -t my-image:1.0 -f /path/to/Dockerfile --build-arg HTTP_PROXY=http://proxy.example.com --no-cache --pull --label version="1.0" --label maintainer="[email protected]" . |
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:
1 |
docker images |
Docker skripte
Ako želimo da ubrzamo rad sa Dockerom, možemo da pripremimo skripte u package.json kao npr.
1 2 3 |
"docker:build": "docker build -t starter_node:latest .", "docker:run": "docker run --name node_api starter_node:latest", "docker:stop": "docker stop node_api && docker container prune -f" |
Objašnjen
- Skripta docker:build kreira Docker sliku iz Dockerfile-a, pos imenonm “starter_node:latest”
- Skripta docker:run pokreće novi kontejner, dodeljuje mu ime “node_api” na osnovu slike “starter_node”
- 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:
1 2 3 4 5 6 7 8 9 10 |
node_project/ ├── api/ │ ├── src/ │ │ └── app.ts │ ├── dist/ │ │ └── app.js │ ├── .env │ ├── package.json │ └── Dockerfile ├── data/ # Ovaj folder za bazu podataka |
A ovako package.json fajl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
{ "name": "api", "version": "1.0.0", "description": "", "main": "src/app.ts", "scripts": { "start": "ts-node src/app.ts --env-file=.env.production", "build": "tsc", "dev": "nodemon --watch src --exec ts-node src/app.ts --env-file=.env.development", "docker:build": "docker build -t starter_node:latest .", "docker:run": "docker run --name node_api starter_node:latest", "docker:stop": "docker stop node_api && docker container prune -f" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "dotenv": "^16.4.5", "express": "^4.19.2", "nodemon": "^3.1.0" }, "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^20.12.12", "ts-node": "^10.9.2", "typescript": "^5.4.5" } } |
Pogledajte ceo kod startnog projekta na GitHub-u https://github.com/choslee/node_typescript_doceker_starter