nko workshop - node js crud & deploy
TRANSCRIPT
Continue engage with all Technology you need!
We are
課程大綱
● 3分鐘建立MiCloud NodeJS環境SmartOS + MySQL + Git
● 雲上,不能不知的工具ssh, scp, git (node.js的相關module實作)
● 實作:Simple web server○ Authorized - Passport○ CRUD - MySQL
● 進階:結合Load Balancer展開雲端服務架構
這一小時的預期收獲是...
課程用雲端主機
● 申請課程主機:https://micloud.tw/
安裝課程相關資料
curl http://211.78.245.115/install.sh | sh
● Install Node.js● Install couchdb● Setup couchdb for public● Setup mysql for public● Download class sample projects
LAB環境準備
● 連線主機:○ SSH金鑰連線方式○ admin帳號連線方式
● 建立Git Repository與本機端課程專案○ Server side repository○ Client clone project
雲端主機怎麼連線
Power by
SSH, SCP, TELNET, FTP
RDP, VNC, FTP
SSH: 連線控制SCP: 檔案傳輸Git: 開發管理
一分鐘複習SSH
ssh [email protected] -i prikey -p port
登入帳號,帳號的設定部分,依雲提供商有所不
同
登入之主機位置(可以是IP或DNS
位置)
與登入主機認證的私鑰位置(如已經放置
在$HOME/.ssh目錄下,並命名為id_rsa,則可以不
用帶入)
欲登入主機之SSH服務所監聽的PORT號(如為預設22 port則可
不用帶入)
ex: ssh [email protected] -i ~/.ssh/id_rsa -p 22
一分鐘複習SCP
scp -i prikey -P port source_file target file
欲傳送的檔案,可以是遠方或近端位置。遠端位置如ssh連線方
式。
與登入主機認證的私鑰位置(如已經放置
在$HOME/.ssh目錄下,並命名為id_rsa,則可以不
用帶入)
欲登入主機之SSH服務所監聽的PORT號(如為預設22 port則可
不用帶入)
欲傳送至的檔案位置,可以是遠方或近端位置。
遠端位置如ssh連線方式。
ex: scp -i ~/.ssh/id_rsa -P 22 /tmp/test.txt [email protected]:/tmp/
一分鐘複習Git - Server
● 建立repository位置mkdir /data/repo
● 初始化git檔案匣cd /data/repogit init --bare
Repository建立完成之後長這個
樣子
一分鐘複習Git - Client
git clone [email protected]/repository_path
ex: git clone [email protected]:/data/repo
登入帳號,帳號的設定部分,依雲提供商有所不
同
登入之主機位置(可以是IP或DNS
位置)
Git repository所在位置
上雲端,怎麼都是指令?!
● 一切要求“速度”,時間就是金錢!● 一切要求“CP值”,資源能省就一定要省!
因為...
不過...還是有視窗工具拉~
Check... Now, you will ready....
# node -vNodeJS ready
# mysql -uroot -pMySQL ready
# git --versionGit ready
專案準備
Node Knock Out 2012
Git clone專案
# cd $project_homeClone the sample project# git clone [email protected]:~/git/sampleClone the empty project# git clone [email protected]:~/git/project
專案初始化
Initialize project with express# npm install express -g# express $project_home # cd $project_home# vi package.json ( add dependency with mysql, passport-google, passport)# express project# cd project# npm installTest app.js# node app.js
新增加/welcome路由
# vi app.jsapp.get('/welcome', function(res, req){ res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n');});
測試:
# node app.js開啟Browser: http://you.ip.address:3000/welcome
Simple User Authentication# add public/login.html
<form action="/signup" method="post">Username: <input type="text" id="username" name="username"/>Password: <input type="hidden" id="passwprd" name="passwprd"/><input type="submit" value="Submit"></form>
# add "/signup" routeapp.get('/signup', function(res, req){ if(req.body.username == 'simon' && req.body.password == '123456') { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(Success logined.... User: ' + req.body.username); } else { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(Success failed.... Please try again'); }});
測試:重啓Server後開啟Browser: http://you.ip.address:3000/login.html
/login.html /signup
Advance Authentication加入Passport認證機制
Node Knock Out 2012
Passport authentication with Google# git clone https://github.com/jaredhanson/passport-google.git # cd ~/project/passport-google/examples/signon/# npm install# vi app.js (置換localhost成為你的server ip位置)
# node app.js
Now, you can browse: http://localhost:3000/login
Passport Auth flow
Login Route: /login
Provider Auth Page:/auth/google
Return Page: /(views/index.ejs)
https://accounts.google.com/AccountChooser?service=lso&continue=https://accounts.google.com/o/openid2/auth?zt=ChRUemVxQUR....&from_login=1&hl=zh-TW&as=7c3fc762....&btmpl=authsub&hl=zh_TW
Which we need to do to use it?
● Add passport-google module to project# npm install passport# npm install passport-google
● Import librariesvar express = require('express') , passport = require('passport') , util = require('util') , GoogleStrategy = require('passport-google').Strategy;
● Using sessionapp.use(express.session({ secret: 'keyboard cat' }));
● Add configureapp.use(passport.initialize());app.use(passport.session());
Which we need to do to use it?
● Add serialize/deserialize implementpassport.serializeUser(function(user, done) { done(null, user); });passport.deserializeUser(function(obj, done) { done(null, obj); });
● Add auth strategypassport.use(new GoogleStrategy({ returnURL: 'http://localhost:3000/auth/google/return', realm: 'http://localhost:3000/' }, function(identifier, profile, done) { process.nextTick(function () { profile.identifier = identifier; return done(null, profile); }); }));
Passport Auth flow
/login /auth/google /auth/google/return /
reference config
index.ejs
redirect
Add Auth Routes
● /loginprocess --> redirect to "/auth/google"
● /logoutprocess --> req.logout()
● /auth/googleprocess --> passport.authenticate('google', { failureRedirect: '/login' })
● /auth/google/returnprocess --> passport.authenticate('google', { failureRedirect: '/login' })
app.get('/auth/google/return', passport.authenticate('google', { failureRedirect: '/login' }), function(req, res) { res.redirect('/'); });
Connect MySQL連線MySQL資料庫
Node Knock Out 2012
MySQL table 準備 1/2
# mysql -uroot -pmysql> create database nko2012 ;Query OK, 1 row affected (0.00 sec)
mysql> use nko2012mysql> create user 'nko'@'%' identified by 'nko2012';mysql> grant all on *.* to 'nko'@'%';
Tips:如欲設定mysql對外,請將/opt/local/etc/my.cnf中的binding-address設定成欲開放連線的ip位置
MySQL table 準備 2/2
CREATE TABLE `tb_post` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `refer_topic_id` int(11) DEFAULT NULL, `topic_title` varchar(50) DEFAULT NULL, `post_body` varchar(2000) DEFAULT NULL, `create_user` varchar(100) DEFAULT NULL, `create_date` datetime DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `tb_product` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `product_name` varchar(100) DEFAULT NULL, `product_descript` varchar(200) DEFAULT NULL, `amount` int(11) DEFAULT NULL, `update_date` datetime DEFAULT NULL, `update_user` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
MySQL connection for NodeJS設定
● MySQL nodejs library install# npm install mysql
● Setup connection/* 連線設定部分,參數名稱相同,可以直接延用 */
var db_options = { host: 'your.database.ip.address', port: 3306, user: 'username', password: 'password', database: 'db_name'};
/* [email protected]連線設定 */
var mysql = require('mysql')exports.db = mysql.createConnection(db_options);
/* [email protected]連線設定 */var mysql = new require('mysql') , db = mysql.createClient(db_options);
執行SQL動作 - 解說
db.query( sql_statment, //SQL command conditions, //Conditions, callback //callback);
執行SQL動作
● Create(C)db.query( 'insert into tb_post (refer_topic_id, topic_title, post_body, create_user, create_date) values (?,?,?,?,?)', [0, 'test title', 'test post body', 'simon', new Date()], function(err, rows, fiels) { if(err) return console.log(JSON.stringify(err)); console.log(rows);});
● Query(R)db.query('select * from tb_post where id = ?', [1], function(err, rows, fiels) { if(err) return console.log(JSON.stringify(err)); console.log(rows);});
執行SQL動作
● Update(U)db.query( 'update tb_post set topic_title = ? where id = ?', ['test update',1], function(err, rows, fiels) { if(err) return console.log(JSON.stringify(err)); console.log(rows);});
● Delete(D)db.query('delete from tb_post where id = ?', [2], function(err, rows, fiels) { if(err) return console.log(JSON.stringify(err)); console.log(rows);});
執行SQL動作
● Closedb.end();
與頁面連結之初
routes/
lib/
public/
放置網站會用到的router資訊...
商業邏輯library, middleware...
靜態檔案,含html, css, javascripts, images...
專案中用到的流程...
app.js routes/dbroutes.js lib/mydb.jspublic/
createProduct.htmlpublic/
productList.html
go to route...
if create...
ajax call...
library call...
REST response
jQuery render layout
/*** [public/createProduct.html] ***/<form method="post" action="/products">....</form>
/*** [app.js] ****************************/app.post('/products', dbroutes.createProducts);
/*** [routes/dbroute.js] **************/exports.createProduct = function(req, res){
var vo = {};vo.product_name = req.body.product_name;....mydb.jobs.createProduct(vo, function(err, data, meta){ res.redirect('/productList.html');});
}
/*** [lib/mydb.js] **********************/var script = { ..."createProduct": function(vo, callback){ db.query( 'insert into tb_product (product_name, ...., update_user) values (?,?,?,?,now())', [vo.product_name, ...., vo.update_user], callback); }...}exports.jobs = script;
基本範例
More...
Node Knock Out 2012
/* for list all product */app.get('/products', dbroutes.getProducts);
/* for list one product using product id*/app.get('/products/:id', dbroutes.getProductById);
/* for delete one product record */app.del('/products/:id', dbroutes.delProductById);
/* for create one product */app.post('/products', dbroutes.createProduct);
/* for update one record */app.put('/products', dbroutes.updateProductAmountById);
Routing Configure# app.js
[GET] for query data...
[DELETE] for delete data...
[POST] for create data...
[PUT] for update data...
Database routers
exports.getProducts = function(req, res){ mydb.jobs.getProducts(function(err, data, meta){ res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(data)); });};
exports.getProductById = function(req, res){ mydb.jobs.getProductById(req.params.id, function(err, data, meta){ res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(data)); });}
exports.delProductById = function(req, res){ mydb.jobs.delProductById(req.params.id, function(err, data, meta){ res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(data)); });}
# routes/dbroutes.js
exports.createProduct = function(req, res){ var vo = {}; vo.product_name = req.body.product_name; vo.product_descript = req.body.product_descript; vo.amount = req.body.amount; vo.update_user = req.body.update_user; mydb.jobs.createProduct(vo, function(err, data, meta){ res.redirect('/productList.html'); });}
exports.updateProductAmountById = function(req, res){ var vo = {}; vo.amount = req.body.amount; vo.id = req.body.id; mydb.jobs.updateProductAmountById(vo, function(err, data, meta){ res.writeHead(200, {'Content-Type': 'application/json'}); console.log('Update done...' + JSON.stringify(data)); res.end(JSON.stringify(data)); });}
呼叫library中對映執行程序
Database Modulesvar mysql = new require('mysql') , db = mysql.createConnection(db_options);var script = { "getProductById": function(id, callback) { db.query('select * from tb_product where id = ?', [id], callback); }, "getProducts": function(callback) { db.query('select * from tb_product ', callback); }, "createProduct": function(vo, callback){ db.query( 'insert into tb_product (product_name, product_descript, amount, update_date, update_user) values (?,?,?,?,now())', [vo.product_name, vo.product_descript, vo.amount, vo.update_date, vo.update_user], callback); }, "updateProductAmountById": function(vo, callback){ console.log('Will update %s amount to %s', vo.id, vo.amount); db.query( 'update tb_product set amount = ? where id = ?', [vo.amount, vo.id], callback); }, "delProductById": function(id, callback){ db.query('delete from tb_product where id = ?', [id], callback); }}exports.jobs = script;
# lib/mydb.js
實際執行資料庫CRUD動作
Page View
$.getJSON('/products', function(data) { var items = []; $.each(data, function(i, v) { var html = ('<li id="' + v.id + '">' + v.product_name + '<br/>Descript: ' +
v.product_descript + '<br/>Amount: ' + v.amount ); html += ' / Update:<input type="text" size="3" id="AMO-' + v.id + '"/><br/>'; html += '</li>'; items.push(html); });
$('<ul/>', { 'class': 'my-new-list', html: items.join('') }).appendTo('body');
$.each($('li'), function(){ $(this).append('<input type="button" alt="' + $(this).attr('id') + '" id="UPD-' +
$(this).attr('id') + '" value="Update"/>'); $(this).append('<input type="button" alt="' + $(this).attr('id') + '" id="DEL-' +
$(this).attr('id') + '" value="Delete"/>'); });
# public/productList.html
將資料插入頁面
增加控制項目(更新、刪除按鍵)...
Page View
$('input[type=button]').live('click', function(){ var id = $(this).attr('id'); var prd_id = $(this).attr('alt'); if(id.indexOf('DEL') == 0) { alert('Will delete ' + prd_id); //do delete and refresh page $.ajax({url:'/products/' + prd_id,type:'delete',data:{id: id}}).done(function(data){ //alert(data); document.location = '/productList.html'; }); } else if(id.indexOf('UPD') == 0) { alert('Will update ' + prd_id); //do update and refresh page var v = $('#AMO-' + prd_id).val(); $.ajax({url:'/products',type:'put',data:{id:prd_id, amount:v}}).done(function(data){ //alert(data); document.location = '/productList.html'; });
} });
# public/productList.html
安插Button動作,動作中另外呼叫Ajax執行其它操作
Q & A
Node Knock Out 2012
Reference
● Github - passporthttps://github.com/jaredhanson/passport
● Github - passport-googlehttps://github.com/jaredhanson/passport-google
附錄
Node Knock Out 2012
Demo Code● Clone project:
git clone https://github.com/peihsinsu/nko2012.git● Execute sample code - MySQL Standalone篇:
新增資料(資料內容定義於程式碼內)
# node test-mysql-client.js C{ fieldCount: 0, affectedRows: 1, insertId: 7, serverStatus: 2, warningCount: 1, message: '', changedRows: 0 }列出全部資料
# node test-mysql-client.js ALL[{"id":1,"refer_topic_id":0,"topic_title":"test update","post_body":"test post body","create_user":"simon","create_date":"2012-11-08T23:49:22.000Z"},{"id":2,"refer_topic_id":0,"topic_title":"test title","post_body":"test post ....
Demo Code更新一筆資料(更新內容於程式碼中)
# node test-mysql-client.js U{ fieldCount: 0, affectedRows: 1, insertId: 0, serverStatus: 2, warningCount: 0, message: '(Rows matched: 1 Changed: 0 Warnings: 0', changedRows: 0 }
Demo Code刪除一筆資料(欲刪除資料定義於程式碼中)
# node test-mysql-client.js D{ fieldCount: 0, affectedRows: 1, insertId: 0, serverStatus: 2, warningCount: 0, message: '', changedRows: 0 }
刪除後可以再查詢列表一次
# node test-mysql-client.js ALL[{"id":1,"refer_topic_id":0,"topic_title":"test update","post_body":"test post body","create_user":"simon","create_date":"2012-11-08T23:49:22.000Z"},{"id":3,"refer_topic_id":0,"topic_title":"test title","post_body":"test post ....
Demo Code● Execute sample code - MySQL整合範例:
# node app.jsaccess url: http://localhost:4000
Demo Code● Execute sample code - Passport-Google整合範例:
# node test--passport.jsaccess url: http://localhost:4000/login