node.js全栈开发之旅(启航篇),一分钟实现todo list后台

作为一名前端开发工程师,时常纠结因而否要学习后端开发,成为一名真正的全栈,后端开发固然首选是node.js,由于不须要从新学习一门新的开发语言,但是node.js好像很难啊,深刻浅出node.js这本书成功劝退了许多想学全栈的同窗,由于一开始就把后端开发最艰深的一面展现了出来,如模块和内存管理等,虽然对于有更高追求的开发者来讲, 这些原理确定是掌握的越深刻越好,可是对于普通的后台业务开发来讲,其实只须要会用能实现业务就能够了。javascript

咱们今天就从最简单的todo list项目开始,揭开后台开发的神秘面纱,帮你走上全栈开发工程师之路。为何选择todo list?由于它是最具表明性的项目,也最为你们所熟悉,接受难度最小,麻雀虽小,五脏俱全,它包含了先后端开发必备的技能,能够说只要彻底掌握了todo list,就能够依葫芦化瓢完成大部分项目的功能开发。 本文是一个能够完整包含先后端代码可以直接运行的项目, 效果以下:php

本教程适合有必定前端基础、轻微了解node.js的开发人员(其实只须要知道npm安装和引用模块就已足够)html

咱们打算从零开始,步步为营逐渐完善整个项目代码前端

  1. 第一步,先实现一个最简化的纯静态版本的todolist
  2. 第二步,创建后台接口,用mock测试数据填充,让页面先可以正常显示出来
  3. 第三步,设计数据库,将测试数据改成真正的接口

其实这也是真实项目的开发流程,如今的先后台开发都是分离的,先后台开发先按照接口约定各自开发代码,前端用静态数据模拟(mock)可以完成99%以上的功能开发,后端都是纯数据,本身单元测试就行了,所有开发完毕以后,先后台对接部署在测试环境。vue

迈步从头跃,前端纯静态版todolist

为了保持教程的简单,前端代码用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

第二次迭代,用mock数据,链接真实的后台接口

好了,静态页面渲染已经写好,要迈出后台开发的第一步了,把静态数据改为调用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://协议的页面,会由于同源策略致使跨域错误,同源策略要求:

  1. 协议相同
  2. 域名相同
  3. 端口相同

控制台的报错信息以下图所示:

这个问题实际上是有办法解决的,只要后台接口实现了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行代码, 咱们来逐行解释一下

  1. module.exports:表示导出这个对象,这样webcontext框架才能自动引用到它,这个对象会自动从Context类继承,你不用编写任何extend或修改prototype的代码 , webcontext框架内部实现了对/service目录下的文件自动添加路由和自动继承的功能。不用任何的require和extend,具体原理也很是简单,能够看下个人这篇文章->:Node.js 实现相似于.php,.jsp的服务器页面技术,自动路由
  2. async onLoad 函数:首先,这是一个异步函数,因为第3行代码访问数据库用了await,因此这里必需要加async关键字,而后onLoad是一个事件,也能够说是一个回调函数,它表示这个函数的代码是在后台接收到http请求后执行。
  3. this.database.insert, 因为当前文件自动继承自Context类,能够经过this获取到请求对象request、响应对象response,以及database对象,database对象封装了基本的curd操做。insert方法第一个参数表示要插入的表名,第二个参数是一个对象,表示要插入的字段名和字段值,为了便于测试,咱们先用死数据测试一下 { title:"hello",status:0}
  4. 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以后,开发门槛也下降了不少。

这个项目比较简单,后台只有这么几行代码,却要拆成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();
        }
    });
})
复制代码

清理战场 ,静态文件迁移部署到http服务器

如今全部代码都已经完工,但是html和js文件总不能一直放在本地吧,webcontext已经内置了静态文件服务,只须要把本地的index.html和jquery.js存放在站点根目录下/client目录下,再来访问http://localhost/index.html,就能够访问到了。 具体原理请参考:Node.js 实现相似于.php,.jsp的服务器页面技术,自动路由

最后,既然已经都在同一个http路径下了,能够把代码里$.post的绝对路径都改为相对路径。

总结

本文用了8行代码实现了todolist 后台接口,为何只须要这么少的代码,源于webcontext的设计理念:约定优于配置,默认配置优于手工配置,配置优于编码,将开发者的用户体验放在第一位。

举例来讲:

  1. 它实现了服务器页面技术,再也不须要添加路由的代码,由于页面地址自己已经包含了路由信息,一个文件处理一个请求,也便于解耦,也能够一个文件处理多个请求,经过url直接调用js文件中的方法,如todo.list就调用todo.js文件中的list方法,不用每增长一个请求路径就去修改路由文件。
  2. 配置了数据库链接字符串,自动链接数据库,一行代码实现CURD,也实现了ORM数据库实体映射功能,实现了不须要写sql就能够操做数据库。
  3. 默认配置文件是自动生成的,大部分状况下都不须要修改这些配置。
  4. 对于表单、上传、json,各个环节都是自动解析为对象的,甚至能够将表单数据直接写入数据库,省去中间转换参数的冗余代码

webcontext的结构设计参考了asp.net的优雅设计,用一个context对象作为容器,很是方便 的调用request,response,session这些http请求处理操做类,即便在ejs模板中,也能够经过this获取到context,直接进行http处理或数据库操做。并扩展了许多功能,如使用database对象操做数据库,logger对象写入日志等。 主要结构以下:

虽然提供了如此多的功能,它的代码却很是精简,只有千行左右,很容易读懂,它虽然强大,却不是阳春白春,并不高深,若是你想了解一个web框架是如何铸成的,不妨来读一下它的源码,若是你认为有用,请不要吝惜你的star,它如今仍是一棵小树,须要你的支持。

github地址:github.com/windyfancy/…

本文的所有代码也已经上传github,在webcontext_examples项目中,有须要的同窗能够自行查阅。

相关文章
相关标签/搜索