Skip to content

Instantly share code, notes, and snippets.

@bydga
Last active November 4, 2015 15:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save bydga/de74580cc8a042391add to your computer and use it in GitHub Desktop.
Save bydga/de74580cc8a042391add to your computer and use it in GitHub Desktop.
Krok za krokem nodejs app

Workshop - tvorba apliakce krok za krokem

Struktura nodejs aplikace (projektu)

/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

package.json

  • 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...


Jdeme na to

příkazová řádka, xterm, iTerm, cmd.exe...

1. založení struktury projektu

mkdir workshop-app
cd workshop-app
mkdir public
mkdir public/css
mkdir public/js
mkdir lib
mkdir views
touch index.js

2. Vytvoření package.json

  • 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 :)

3. Instalace externích npm modulů

  • 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 aplikace npm install (bez parametrů)
  • npm přečte dependencies z package.json a stáhne všechny potřebné moduly (opět do node_modules/
  • můžeme se podívat do package.json, uvidíme nový klíč dependencies a v něm naše nainstalované moduly

4. Základní kostra index.js

  • 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)
})

5. Přidáme frontendové knihovny

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

6. Vytvoříme jednoduchou jade šablonu

  • 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

7.Routování statických souborů, bodyparser a index routa

  • 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 pod require("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)

8. Modul pro komunikaci s databází (mongem)

  • 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)
	})
}

9. require našeho database modulu a jeho použití

  • 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)
	})
})

10. Nyní by to chtělo rozhýbat frontend

  • 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 + '">&times; 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)
	})
}

11. Přidávání a mazání uživatelů

  • 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()

	})
}

12. HOTOVO!!

  • localhost:8090 - mělo by fungovat úplně vše!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment