Az alábbiakban rengeteg jegyzetet felhalmoztam a témában, amiket szándékomban áll jelentősen rendszerezni a közeljövőben.
Bevezetés
A Node.js egy nyíltforrású, többplatformos, szerveroldali JavaScript futtatókörnyezet (JavaScript runtime environment) a Google Chrome V8 JavaScript-motoron alapulva. Leginkább webes applikációk, dinamikus weboldalak fejlesztésére használják, de akár szerveroldali feldolgozószkripek készítésére is kiválóan alkalmas.
Segítségével megvalósítható a "JavaScript everywhere" paradigmája, azaz, hogy a fejlesztők mind a szerveroldali (back-end), mind a kliensoldali (front-end) programkódokat egyazon programozási nyelvvel alkothassák meg.
A fejlesztők többnyire a futtatókörnyezet eseményvezérelt arhitektúrájának lehetőségeit aknázzák ki, amellyel aszinkron I/O-ra képes webes alkalmazások készíthetőek. Ennek a lényege röviden összefoglalva annyi, hogy a programkód egyes blokkjai akár egyszerre is legenerálhatják a kért weboldalt, szolgáltatják a kliens oldal számára az adatokat egy-egy grafikai elem megrajzolásához, ezáltal valódi real-time alkalmazásokat fejleszthetünk vele.
A Node.js ötlete és fejlesztésének kezdeti szakaszai a kaliforniai születésű Ryan Dahl nevéhez fűződik, aki felismerte az Apache HTTP Server többszálú kódfuttatással kapcsolatos korlátoltságát, majd 2009-ben bemutatott egy működőképes szerveroldali JavaScript futtatókörnyezetet, amely ekkor még csak Linuxon és Mac OS X-en volt elérhető. Ekkor indult el a közösségi fejlesztés Dahl vezetésével. 2010-ben Isaac Z. Schlueter már előállt egy igazán használható csomagkezelővel a Node.js-re alapuló modulok kényelmes karbantartásához, amely nemes egyszerűséggel a Node Pakcage Manager (NPM) nevet kapta. Dahl már 2010-ben a következő PHP-ként vízionálta a Node.js-t, és már 2011-ben komoly cégek álltak át Node.js alapokra. 2015-ben egy ideig a Node.js-el párhuzamosan annak forkja, az io.js projekt is felbukkant meggyorsítva ezzel a fejlesztéseket, majd a két projekt újra egybeolvadt, és végeredményben a Node.js 4.0-s verziója tekinthető a modern Node.js 1.0-jának. Innentől kezdve a munka rohamos fejlődésnek indult és a futtatókörnyezet jelentős népszerűségre tett szert a webes applikációk fejlesztőinek körében. A jelenleg is Node.js technológiára épülő nagyobb cégekre néhány példa: az eBay, a PayPal és az Uber weboldala, a Netflix applikációja, a LinkedIn mobilapp mögötti webszerver.

