作爲一名前端開發工程師,時常糾結因而否要學習後端開發,成爲一名真正的全棧,後端開發固然首選是node.js,由於不須要從新學習一門新的開發語言,但是node.js好像很難啊,深刻淺出node.js這本書成功勸退了許多想學全棧的同窗,由於一開始就把後端開發最艱深的一面展現了出來,如模塊和內存管理等,雖然對於有更高追求的開發者來講, 這些原理確定是掌握的越深刻越好,可是對於普通的後臺業務開發來講,其實只須要會用能實現業務就能夠了。javascript
咱們今天就從最簡單的todo list項目開始,揭開後臺開發的神祕面紗,幫你走上全棧開發工程師之路。爲何選擇todo list?由於它是最具表明性的項目,也最爲你們所熟悉,接受難度最小,麻雀雖小,五臟俱全,它包含了先後端開發必備的技能,能夠說只要徹底掌握了todo list,就能夠依葫蘆化瓢完成大部分項目的功能開發。 本文是一個能夠完整包含先後端代碼可以直接運行的項目, 效果以下:php
本教程適合有必定前端基礎、輕微瞭解node.js的開發人員(其實只須要知道npm安裝和引用模塊就已足夠)html
咱們打算從零開始,步步爲營逐漸完善整個項目代碼前端
其實這也是真實項目的開發流程,如今的先後臺開發都是分離的,先後臺開發先按照接口約定各自開發代碼,前端用靜態數據模擬(mock)可以完成99%以上的功能開發,後端都是純數據,本身單元測試就行了,所有開發完畢以後,先後臺對接部署在測試環境。vue
爲了保持教程的簡單,前端代碼用jquery實現,先創建一個index.html的空白文件,在頁面引入jquery,並添加一個ul元素 作爲容器。加一個文本框和添加按鈕,html代碼以下:java
<ul id="list"></ul>
<input type="text" id="title" placeholder="輸入待辦事項">
<button id="btn_add">添加</button>
複製代碼
如今尚未後臺數據庫,數據只能先用靜態的數組來定義,每條記錄有id,title,status 三個屬性,分別表示事項的編號、標題、是否已完成。 定義一個數組存放用測試數據,而後用forEach循環該數組,對html字符串進行拼接,最後合併html生成dom,該示例用了ES6的模板字符串,方便書寫和演示。node
代碼以下:mysql
function render(){
var html="";
var result=[
{id:1,title:"hello",status:0},
{id:2,title:"hello",status:0}
];
result.forEach(function(item){
var checked=item.status>0?"checked":"";
var li=`<li itemId="${item.id}"> <input type="checkbox" ${checked}> <input type="textbox" value="${item.title}"> <a href="javascript:" class="delete">刪除</a> </li>`
html+=li;
})
$("#list").html(html);
}
$(function (){
render();
})
複製代碼
運行 index.html,成功顯示出頁面。react
好了,靜態頁面渲染已經寫好,要邁出後臺開發的第一步了,把靜態數據改爲調用ajax 接口,對render函數稍作改造便可jquery
function render(){
$.post("http://localhost/todo/list",{},function (result){
var html="";
result.forEach(function(item){
var checked=item.status>0?"checked":"";
var li=`<li itemId="${item.id}"> <input type="checkbox" ${checked}> <input type="textbox" value="${item.title}"> <a href="javascript:" class="delete">刪除</a> </li>`
html+=li;
})
$("#list").html(html);
});
}
複製代碼
可是問題是如今尚未後臺服務器,並且本地的html文件發送ajax請求會遭遇跨域錯誤,有在本地作過開發的同窗必定知道ajax會產生cors異常,由於本地的文件是用file://協議打開的,若是訪問http://協議的頁面,會由於同源策略致使跨域錯誤,同源策略要求:
控制檯的報錯信息以下圖所示:
這個問題實際上是有辦法解決的,只要後臺接口實現了cors跨域,就能夠暢通無阻的調用了,這樣你的代碼不用部署到遠程服務器,就能夠調用遠程服務器的接口,很是的方便。那麼先來搭建個http服務實現cors吧,少年!咱們今天不用express,也不用koa,由於他們都沒有mock功能,跨域也要額外編寫不少代碼。所以我本身封裝了一個,名爲webcontext,github地址: github.com/windyfancy/…
經過npm能夠安裝,咱們在硬盤上建個目錄,例如todo_sample,用於存放後臺項目,切換進入該文件夾,運行npm install安裝
npm install --save webcontext
複製代碼
安裝好以後,在項目根目錄建一個app.js,用於啓動http服務,寫兩行代碼:
app.js
const WebContext = require('webcontext');
const app = new WebContext();
複製代碼
運行node app.js,http服務就啓動了,而後訪問http://localhost/,可以輸出hello信息,表示http服務已經搭建成功!
接下來,在項目根目錄/service目錄中創建一個todo子目錄,將在todo目錄中創建一個list.ejs空白文件,目錄結構以下
|-- service
| |--todo
| |--list.ejs
複製代碼
把以前的靜態數據存入list.ejs中,它實際上是個ejs模板文件,固然也能夠存放json
[
{id:1,title:"hello",status:0},
{id:2,title:"hello",status:0}
]
複製代碼
如今再來用瀏覽器直接訪問:http://localhost/todo/list ,已經能夠直接輸了該文件的內容了,可是用本地的index.html調用這個接口仍然是返回跨域錯誤的,爲了安全考慮, webcontext 跨域的配置默認是關閉的,咱們打開項目根目錄的web.config.json(首次運行時會自動生成),修改cors屬性中的allowOrigin字段值爲*便可開啓跨域
"cors":{
"allowOrigin":"*"
}
複製代碼
如今再來訪問本地的index.html,發現已經能夠請求成功了,http 響應頭正確的輸出了cors 跨域的信息。
上一步完成了後臺http接口的搭建,用mock靜態數據驗證了todolist的加載功能正常。終於到了激動人心的數據庫開發環節了,想一想立刻就能夠作個高大上的CURD boy了,走上人生巔峯,出任CEO,心情真是有點小激動呢。
簡單瞭解一下吧,顧名思義,數據庫是用來存放數據的地方,目前主流的數據庫是關係數據庫,如mysql、oracle、sql server等,以行列結構存儲一張張表的數據,就如同一個excel表格,每一張表是一個獨立的sheet,咱們把剛纔靜態的json數據轉換成表的形式以下:
id | title | status |
---|---|---|
1 | hello | 0 |
2 | world | 0 |
以mysql爲例,來定義一下這張todo_list表的結構,共有3個字段:
咱們來建表吧,首先確定要安裝mysql了
上官方網站https://www.mysql.com/downloads/ 下載安裝一下,完整安裝一下。裝 好以後,就 能夠用自帶的workbench鏈接數據庫了。用你剛纔安裝時初始設定的密碼鏈接一下:
鏈接上以後,數據庫仍是空的呢,要先新建一個數據庫(schema),名稱爲todo_db,而後在這個數據庫中新建一個表,能夠用工具欄或菜單中的create new table快速建立一張表:
固然也能夠用sql代碼去建立表:
CREATE TABLE `todo_list` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(45) DEFAULT NULL,
`status` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
)
複製代碼
建立好數據庫表以後,咱們嘗試用代碼鏈接數據庫,webcontext框架已經集成了數據庫的鏈接,只須要配置一下,在app.js啓動時就能夠自動鏈接數據庫了,不用編寫任何額外的代碼
修改web.config.json,設置數據庫鏈接參數
"database":{
"host":"127.0.0.1",
"port":"3306",
"user":"root",
"password":"你的密碼",
"database":"todo_db"
}
複製代碼
注:MySQL8.0以上版本密碼認證協議發生了改變,須要用mysql workbench執行以下代碼才能使用node.js鏈接數據庫,sql代碼以下: ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密碼';
好了,如今從新運行一下node app.js,就能夠自動鏈接數據庫了
數據庫鏈接成功後,咱們要測試一下,可否正常查詢和寫入數據,由於如今數據庫仍是空的,咱們先從插入數據開始。
它表明數據庫的 建立(Create)、更新(Update)、讀取(Retrieve)和刪除(Delete) 4個基本操做。對於關係型數據庫,能夠經過sql (結構化查詢語言 Structured Query Language 簡稱SQL) 來編寫代碼支持這4個基本操做,分別對應insert,update,select,delete 4條語句。
webcontext已經對insert,update,select,delete進行了封裝,所以對於簡單的讀寫操做,不須要編寫原始的sql語句了。
在項目目錄建立一個js文件,/service/todo/add.js,webcontext實現了頁面地址自動路由,不用手工編寫路由代碼,當訪問http://localhost/service/todo/add時,會自動調用這個路徑(/service/todo/add.js)文件中的onLoad方法,爲add.js並編寫以下代碼:
module.exports= {
async onLoad() {
await this.database.insert("todo_list",{ title:"hello",status:0});
this.render({code:"OK"})
}
}
複製代碼
只有4行代碼, 咱們來逐行解釋一下
寫好以後,咱們來訪問http://localhost/service/todo/add,而後使用mysql workbench查看一下數據庫,發現數據已經成功入庫了。 測試成功後,咱們把這行寫死的數據改過來吧,this.request.data["title"]表示獲取post表單中的title字段。
await this.database.insert("todo_list",{ title:this.request.data["title"],status:0});
複製代碼
如今數據庫裏已經有數據了,咱們要把請求mock的接口改爲讀取真實數據。刪掉list.ejs(固然不刪留着它作活口也是能夠的) 在/service/todo目錄新建一個list.js文件,書寫代碼以下:
/service/todo/list.js
module.exports= {
async onLoad() {
var result=await this.database.select("todo_list")
this.render(result);
}
}
複製代碼
不用過多解釋了,套路和上一個頁面同樣,你惟一要改的就是把insert方法改爲select方法,這個例子比較簡單,只須要傳一個表名就能夠了。實際上select方法已經封裝的很是強大,支持各類where條件、排序、數據庫層分頁、多表鏈接等,暫不展開講述。
/service/todo/update.js
module.exports= {
async onLoad() {
await this.database.update("todo_list",this.request.data)
this.render({code:"OK"})
}
}
複製代碼
update和insert寫法幾乎同樣,爲了增長點新鮮感,第二個參數直接用this.request.data表單數據了,這樣能夠節省不少代碼,可是若是別人惡意post不合法的數據的話你的代碼會報錯。
/service/todo/delete.js
module.exports= {
async onLoad() {
await this.database.delete("todo_list",{ id:this.request.data["id"]})
this.render({code:"OK"})
}
}
複製代碼
刪除,只須要表名和id參數
好了,如今CURD操做都已經完成了,業務代碼只有8行,其實能夠更少,由於mysql 支持replace into語句,insert 和 update能夠合二爲一。即便加上一些參數的合法性校驗,代碼量也是很是少的。如今先後臺分離以後,數據庫業務的後臺開發 真的要比前端要簡單不少,除了一些多表鏈接和統計的sql語句比較難寫以外,其它都是重複度很高的數據操做代碼,不過話說回來,自從前端普及了vue,react以後,開發門檻也下降了不少。
這個項目比較簡單,後臺只有這麼幾行代碼,卻要拆成4個文件實現,能不能更精簡一點呢,答案是確定的,webcontext支持經過擴名直接映到js文件中的函數!例如請求http://localhost/todo.list,會直接調用/service/todo.js中的list()方法。咱們刪掉剛纔編寫的todo目錄的代碼,從新實現 ,把他們整理到一個文件中便可。
/service/todo.js
module.exports= {
onRequest() {},
async list() {
var result=await this.database.select("todo_list")
this.render(result);
},
async add() {
await this.database.insert("todo_list",{ title:this.request.data["title"],status:0});
this.render({code:"OK"})
},
async update() {
await this.database.update("todo_list",this.request.data)
this.render({code:"OK"})
},
async delete() {
await this.database.delete("todo_list",{ id:this.request.data["id"]})
this.render({code:"OK"})
}
}
複製代碼
咱們在地址欄,訪問http://localhost/todo.list 測試一下,成功!可以正常返回數據。
如今後臺接口都完成了,可是添加、修改、刪除的前端代碼尚未實現,咱們用事件委託的方式實現,前端代碼都很是簡單再也不詳細描述,完整代碼以下。
function saveItem(target){
var li=$(target).parents("li");
var id=li.attr("itemId")
var title=li.find("input[type=textbox]").val();
var status=li.find("input[type=checkbox]").prop("checked")?1:0;
$.post("http://localhost/todo.update",{id:id,title:title,status:status},function (res){
if(res.code="OK"){
render();
}
});
}
function deleteItem(target){
var id=$(target).parents("li").attr("itemId")
$.post("http://localhost/todo.delete",{id:id},function (res){
if(res.code="OK"){
render();
}
});
}
$("#list").on("change","input[type=textbox]",function (e){
saveItem(e.target);
})
$("#list").on("click","input[type=checkbox]",function (e){
saveItem(e.target)
})
$("#list").on("click",".delete",function (e){
deleteItem(e.target)
})
$("#btn_add").click("click",function (e){
$.post("http://localhost/todo.add",{title:$("#title").val()},function (res){
if(res.code="OK"){
render();
}
});
})
複製代碼
如今全部代碼都已經完工,但是html和js文件總不能一直放在本地吧,webcontext已經內置了靜態文件服務,只須要把本地的index.html和jquery.js存放在站點根目錄下/client目錄下,再來訪問http://localhost/index.html,就能夠訪問到了。 具體原理請參考:Node.js 實現相似於.php,.jsp的服務器頁面技術,自動路由
最後,既然已經都在同一個http路徑下了,能夠把代碼裏$.post的絕對路徑都改爲相對路徑。
本文用了8行代碼實現了todolist 後臺接口,爲何只須要這麼少的代碼,源於webcontext的設計理念:約定優於配置,默認配置優於手工配置,配置優於編碼,將開發者的用戶體驗放在第一位。
舉例來講:
webcontext的結構設計參考了asp.net的優雅設計,用一個context對象作爲容器,很是方便 的調用request,response,session這些http請求處理操做類,即便在ejs模板中,也能夠經過this獲取到context,直接進行http處理或數據庫操做。並擴展了許多功能,如使用database對象操做數據庫,logger對象寫入日誌等。 主要結構以下:
雖然提供瞭如此多的功能,它的代碼卻很是精簡,只有千行左右,很容易讀懂,它雖然強大,卻不是陽春白春,並不高深,若是你想了解一個web框架是如何鑄成的,不妨來讀一下它的源碼,若是你認爲有用,請不要吝惜你的star,它如今仍是一棵小樹,須要你的支持。
github地址:github.com/windyfancy/…
本文的所有代碼也已經上傳github,在webcontext_examples項目中,有須要的同窗能夠自行查閱。