内容简介:做为一名前端开发工程师,时常纠结于是否要学习后端开发,成为一名真正的全栈,可是node.js好像很难啊,深入浅出node.js这本书成功劝退了许多想学全栈的同学,因为一开始就把后端开发最艰深的一面展示了出来,如模块和内存管理等,虽然对于有更高追求的开发者来说, 这些原理肯定是掌握的越深入越好,但是对于普通的后台业务开发来说,只需要会用能实现业务就可以了, 我们今天就从todo list项目开始,揭开后台开发的神秘面纱,帮你走上全栈开发工程师之路。本教程适合有一定前端基础、轻微了解node.js的开发人员(其
做为一名前端开发工程师,时常纠结于是否要学习后端开发,成为一名真正的全栈,可是node.js好像很难啊,深入浅出node.js这本书成功劝退了许多想学全栈的同学,因为一开始就把后端开发最艰深的一面展示了出来,如模块和内存管理等,虽然对于有更高追求的开发者来说, 这些原理肯定是掌握的越深入越好,但是对于普通的后台业务开发来说,只需要会用能实现业务就可以了, 我们今天就从todo list项目开始,揭开后台开发的神秘面纱,帮你走上全栈开发工程师之路。
本教程适合有一定前端基础、轻微了解node.js的开发人员(其实只需要知道npm安装和引用模块就已足够)
我们打算从零开始,步步为营逐渐完善整个项目代码
- 第一步,先实现一个最简化的纯静态版本的todolist
- 第二步,建立后台接口,用mock测试数据填充,让页面先能够正常显示出来
- 第三步,设计数据库,将测试数据改为真正的接口
其实这也是真实项目的开发流程,现在的前后台开发都是分离的,前后台开发先按照接口约定各自开发代码,前端用静态数据模拟(mock)能够完成99%以上的功能开发,后端都是纯数据,自己单元测试就好了,全部开发完毕之后,前后台对接部署在测试环境。
迈步从头跃,前端纯静态版todolist
为了保持教程的简单,前端代码用jquery实现,先建立一个index.html的空白文件,在页面引入jquery,并添加一个ul元素 做为容器。加一个文本框和添加按钮,html代码如下:
<ul id="list"></ul> <input type="text" id="title" placeholder="输入待办事项"> <button id="btn_add">添加</button> 复制代码
现在还没有后台数据库,数据只能先用静态的数组来定义,每条记录有id,title,status 三个属性,分别表示事项的编号、标题、是否已完成。 定义一个数组存放用测试数据,然后用forEach循环该数组,对html字符串进行拼接,最后合并html生成dom,该示例用了ES6的模板字符串,方便书写和演示。
代码如下:
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,成功显示出页面。
第二次迭代,用mock数据,连接真实的后台接口
好了,静态页面渲染已经写好,要迈出后台开发的第一步了,把静态数据改成调用ajax 接口,对render函数稍做改造即可
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 跨域的信息。
战前准备工作,设计 mysql 数据库
上一步完成了后台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个字段:
- id:表示待办事项的编号,数值类型,在数据库中以int类型表示,它是每条记录的唯一标识,即主键
- title:表示待办事项的名称,字符串类型,在数据库中以varchar变长字符串类型表示
- status:表示待办事项的状态,布尔类型,为了便于扩展我们定义成数据类型,也用int类型表示
我们来建表吧,首先肯定要安装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,就可以自动连接数据库了
第三次迭代,编写数据库查询保存接口,实现CURD
数据库连接成功后,我们要测试一下,能否正常查询和写入数据,因为现在数据库还是空的,我们先从插入数据开始。
什么是CURD?
它代表数据库的 创建(Create)、更新(Update)、读取(Retrieve)和删除(Delete) 4个基本操作。对于关系型数据库,可以通过sql (结构化查询语言 Structured Query Language 简称SQL) 来编写代码支持这4个基本操作,分别对应insert,update,select,delete 4条语句。
webcontext已经对insert,update,select,delete进行了封装,因此对于简单的读写操作,不需要编写原始的sql语句了。
insert
在项目目录创建一个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行代码, 我们来逐行解释一下
- module.exports:表示导出这个对象,这样webcontext框架才能自动引用到它,这个对象会自动从Context类继承,你不用编写任何extend或修改prototype的代码 , webcontext框架内部实现了对/service目录下的文件自动添加路由和自动继承的功能。不用任何的require和extend,具体原理也非常简单,可以看下我的这篇文章->: Node.js 实现类似于.php,.jsp的服务器页面技术,自动路由
- async onLoad 函数:首先,这是一个异步函数,由于第3行代码访问数据库用了await,所以这里必须要加async关键字,然后onLoad是一个事件,也可以说是一个回调函数,它表示这个函数的代码是在后台接收到http请求后执行。
- this.database.insert, 由于当前文件自动继承自Context类,可以通过this获取到请求对象request、响应对象response,以及database对象,database对象封装了基本的curd操作。insert方法第一个参数表示要插入的表名,第二个参数是一个对象,表示要插入的字段名和字段值,为了便于测试,我们先用死数据测试一下 { title:"hello",status:0}
- this.render 将传入的字符串或对象输出到http响应。传入object的话会自动stringify。
写好之后,我们来访问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}); 复制代码
select
现在数据库里已经有数据了,我们要把请求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条件、 排序 、数据库层分页、多表连接等,暂不展开讲述。
update
/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不合法的数据的话你的代码会报错。
delete
/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之后,开发门槛也降低了很多。
现在后台接口都完成了,但是添加、修改、删除的前端代码还没有实现,我们用事件委托的方式添加dom操作和ajax请求的代码
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(); } }); }) 复制代码
清理战场 ,静态文件迁移部署到http服务器
现在所有代码都已经完工,可是html和js文件总不能一直放在本地吧,webcontext已经内置了静态文件服务,只需要把本地的index.html和jquery.js存放在站点根目录下/frontend目录下,再来访问http://localhost/index.html,就可以访问到了。 具体原理请参考: Node.js 实现类似于.php,.jsp的服务器页面技术,自动路由
最后,既然已经都在同一个http路径下了,可以把代码里$.post的绝对路径都改成相对路径。
总结
本文用了8行代码实现了todolist 后台接口,为什么只需要这么少的代码,源于webcontext的设计理念:约定优于配置,默认配置优于手工配置,配置优于编码,将开发者的用户体验放在第一位。
举例来说:
- 它实现了服务器页面技术,不再需要添加路由的代码,因为页面地址本身已经包含了路由信息,一个文件处理一个请求,也便于解耦,定义路由本身就属于画蛇添足的行为
- 配置了数据库连接字符串,自动连接数据库,一行代码实现CURD,也实现了ORM数据库实体映射功能,实现了不需要写sql就可以操作数据库。
- 默认配置文件是自动生成的,大部分情况下都不需要修改这些配置。
- 对于表单、上传、json,各个环节都是自动解析为对象的,甚至可以将表单数据直接写入数据库,省去中间转换参数的冗余代码
webcontext的结构设计参考了asp.net的优雅设计,用一个空的context对象容器,引用request,response,session这些http请求处理操作类,即使在ejs模板中,也可以通过this获取到context,轻易进行各种操作,并进行了扩展,增加了数据库访问,日志写入器等对象 主要结构如下:
虽然提供了如此多的功能,它的代码却非常精简,只有千行左右,很容易读懂,它虽然强大,却不是阳春白春,并不高深,如果你想了解一个web框架是如何铸成的,不妨来读一下它的源码,如果你认为有用,请不要吝惜你的star,它现在还是一棵小树,需要你的支持。
github地址: github.com/windyfancy/…
本文的全部代码也已经上传github,在webcontext_examples项目中,有需要的同学可以自行查阅。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 第二届网易前端技术大会-启航
- 迟来的2018总结之一个有仪式感的2019启航
- 河南卫生云 九千万人口的健康中国梦从这里启航
- 人工智能发展需要真正的开源开放,OpenI启智平台肩负使命正式启航
- 前端科普系列(三):CommonJS 不是前端却革命了前端
- 前端科普系列(三):CommonJS 不是前端却革命了前端
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Traction: A Startup Guide to Getting Customers
Gabriel Weinberg、Justin Mares / S-curves Publishing / 2014-8-25 / USD 14.99
Most startups end in failure. Almost every failed startup has a product. What failed startups don't have is traction -- real customer growth. This book introduces startup founders and employees to......一起来看看 《Traction: A Startup Guide to Getting Customers》 这本书的介绍吧!