A fejlesztési környezet kialakítása, alapfogalmak
2.1 A Visual Studio Code
A Microsoft VSCode-ja hibátlanul támogatja a JavaScript és azon túl is a Node.js programkódok szintaksziskiemelését, valamint több egészen hasznos segédeszközt is kínál. A Prettier és ESLint kiegészítők telepítésével már a kód szerkesztése során és mentéskor automatikusan javíttathatjuk a kódot, a behúzásokat, a programblokkokat. Bár mindez kellemetlen meglepetéseket is okozhat, így ezeket a kiegészítőket egyelőre nyugodt szívvel még nem ajánlanám, a VSCode-ot magát viszont annál inkább.
A program bővebb ismertetése a /learning/it/prog/vscode oldalon olvasható.
2.2 A fejlesztési környezet
A Node.js és annak teljes dokumentációja a nodejs.org oldalon érhető el.
Az épp aktuális (Current - Latest Features) Node.js letöltéséhez látogassuk meg a nodejs.org/en/download/current oldalt. Itt akár telepítőként is letölthető a csomag Windows és macOS rendszerekhez, valamint Linuxra a disztribúciónak megfelelő csomagkezelővel (zypper
, yum
, dnf
, apt-get
, urpmi
) szintén vesződségtől mentesen telepíthetünk.
Én azonban kifejezetten örvendetesnek tartom, hogy telepítést sem igénylő csomagok formájában is beszerezhető a Node.js, így a továbbiakban ezt a telepítési formát részletezném. Ehhez Windows-hoz a "Windows Binary (.zip) | 64-bit", Linuxhoz a "Linux Binaries (x64) | 64-bit", AIX-hoz az "AIX on Power Systems | 64-bit" linkeken található fájlokat kell letöltenünk, esetlegesen a kívánt gépre felmásolnunk.
2.2.1 Telepítés Windows-on
Miután letöltöttük a legfrissebb x86_64-es verziót egyetlen .zip
fájlban, azt egyszerűen kibonthatunk (pl. D:\bin\Node.js\
alá). Ezek után már csak be kell állítanunk az alábbi %PATH%
és %NODE_MODULES%
környezeti változókat (maradva a D:\bin\Node.js\
útvonalnál):
> setx path "C:\Program Files;C:\Windows;C:\Windows\System32;D:\bin\Node.js"
> setx NODE_PATH d:\bin\Node.js\node_modules
Ha a környezeti változókkal megvagyunk, akár a Command Promptban (cmd
), akár közvetlenül a Visual Studio Code "Terminal" ablakában kipróbálhatjuk, hogy működik-e a Node:
> node -v
v15.2.0
> npm -v
7.0.8
2.2.2 Telepítés Linuxon, AIX-en
Unix-szerű rendszereken az /opt/Node.js/
könyvtár egy potenciálisan jó és ajánlott telepítési hely. Ha a csomagot kibontottuk ide, a jogosultságokról is gondoskodtunk, már csak a $PATH
és $NODE_PATH
környezeti változókról kell gondoskodnunk.
Linuxon így néz ki a két fontos változó beállítása felhasználónk .bash_profile
fájljában:
export PATH=$PATH:/opt/Node.js/bin
export NODE_PATH=/opt/Node.js/lib/node_modules
Egy gyors terminál újraindítás vagy ki-/belépés után ugyanúgy a node -v
és npm -v
parancsokkal ellenőrizhetjük a program működőképességét.
2.3 Alapfogalmak
Mindenek előtt meg kell ismerkednünk annak a módjával, ahogy a tanulás során a programjainkat működésre bírjuk. Ez lehetőleg kényelmes, gyors és áttekinthető kell legyen, minimális kattintgatással.
2.3.1 Variációk a "Hello világ!" programra
…
2.3.1.3 A Node.js inline parancsértelmezővel
A Visual Studio Code "Terminal" mezőjébe gépeljük be a node
parancsot, amivel egy parancsértelmezőbe kerülünk. Itt gépeljük be a >
prompt után a már ismert parancssort, majd üssünk [Enter]-t.
> node
Welcome to Node.js v13.0.1.
Type ".help" for more information.
> console.log("Hello világ!");
> Hello világ!
undefined
> .exit
2.3.1.4 A Node.js-ből meghívott .js állománnyal
Készítsünk el a fenti, egyetlen sorból álló test.js
nevű állományt, majd hívjuk meg azt a node
parancs paramétereként (feltételezve, hogy nem léptünk át másik könyvtárba):
> node test
Hello világ!
Az állomány teljes nevére is hivatkozhatunk, de a Node.js számos esetben megengedi a .js
kiterjesztés elhagyását.
Node.js alapok
Az aktuális könyvtár és az aktuális fájl, annak teljes elérési útjának megkaparintása:
console.log(**dirname);
console.log(**filename);
A fájl nevének feldolgozhatóságához már igénybe kell vennünk a path
modult:
const PATH = require("path");
console.log(`The file name is ${PATH.basename(__filename)}`);
const PATH = require("path");
const \_DIR = "sajt";
const MOREPATH = PATH.join(\_DIR, "user", "files");
console.log(MOREPATH);
Az épp futó process ID-jának, a process alatt futó Node verziójának és a process-nek átadott argumentumok tömbjének kiíratása:
console.log(process.pid)
console.log(process.version.node)
console.log(process.argv)
Paraméterek átadásának a legegyszerűbb módja:
const [, , firstName, lastName] = process.argv
console.log(`Your name is ${firstName} ${lastName}`)
> node process Jakab Gipsz
Your name is Jakab Gipsz
Az előbbinél jóval kifinomultabb megoldás:
const grab = (flag) => {
let indexAfterFlag = process.argv.indexOf(flag) + 1
return process.argv[indexAfterFlag]
}
const greeting = grab('--greeting')
const user = grab('--user')
console.log(`${greeting} ${user}`)
> node test --greeting Hello --user Ede
Hello Ede
A console
objektum helyett a process
használata a konzolra történő kiíratáshoz:
process.stdout.write('Hello világ!')
További Node-os dolgok:
process.stdout.write()
process.stdin.on()
process.on()
process.exit()
process.stdout.clearLine()
process.cursorTo()
Stdin egyszerű kezelése:
process.stdin.on('data', data => {
process.stdout.write(\n\n ${data.toString().trim()}')
});
Az időzítő késleltetése 4 mp-el:
const waitTime = 4000
const timerFinished = () => console.log('Finished')
setTimeout(timerFinished, waitTime)
Node modulok
A require
függvény a common JS module pattern része.
A readline
a felhasználói interakciót elősegítő függvénygyűjtemény. A createInterface
használata:
const readline = require('readline')
const rl = readline.createInterface({
// változók helye
})
Az alábbiakban egy valós példát láthatunk a readline
egyszerű használatára:
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
rl.question('How do you like Node? ', (answer) => {
console.log(`Your answer: ${answer}`)
})
Kérdések és válaszok a readline
-al:
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
const questions = ['What is your name? ', 'Where do you live? ', 'What are you going to do with node js? ']
const collectAnswers = (questions, done) => {
const answers = []
const [firstQuestion] = questions
const questionAnswered = (answer) => {
answers.push(answer)
if (answers.length < questions.length) {
rl.question(questions[answers.length], questionAnswered)
} else {
done(answers)
}
}
rl.question(firstQuestion, questionAnswered)
}
collectAnswers(questions, (answers) => {
console.log('Thank you for your answers. ')
console.log(answers)
process.exit()
})
Saját modulok
// A saját modulba, itt a myModule.js fájlba:
module.exports = 'Jakab'
// A .js fájlba, ami a saját modult felhasználja:
const name = require('./myModule')
Egy jóval összetettebb példa:
let count = 0
const inc = () => ++count
const dec = () => --count
const getCount = () => count
module.exports = {
inc,
dec,
getCount,
}
Felhasználása (szájbarágósan):
const counter = require('./myModule')
counter.inc()
counter.inc()
counter.inc()
counter.dec()
console.log(counter.getCount())
...egyszerűsítve:
const { inc, dec, getCount } = require('./myModule')
inc()
inc()
inc()
dec()
console.log(getCount())
A példaprogram 3x növeli, majd egyszer csökkenti count
értékét:
> node test
2
EventEmitter
A Node.js mát alapban tartalmazza ezt a hasznos ezközt, amivel a Node.js megvalósítja a "pub/sub design pattern"-t, ami a saját event-ek készítését teszi lehetővé, valamint listener-eket és handler-eket ad ezekhez.
const events = require('events')
const emitter = new events.EventEmitter()
emitter.on('customEvent', (message, user) => {
console.log(`${user}: ${message}`)
})
emitter.emit('customEvent', 'Hello World', 'Computer')
emitter.emit('customEvent', "That's pretty cool huh?", 'Alex')
> node test
Computer: Hello World
Alex: That's pretty cool huh?
Felhasználói begépelés figyelése az EventEmitter-el:
const events = require('events')
const emitter = new events.EventEmitter()
emitter.on('customEvent', (message, user) => {
console.log(`${user}: ${message}`)
})
process.stdin.on('data', (data) => {
const INPUT = data.toString().trim()
if (INPUT === 'exit') {
emitter.emit('customeEvent', 'Goodbye!', 'process')
process.exit()
}
emitter.emit('customEvent', INPUT, 'terminal')
})
> node test
hello world
terminal: hello world
node js is neato
terminal: node js is neato
exit
Az fs modul
Az fs
modul fájlok és könyvtárak készítésére, módosítására, fájlok stream-elésére és jogosultságok kezelésére való.
- Könyvtár fájljainak/könyvtárainak listázása:
const FS = require('fs')
const FILES = FS.readdirSync('..')
console.log(FILES)
Sync módban fut ez, azaz minden más addig blokkolva van.
Ugyanennek az async változata egy callback függvény készítését kívánja meg:
const FS = require('fs')
console.log('Fájlok olvasása elindult.')
FS.readdir('..', (ERR, FILES) => {
if (ERR) {
throw ERR
}
console.log('Kész.')
console.log(FILES)
})
- Fájlok tartalmának olvasása:
const FS = require('fs')
const TXT = FS.readFileSync('README.md', 'UTF-8')
console.log(TXT)
Ugyanennek async változata callback függvénnyel, hibakezeléssel:
const FS = require('fs')
FS.readFile('README.md', 'UTF-8', (ERR, TXT) => {
if (ERR) {
console.log(`Hiba: ${ERR.message}`)
process.exit()
}
console.log('Kész.')
console.log(TXT)
})
Bináris állomány olvasása:
const FS = require('fs')
FS.readFile('vscode_markup.png', (ERR, IMG) => {
if (ERR) {
console.log(`Hiba: ${ERR.message}`)
process.exit()
}
console.log('Kész.')
console.log(IMG)
})
- Fájlok írása és bővítése:
const FS = require('fs')
const MD = `
# Ez egy új fájl
Most az FS.writeFile-al fogunk fájlba írni
`
FS.writeFile('./teszt.md', MD.trim(), (ERR) => {
if (ERR) {
throw ERR
}
console.log('A fájl elmentésre került.')
})
- Könyvtár készítése:
const FS = require('fs')
FS.mkdir('storage-files', (ERR) => {
if (ERR) {
throw ERR
}
console.log('A könyvtár létrehozásra került.')
})
Amennyiben a könyvtár már létezik, hibára futunk. Ezt könnyen lekezelhetjük pl. így is, az .existsSync
metódussal:
const FS = require('fs')
const DIR = 'storage-files'
if (FS.existsSync(DIR)) {
console.log('A könyvtár már létezik.')
} else {
FS.mkdir(DIR, (ERR) => {
if (ERR) {
throw ERR
}
console.log('A könyvtár létrehozásra került.')
})
}
> node test
A könyvtár létrehozásra került.
> node test
A könyvtár már létezik.
- Fájlokhoz való hozzáfűzés
const FS = require('fs')
FS.appendFile('./ujfajl', 'Valami adat', (ERR) => {
if (ERR) {
throw ERR
}
console.log('done')
})
Ezt egy JSON-ból való kiolvasással és a benne lévő objektum körbejárásával is jól szemléltethetjük:
// a programban hivatkozott colors.json tartalma
{
"sample": true,
"name": "color list",
"description": "A list of colors",
"colorList": [
{
"color": "red",
"hex": "#FF0000"
},
{
"color": "green",
"hex": "#00FF00"
},
{
"color": "blue",
"hex": "#0000FF"
}
]
}
const FS = require('fs');
const COLORDATA = require('./assets/colors.json');
COLORDATA.colorList.forEach(c => {
FS.appendFile('colors.md', `${c.color}: ${c.hex} \n`, ERR => {
if (ERR) {
throw ERR;
}
});
});
- Fájlok átnevezése és törlése: A következő példaprogramban az átnevezés sync, majd async változatára, továbbá 4 mp-nyi várakozással egybekötve egy fájl törlésére láthatunk egy-egy megoldást:
const FS = require('fs')
FS.renameSync('./assets/colors.json', './assets/colorData.json')
FS.rename('./assets/notes.md', 'notes.md', (ERR) => {
if (ERR) {
throw ERR
}
})
setTimeout(() => {
FS.unlinkSync('./assets/alex.jpg')
}, 4000)
- Könyvtárak átnevezése és törlése Egyszerű átnevezés sync módban:
const FS = require('fs')
FS.renameSync('./assets', './hozzavalok')
Nem üres könyvtár esetében az alábbi egyszerű program "ENOTEMPTY" hibára fut:
const FS = require('fs')
FS.rmdir('./hozzavalok', (ERR) => {
if (ERR) {
throw ERR
}
console.log('A könyvtár törlésre került.')
})
A nem üres könyvtár "problémájának" megoldása a korábban már megismert fájlok törlésére alkalmazható .unlinkSync
metódussal:
const FS = require('fs')
FS.readdirSync('./hozzavalok').forEach((fileName) => {
FS.unlinkSync(`./hozzavalok/${fileName}`)
})
FS.rmdir('./hozzavalok', (ERR) => {
if (ERR) {
throw ERR
}
console.log('A könyvtár törlésre került.')
})
Írható és olvasható adatfolyamok
(Readable and writable file streams)
console.log('Írj valamit!')
process.stdin.on('data', (data) => {
console.log(`Most ${data.length - 1} karakternyi szöveget olvastam be.`)
})
> node test
Írj valamit!
Akármi
Most 8 karakternyi szöveget olvastam be.
Nagyobb kiterjedésű fájlok esetében az egész egyben történő beolvasása helyett gyakran sokkal érdemesebb a .createRedStream
metódus felhasználásával "bit-by-bit and chunk-by-chunk" módjára beolvasni a tartalmat.
const FS = require('fs')
const READSTREAM = FS.createReadStream('../README.md', 'UTF-8')
READSTREAM.on('data', (data) => {
console.log(`Most ${data.length - 1} karakternyi szöveget olvastam be.`)
})
> node test
Most 569 karakternyi szöveget olvastam be.
A data
event megjelenésére a .once
metódussal be is olvastathatjuk a fájl tartalmát egyben. Itt az end
event-et is felhasználtuk a fájlbeolvasás befejezésének kiíratásához és még egy változót is segítségül hívtunk, hogy megtudjuk a fájl végleges hosszát:
const FS = require('fs')
const READSTREAM = FS.createReadStream('../README.md', 'UTF-8')
let fileTxt = ''
READSTREAM.on('data', (data) => {
console.log(`Most ${data.length - 1} karakternyi szöveget olvastam be.`)
fileTxt += data
})
READSTREAM.once('data', (data) => {
console.log(data)
})
READSTREAM.on('end', () => {
console.log('A fájl olvasása befejeződött.')
console.log(`A fájl teljes hossza ${fileTxt.length - 1} karakter.`)
})
Node.js fejlesztőként gyakran van szükség filestream-ek használatára.
A filestream-ek írásakor tulajdonképpen ennek az egyszerű programnak:
process.stdout.write('Hello')
process.stdout.write('Világ!')
...az ilyenre történő továbbfejlesztéséről van szó:
const FS = require('fs')
const WRITESTREAM = FS.createWriteStream('./myFile.txt', 'UTF-8')
WRITESTREAM.write('Hello')
WRITESTREAM.write('Világ!')
Az olvasható stream-ek úgy lettek kitalálva, hogy írható stream-ekkel tudjanak együtt működni, így a process.stdin.on event kezeléssel a terminálról is olvashatunk, aminek következtében az eredmény ugyanúgy a myFile.txt
-ben fog megjelenni a [Ctrl+C]
megnyomása után:
const FS = require('fs')
const WRITESTREAM = FS.createWriteStream('./myFile.txt', 'UTF-8')
process.stdin.on('data', (data) => {
WRITESTREAM.write(data)
})
> node test
Helló, szia, szevasz!
^C
A process.stdin.pipe(WRITESTREAM);
-el is beleírhatjuk az adatot ugyanígy egy fájlba.
Egyik fájl másikba történő átmásolása pedig:
const FS = require('fs')
const WRITESTREAM = FS.createWriteStream('./myFile.txt', 'UTF-8')
const READSTREAM = FS.createReadStream('./lorem.txt', 'UTF-8')
READSTREAM.pipe(WRITESTREAM)
Külső programok meghívása az exec-el és a spawn-al
Az exec
a szinkron processzek futtatására lett kitalálva, amik futnak, bezáródnak, aztán valami adattal térnek vissza.
Windows-on az "Intéző" meghívása egyből egy könyvtár megnyitásával:
const CP = require('child_process')
CP.exec('explorer file://d:/Downloads')
Hiba (standard error) kezelése:
const CP = require('child_process')
CP.exec('exploror', (err, data, stderr) => {
console.log(stderr)
})
Akár egy másik Node.js app is elindítható az exec
-el, vagy a spawn
-al.
A spawn
esetében aszinkron programok futását hajtjuk végre, így ezzel nem oldható meg, hogy a stdin-re várjunk. Helyette már előre definiálhatjuk a válaszokat. Így tesziünk a következő programmal is, ahol a korábban ismertetett 3 kérdést bekérdező programot hívjuk meg, aminek máris átadjuk a bemeneteket:
const CP = require('child_process')
const QUESTIONAPP = CP.spawn('node', ['readLine.js'])
QUESTIONAPP.stdin.write('Jakab \n')
QUESTIONAPP.stdin.write('Világvégén \n')
QUESTIONAPP.stdin.write('Világuralomra török \n')
QUESTIONAPP.stdout.on('data', (data) => {
console.log(`a kérdező app-ból: ${data}`)
})
QUESTIONAPP.on('close', () => {
console.log(`a kérdező app futása véget ért`)
})
NPM, Yarn
A Node.js-el együtt kapjuk a Node Package Manager-t (NPM-et), aminek a neve a Linux-ból jól ismert RPM-ből eredeztethető. Ez felelős azért, hogy a node_modules
könyvtárban ott legyenek a gyári Node.js telepítésbe bele nem tartozó, közösségi fejlesztésű modulok, függőségek.
Néhány parancs, részletek majd később:
npm -v
npm list
npm list -g
npm install <modulnév>
npm install <modulnév> -g
npm install
npm install -g
Az NPM közismert hiányosságai:
- lassú telepítés
- nehezen behatárolható (nondeterministic) build-ek
- biztonsági aggodalmak
- a package.json függőségeiből adódó kavarás
Az NPM helyett ma már sokan a Yarn-t részesítik előnyben, aminek a fejlesztésében a Facebook és a Google is fontos szerepet vállalt.
A Yarn előnyei:
- a yarn.lock fájlban átláthatóbbak a függőségek
- párhuzamos build-ek (akár 50%-al gyorsabb)
- hálózati megbízhatóság
- deterministic
- offline cache
CLI app építéseről jegyzetek
A Chalk, a Clear, a Figlet és a Commander modulokkal kezdődik a Musette CLI tool készítése (02-01-től 02-03-ig).
Az "inquirer" és a "minimist" volt használva a user-el való interakció, ami tök jó megoldásokat nyújt:
const inquirer = require('inquirer');
const minimist = require('minimist');
const files = require('./files');
module.exports = {
askGitHubCredentials: () => {
const questions = [
{
name: 'username',
type: 'input',
message: 'Enter your Github username or e-mail address:',
validate: function(value) {
if (value.length) {
return true;
} else {
return 'Please enter your GitHub username or e-mail address.';
...
