/my-project # kořenový adresář apliakce
node_modules/ # adresář s nainstalovanými third-party moduly
index.js # soubor s naší aplikací
package.json # soubor obsahující meta-informace o aplikaci:
# název, entry point, url repozitáře, third-party závislosti
- Soubor s plain JSON objektem
- Součástí zdrojových souborů projektu (v kořenu)
- Jedniný potřebný k nasazení aplikace
- Standardně neupravujeme ručně
- Pracuje s ním příkaz
npm
Příklad:
{
"name": "analytics-backend",
"version": "1.5.14",
"main": "index.js",
"scripts": {
"test": "TEST=1 node test-index.js"
},
"repository": {
"type": "git",
"url": "git@git.ccl:analytics/analytics-backend.git"
},
"author": "Martin Bydžovský <martin.bydzovsky@socialbakers.com",
"license": "BSD",
"dependencies": {
"@sbks/hera": "^3.0.15",
"adctd": "^0.4.1",
"async": "0.9.0",
"node-statsd": "0.0.7",
"nodemailer": "1.0.3",
"redis-url": "0.2.0"
}
}
Každá závislost je identifikována názvem modulu. Název modulu odpovídá http://npmjs.org/package/async
Verze je potom identifikována semver
označením. Lze specifikovat rozsahy verzí, minimální patch verzi, maximální major verzi atd...
příkazová řádka, xterm, iTerm, cmd.exe...
mkdir workshop-app
cd workshop-app
mkdir public
mkdir public/css
mkdir public/js
mkdir lib
mkdir views
touch index.js
- Nikdy nekopírujeme
package.json
z jiného projektu! NIKDY! - Přináší to chyby, zapomeneme změnit jednu direktivu, druhou, zbydou nám zbytečné dependencies, špatně se to pak hledá...
- Máme npm!
npm init
- enter, enter, enter :)
npm install -S express body-parser jade mongoq
- Příkaz udělá několiv věcí:
- Stáhne tři vyjmenované moduly do složky
node_modules/
- Díky přepínači -S (--save) také upraví
package.json
a přidá tyto moduly do dependencies. - To je důležité, celý adresář node_modules včetně podadresářů nepatří do repozitáře aplikace!
- V momentě, kdy si někdo stáhne zdrojáky naší aplikace - samozřejmě nemá tyto moduly k dispozici
- Ale má
package.json
. Spustí v adresáři aplikacenpm install
(bez parametrů) - npm přečte dependencies z
package.json
a stáhne všechny potřebné moduly (opět donode_modules/
- můžeme se podívat do
package.json
, uvidíme nový klíč dependencies a v něm naše nainstalované moduly
- do (zatím prázdného souboru)
index.js
vložíme následující kód:
//modul pro webovou aplikaci
var express = require("express")
// port na kterém bude aplikace poslouchat
var port = 8090
console.log("Starting app...")
// vytvoření konkrétní express.js aplikace
var app = express()
// nastavíme jade jako renderovací engine pro tuhle express aplikaci a řekneme, kde jsou šablony
app.set('views', './views')
app.set('view engine', 'jade')
// ukázková GET routa
app.get("/test", (req, res) => {
res.json({hello:{"i-am": "here"}})
})
//apliakce začíná poslouchat na specifikovaném portu - odteď je živá
app.listen(port, () => {
console.log("App listening on ", port)
})
- nyní zpět do terminálu a
node index.js
Application listening on 8090
- prohlížeč: http://localhost:8090/test
Stáhneme a uložíme do dpovídajících souborů následující:
- js/jquery.min.js
https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
- js/bootstrap.min.js
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js
- css/bootstrap.min.css
https://bootswatch.com/slate/bootstrap.min.css
- vytvoříme prázdný soubor
public/js/frontend.js
- tam později vložíme javascript, který bude spuštěn v prohlížeči
- views/main.jade
doctype html
html
head
meta(charset='utf-8')
title Nodejs appka
link(rel='stylesheet', href='/css/bootstrap.min.css')
script(src='/js/jquery.min.js')
script(src='/js/bootstrap.min.js')
script(src='/js/frontend.js')
body
h1 Nodejs workshop
p Simple node.js + express + jade + bootstrap + mongo application
div#wrapper
- v node.js řešíme komplet webový server (nemáme directory listing jako má Apache...)
- express to umí - musíme mu ale říct, odkud co chceme zobrazovat (nechceme ukázat zdroják naší index.js, že...)
- stejně tak parsování HTTP POSTu body - ze streamu udělat čitelná data
- do
index.js
nahoru, hned podrequire("express")
:
//modul pro parsování HTTP POST body
var bodyParser = require("body-parser")
//helper modul pro práci s cestami
var path = require("path")
- dále pod
app.set('view engine', 'jade')
:
// middleware pro parsování HTTP POST pořadavků
app.use(bodyParser.urlencoded({extended: true}))
// middleware pro servírování statických souborů
app.use(express.static(path.join(__dirname, "public")))
// homepage (GET) routa
app.get("/", (req, res) => {
// .render říká, jaká (jade) šablona se má použít pro vyrenderování této routy
res.render("main")
})
- vyzkoušíme http://localhost:8090
- měli bychom vidět náš main.jade přeložený do html a poslaný prohlížeči (černá stránka s bílým textem)
- Připravil jsem pro vás běžící MongoDB databázi
- connstring:
mongodb://nodejs:nodejs@ds039351.mongolab.com:39351/bydga?connectionPool=5&w=1
- Ruční připojení z řádky (musíte mít mongo clienta):
mongo ds039351.mongolab.com:39351/bydga -u nodejs -p nodejs
- V ní existuje kolekce
userlist
s pár ukázkovými záznamy (dokumenty) - Nyní vytvoříme náš vlastní modul:
lib/database.js
- Do něj vložíme následující obsah:
//mongoq module (driver)
var mongoq = require("mongoq")
//funkce pro tvorbu ObjectIDs (primární klíče v mongu)
var ObjectID = mongoq.mongodb.ObjectID
// konkrétní navázané spojení do monga. Žije po celou dobu běhu aplikace (všech 5 connection pool spojení)
var mongo = mongoq("mongodb://nodejs:nodejs@ds039351.mongolab.com:39351/bydga?connectionPool=5&w=1")
// reference na konkrétní kolekci (kolekce = jako tabulka v SQL) do monga.
// kolekce uchovává dokumenty (1 dokument = jako 1 řádek v SQL)
var userCollection = mongo.collection("userlist")
//veřejný interface tohoto modulu
module.exports = {
}
- nyní přidáme funkci pro vylistování všech uživatelů do module.exports
/**
funkce vracející všchny uživatele z kolekce userlist
@param callback: funkce zavolaná při dokončení této operace. Signatura je callback(err, users)
*/
getAllUsers: (callback) => {
userCollection.find().toArray((err, items) => {
if (err) {
return callback(err)
}
callback(null, items)
})
}
- do naší
index.js
přidáme na začátek mezi requiry:
//náš vlastní modul pro práci s databází ze složky lib/
var database = require("./lib/database")
- a přidáme routu, která použije náš database modul a vrátí prohlížeči všechny uživatele:
// GET routa pro získání všech uživatelů (volána z aplikace AJAXem)
app.get("/users/list", (req, res) => {
database.getAllUsers((err, users) => {
if (err)
return res.json({error: err})
res.json(users)
})
})
- nyní můžeme v prohlížeči zkusit http://localhost:8090/users/list
- dostanete json s polem objektů - každý objekt reprezentuje jednoho uživatele
- přidáme do
views/main.jade
následující (dovnitř div#wrapper):
div#userList.col-md-4
h2 User List
table.table.table-striped.table-bordered
thead
th UserName
th.col-md-3 Action
tbody
- do souboru
public/frontend.js
vložíme:
// Jednoduchá hashmapa pro uchování aktuálně staženého senzmau uživatelů
var userListData = {}
// všechny operace nad DOMem musí být v jquery navěšeny na DOM-ready event
$(document).ready(function() {
//stáhneme a vyplníme tabulku uživatelů
populateTable()
})
// funckce volaná po každé úspěčné operaci (add/delete). Stáhne ze serveru všechny uživatele
// a vyplní je do HTML tabulky
function populateTable() {
// připravíme si html content pro tabulku
var tableContent = ''
// uděláme GET request na naše API
$.getJSON( '/users/list', function( data ) {
// není zde var = upravujeme globální proměnnou definovanou na začátku skriptu
userListData = {}
// projdeme odpověď ze serveru a pro každého uživatele vytvoříme řádek tabulky
for (var i=0; i<data.length; i++) {
// class linkshowuser a linkdeleteuser = na ty navěsíme onclick
// Do rel atributu si přidáme ID uživatele
// budeme ho podle toho pak hledat v userListData a při mazání
tableContent += "<tr>"
tableContent += '<td><a href="#" class="linkshowuser" rel="' + data[i]._id + '">' + data[i].username + '</a></td>'
tableContent += '<td><a href="#" class="btn btn-danger btn-xs linkdeleteuser" rel="' + data[i]._id + '">× delete</a></td>'
tableContent += "</tr>"
//uživatele si uložíme do lokální proměnné (cache)
userListData[data[i]._id] = data[i]
}
// upravíme body tabulky = nahradíme původní obsah aktuálně staženými uživateli
$("#userList table tbody").html(tableContent)
})
}
- nyní po načtení http://localhost:8090/ bychom měli vidět nějaké uživatele
- upravíme
database.js
a do module.exports přidáme dvě funkce:
/**
funkce která uloží nového uživatele do kolekce userlist
@param user: objekt uživatele, který bude uložen
@param callback: funkce zavolaná při dokončení této operace. Signatura je callback(err)
*/
insertUser: (user, callback) => {
userCollection.insert(user, (err) =>{
if (err) {
return callback(err)
}
callback(null)
})
},
/**
funkce která odstraní existujícího uživatele z kolekce userlist
@param userId: identifikátor uživatele k odsranění
@param callback: funkce zavolaná při dokončení této operace. Signatura je callback(err)
*/
deleteUserById: (userId, callback) => {
userCollection.remove({_id: ObjectID(userId)}, (err) => {
if (err) {
return callback(err)
}
callback(null)
})
}
- do
index.js
přidáme odpovídající routy:
// POST routa pro uložení nového uživatele (volána z aplikace AJAXem)
app.post("/users/add", (req, res) => {
var user = {
username: req.body.username,
email: req.body.email,
fullname: req.body.fullname,
age: req.body.age,
location: req.body.location,
gender: req.body.gender
}
database.insertUser(user, (err) => {
if (err)
res.json({error: err})
else
res.json({result: "ok"})
})
})
// DELETE routa pro smazání uživatele (volána z aplikace AJAXem)
app.delete("/users/delete/:id", (req, res) => {
var userId = req.params.id
database.deleteUserById(userId, (err) => {
if (err)
res.json({error: err})
else
res.json({result: "ok"})
})
})
- přidáme do
main.jade
ovládací prvky:
do div#wrapper, před div#userlist:
div#userInfo.col-md-3
h2 User Details
table.table
tr
td.col-md-1
strong Username:
td#userInfoUsername
tr
td.col-md-1
strong Email:
td#userInfoEmail
tr
td.col-md-1
strong Full Name:
td#userInfoFullname
tr
td.col-md-1
strong Age:
td#userInfoAge
tr
td.col-md-1
strong Gender:
td#userInfoGender
tr
td.col-md-1
strong Location:
td#userInfoLocation
do div#wrapper, za div#userlist:
div#addUser.col-md-3
h2 Add New User
p
input#inputUserName.form-control(type='text', placeholder='Username')
p
input#inputUserEmail.form-control(type='text', placeholder='Email')
p
input#inputUserFullname.form-control(type='text', placeholder='Full Name')
p
input#inputUserAge.form-control(type='text', placeholder='Age')
p
input#inputUserLocation.form-control(type='text', placeholder='Location')
p
input#inputUserGender.form-control(type='text', placeholder='Gender')
p
button#btnAddUser.btn.btn-success Add User
- a nakonec
frontend.js
javascript, který s nimi bude pracovat: - dovnitř do $(document).ready handleru:
//navěsíme události na jednotlivé odkazy
// obyčejný elm.click(function(){}) nestačí, protože by se nezaregistroval pro
// elementy vytvořené později javascriptem
$("#userList").on("click", ".linkshowuser", showUserInfo)
$("#btnAddUser").on("click", addUser)
$("#userList").on("click", ".linkdeleteuser", deleteUser)
- a někam nakonec souboru tyto 3 metody:
// Obsluha události kliknutí na jméno uživatele - zobrazíme detaily do levé tabulky
function showUserInfo(event) {
// zastavíme defaultní akci (a href=# by skočilo na HTML začátek stránky)
event.preventDefault()
// z rel atribudu linku si vytáhneme id uživatele
var id = $(this).attr('rel')
// najdeme ho v lokální cachi
var user = userListData[id]
// vyplníme jednotlivé informace u tomto uživateli
$("#userInfoUsername").text(user.username)
$("#userInfoFullname").text(user.fullname)
$("#userInfoEmail").text(user.email)
$("#userInfoAge").text(user.age)
$("#userInfoGender").text(user.gender)
$("#userInfoLocation").text(user.location)
}
// Obsluha události new user
function addUser(event) {
// připravíme si objekt nového uživatele který pošleme na server
var newUser = {
username: $("#inputUserName").val(),
email: $("#inputUserEmail").val(),
fullname: $("#inputUserFullname").val(),
age: $("#inputUserAge").val(),
location: $("#inputUserLocation").val(),
gender: $("#inputUserGender").val()
}
//zkontrolujeme že jsou všechna pole vyplněna
for (var prop in newUser)
if(!newUser[prop])
//return ukončí celou funkci..
return alert("Please fill in all fields")
//uděláme AJAXem HTTP POST na server s daty nového uživatele
$.ajax({
type: "POST",
data: newUser,
url: "/users/add"
}).done(function( response ) {
if (response.error)
return alert("Error from server: " + response.error)
//vyprázdníme všechna input pole
$("#addUser input").val("")
//znovy vyplníme HTML tabulku s uživateli
populateTable()
})
}
// Obsluha události delete user
function deleteUser(event) {
// zastavíme defaultní akci (a href=# by skočilo na HTML začátek stránky)
event.preventDefault()
// opravdu chceme mazat? Jednoduchý JS popup
var confirmation = confirm("Are you sure you want to delete this user?")
if (confirmation === false) {
//return ukončí volání delého deleteUser
return false
}
var userId = $(this).attr("rel")
//uděláme AJAXem HTTP DELETE požadavek na server
$.ajax({
type: "DELETE",
url: "/users/delete/" + userId
}).done(function( response ) {
if (response.error)
return alert("Error: " + response.error)
// aktualizujeme tabulku po úspěšném smazání
populateTable()
})
}
- localhost:8090 - mělo by fungovat úplně vše!