首先关于node.js的学习,这里推荐一本比较好的教程,nodejs web开发指南,该书通俗易懂地将node.js语言特性讲解完之后,又从一个项目角度带领读者使用node.js学习web开发。相信这是一个比较好的学习模式和过程。由于这本书是2012年出的,书中的一个web教学项目是开发一个微博。从2012到现在,node.js及其生态环境发生了很大改变,所以关于该书的学习如果照着书本显然是过于陈旧的。到目前为止,node.js的web开发框架已经升级到了Express4.12.1,对于MongoDB的操作更多是使用mongoose这个对象模型,而不是之前mongoDB 官方提供的原生node.js的API,所以本文将基于nodejsV0.1033 + MongoDBV3.0.2+ Jade1.9.2 + mogooseV4.0.1来重构该书中的微博项目,这个组合也是目前最新的使用node.js进行web开发的常用组合之一,如果需要入门使用node.js进行web开发,正在学习nodejs web开发指南的和想快速了解node.js web开发模式的朋友,相信本文是有一定帮助意义的。
1.express框架安装
1)在node命令行模式下输入以下命令
npm install -g express
该命令在全局环境下安装express框架,在安装完这一步之后,并不能直接使用express命令来生成express项目,需要再安装一个express项目生成器,在express2.X的版本中是不需要的,express4.X版本之后将项目生成器和express本身分离了出来,如果不安装express-generator这个生成器就使用express命令来生成项目,会遇到报express不是内部或外部命令这个错误,这是需要注意的地方,nodejs web开发指南原书中是没有安装express-generator这一步的。
2)安装express-generator
npm install -g express-generator
3)生成一个项目
cd .. mkdir microblog cd microblog express micorblog
这里随意在硬盘某个目录下创建一个microblog的文件夹,进入该文件夹,然后使用express microblog命令创建了一个microblog的express项目。
生成结构如下:
其中app.js是项目入口文件,package.json是npm 包管理文件,bin文件夹里面的www.js放一些全局配置项以及命令行配置等。public 文件夹是用来存放项目静态文件目录如js,css以及图片,routes文件夹是用来存放路由监听的代码相关文件。views文件夹用来存放模板文件,这里需要注意的是express4.X使用jade作为项目的默认模板引擎,而在原书中是使用ejs作为模板引擎的,所以这里默认生成的是jade文件。无可否认ejs是要简单些,但是原理都是一样的,我们使用jade作为开发的模板引擎。
4)启动项目并查看
cd microblog npm install npm start
进入到microblog文件夹,安装项目所需相关模块(根据pacakge.json文件),然后启动项目,这时候打开浏览器查看项目输入地址localhost:3000,结果如下说明一切正常,
到目前为止,我们已经拥有了一个在浏览器中运行的web项目雏形。下面进行开发,原书中的微博项目的主要功能是用户能够注册登录,权限控制并让用户发布微博在用户个人主页和项目首页分别显示,这些功能完整版代码会提供,由于篇幅原因,这里以用户注册登录模块来说明如何进行一个完整流程的web开发。
2.页面布局
依照web开发流程,我们首先来构建一个项目主页,项目主页是由布局文件layout.jade和内容文件index.jade组成,关于的jade的学习,这里提供两个地址,对于以前使用过类似模板引擎如smarty,razor等的,可以看看文档就能够上手做了,基本原理都是大同小异。
打开views文件,将layout.jade文件代码改写如下:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body nav.header ul.list li.logo a(href='/') Microblog li a(href='/') 首页 li a(href='/login') 登录 li a(href='/reg') 注册 p.container block content hr footer.footer p a(href='http://myzhibie.coding.io') myzhibie | @2015
需要注意父级元素和子元素的换行之间缩进,jade是利用缩进来区别代码层级的。
首页内容文件index.jade
extends layout block content main.main section.intro if message h3.indexmes #{message} //如果用户登录或者注册成功并且没有在登录状态下点击注册或者登录 if success&&user h1.welcome #{success},欢迎 #{user} 来到 Microblog else if !success&&user h1.welcome 欢迎 #{user} 来到 Microblog else h1.welcome 欢迎来到 Microblog h3.tech Microblog是一个基于Node.js,使用express4.12.1,jade1.9.2以及MongoDB搭建起来的微博系统,是对Node.js开发指南一书中教学项目的重构。 p.btnlist if user a.login(href='/logout') 退出 a.userlink(href='/users/#{user}') 发表文章 else a.login(href='/login') 登录 a.register(href='/reg') 立即注册 section.show each val in posts article.col h3.author #{val.user}说 p | #{val.post}
首页内容是继承了模板文件layout.jade.原书中使用的bootstrap来构建页面的css布局和样式,这里我自己手写了一个仿bootstrap风格的布局样式,没有应用bootstrap,style.css文件如下:
body { padding: 50px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } html,body,ul,p,hr,h3{ margin:0; padding: 0; } a { color: #00B7FF; } .header{ background:#337aB7; width: 100%; height: 60px; color: #fff; font-size: 22px; overflow: hidden; } .list{ line-height: 60px; } .navigation{ overflow: hidden; } .list li{ list-style: none; float: left; display: inline-block; margin-left: 20px; margin-right: 20px; } .list li a{ text-decoration: none; color: #fff; } .list li a:hover{ } .list li:not(:first-child) a:hover{ font-size: 26px; color: #F5F5F5; } .logo{ font-size: 26px; font-weight: 700; } .container{ min-height: 500px; text-align: center; width: 100%; } .footer{ width: 100%; height: 50px; font-size: 22px; background:#F5F5F5 ; line-height: 50px; } .footer a{ color:#337aB7; text-decoration: none; } .main{ color: #000000; width: 96%; margin: 30px auto; } .intro{ width: 100%; margin:0 auto; border-radius: 5px; height: 300px; background:#F5F5F5 ; } .userintro{ width: 100%; margin:0 auto; border-radius: 5px; height: 200px; background:#F5F5F5 ; } .welcome{ padding-top: 50px; padding-left:50px; font-size: 50px; text-align: left; padding-bottom: 0; margin: 0; } .tech{ text-align: left; padding-left:50px; margin: 0; } .show{ overflow: hidden; width: 100%; } .show li{ text-align: left; font-size: 18px; } .col{ display: inline-block; float: left; width: 32%; height: 100px; overflow: hidden; padding-right: 20px; text-align: left; text-overflow: ellipsis; } .author{ margin-top: 10px; margin-bottom: 3px; } .btnlist{ padding-left: 50px; text-align: left; } .login{ display: inline-block; padding-left: 15px; padding-right: 15px; height: 38px; line-height: 40px; background: -webkit-gradient(linear, left top, left bottom, from(#0068A6), to(#337aB7)); color: #fff; text-align: center; border-radius: 5px; font-size: 20px; font-weight: 600; border: 1px solid #ccc; text-decoration: none; margin-right: 10px; } .register{ display: inline-block; padding-left: 15px; padding-right: 15px; height: 38px; line-height: 40px; background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#F5F5F5)); color: #000; text-align: center; border-radius: 5px; font-size: 20px; font-weight: 600; border: 1px solid #ccc; text-decoration: none; } .field{ margin-top: 20px; margin-left: 50px; text-align: left; margin-bottom: 20px; border:none; border-bottom: 1px solid #ccc; } .label{ font-size: 18px; font-weight: 600; line-height: 100%; display: inline-block; width: 10%; vertical-align: middle; text-align: right; padding-right: 10px; } .regheader{ text-align: left; font-size: 24px; font-weight: 600; } .regform{ text-align: left; padding-left: 100px; margin-bottom: 20px; } .regform input[type='text'],input[type='password']{ width: 200px; height: 20px; } .regform input[type='submit']{ width: 120px; height: 30px; color: #fff; background:-webkit-gradient(linear, left top, left bottom, from(#0068A6), to(#337aB7)); border-radius: 5px; font-size: 20px; } .item{ margin:20px; width: 100%; } .mess{ font-size: 18px; color: #E73C3C; background: #F2DEDE; border-radius: 5px; width: 300px; text-align: center; margin-left: 100px; } .indexmes{ height: 30px; line-height: 30px; background: #F2DEDE; color: #E73C3C; } .article{ width: 60%; height: 30px; border-radius: 3px; border: 1px solid #A3C732; margin-top: 5px; font-size: 20px; } .submit{ height: 40px; vertical-align: middle; padding: 0; margin-top: -5px; margin-left: 5px; width: 80px; background: #A3c732; font-size: 20px; border: none; border-radius: 5px; color: #fff; } .submitform{ margin-top: 25px; margin-left: -10px; } .userlink{ display: inline-block; text-decoration: none; line-height: 38px; height: 38px; vertical-align: middle; padding: 0; margin-top: -8px; margin-left: 5px; width: 90px; text-align: center; background: #A3c732; font-size: 20px; font-weight: 600; border-radius: 5px; color: #fff; border: 1px solid #ccc; } .usertitle{ text-align: left; padding-top: 5px; padding-bottom: 0; padding-left: 5px; margin-bottom: 8px; } .usersuccess{ height: 30px; background: #DFF0D8; line-height: 30px; color: #3C7668; }
这个css文件是项目中所有的css全部包含在这里,所以比较庞大。到目前为止,可以查看首页效果如下:
首页中的数据都是之前自己测试过程中加入的,这里主要为了查看首页效果,可以忽略这些数据。
由于这里要演示用户注册登录模块,用户注册模块的模板文件reg.jade如下:
extends layout block content h3.field.regheader #{title} form.regform(method='post') p.mess #{message} p.item label.label(for='username') 用户名 input(type='text',placeholder='输入注册用户名',id='username',name='username') p.item label.label(for='password') 用户密码 input(type='password',placeholder='用户密码',id='password',name='password') p.item label.label(for='passwordconf') 重复密码 input(type='password',placeholder='重复密码',id='passwordconf',name='passwordconf') p.item label.label input(type='submit' id='sub',name='sub' value='注册')
用户登陆模板login.jade如下:
extends layout block content h3.field.regheader #{title} form.regform(method='post') p.mess #{message} p.item label.label(for='username') 用户名 input(type='text',placeholder='输入登陆用户名',id='username',name='username') p.item label.label(for='password') 用户密码 input(type='password',placeholder='用户密码',id='password',name='password') p.item label.label input(type='submit' id='sub',name='sub' value='登陆')
最终用户注册效果如下:
用户登录模块和这个效果相仿,就不查看了,少了一个重复密码的input而已。
下面我们需要编写用户注册的逻辑,在编写用户注册逻辑的前,用户数据需要持久化,所以首先要安装MongoDB数据库在自己的机器上.
MongoDB这种nosql类型的数据库,非常适合用户存储JSON对象类型的数据,有了mongoDB,就可以免去数据库表设计部分的工作,对比以前使用的mysql,sqlserver以及oracle还是非常方便的。关于mongoDB数据库的熟悉和学习,推荐其官网,官网详细介绍了该数据库的一切。英文不好可以去中文社区。同时为了使用nodejs来操作mongoDB数据库,我们使用mongoose这个对象模型,它是将mongoDB中的一个集合映射为nodejs中的一个model,然后在该model上提供操作这个集合的一些方法,使用它就可以避免我们自己利用nodejs提供的原生操作mongoDB数据库的语法去手写数据库CURD的方法,大大见晒了工作量,提高了开发效率。关于mongoose的学习,推荐去其官网,里面详述了它的安装,使用以及API调用情况。
解决了mongoDB安装和操作问题,我们来对数据库操作的model类,首先在项目路径下建立一个db.js文件,用来连接数据库并对数据库进行全局配置,如下
db.js
var settings=require("./settings"); var mongoose=require('mongoose'); mongoose.connect("mongodb://"+settings.ip+"/"+settings.db); var db=mongoose.connection; module.exports={ "dbCon":db, "mongoose":mongoose };
这里首先加载了配置文件settings.js文件,为了数据库便于灵活修改,我们将某些信息存储在配置文件中。然后加在了之前安装的mongoose模块,然后调用该模块的connect方法来连接我们配置的数据库,然后将连接以对象的形式返回供外部调用。
settings.js
module.exports={ "ip":"localhost", "db":"microblog", "host":27071 };
MongoDB的默认端口是27071,一般可以使用默认端口即可,数据库连接大时候可以不指定端口,数据库名为microblog.
然后以db.js返回的数据库连接对象为基础,我们在项目根目录下创建一个models文件夹,用来存放数据模型。创建一个user.js映射我们数据库中的user集合(可以理解为user表),代码如下:
var mongoose=require('../db').mongoose; var schema=new mongoose.Schema({ name:'string', password:'string' }); var User=mongoose.model('User',schema); module.exports=User;
这里首先获得db.js中定义的连接对象,并以该对象为基础构造一个Schema(架构),mogoose操作数据库是以架构为基础的,类似于我们其他ORM模型中属性和方法的定义。这里我们定义了一个架构,拥有两个属性,name和password,都是string类型,对应用户的用户名和密码。然后利用该架构去创建一个model,该model上定义了对数据集合的增删改查等方法,不用我们自己再去定义和编写其他代码。在原书中这一节是利用node.js操作MongoDB数据库的原生API去定义了一个user对象,然后在user对象上自定义了一些CRUD的方法。可以看出,直接使用Mongoose可以大大减少开发量并且拥有更好的效率和性能。
到目前为止,我们已经有了界面(view),数据模型(model),就差逻辑代码(controller)没有编写了。在编写逻辑代码之前需要先说下express框架的特点以及它的整体运行方式。由于本人使用过一些类似的如Asp.net mvc,Yii以及thinkphp等MVC框架,使用express之后最大的感觉是这个框架够轻量级,尤其是express4.X之后,它仅仅保留了静态文件路径映射模块作为该框架本身的内置模块,其他的功能都以中间件的形式采用require(modulename)进行引入,只有引入后才能够使用该模块提供的功能。
express的工作原理是客户端发送一个request,express接到该请求,可以将它进行处理之后传递给其他中间件进行处理,最终处理完成之后,采用respond.end或者response.render进行页面渲染或响应,进行页面渲染的时候,采用参数传递页面需要的数据给对应模板引擎,模板引擎收到数据然后按照自己的语法进行替换生成对应的html,最终返回给浏览器进行渲染。
在express中,最关键的部分就是路有机制,我们所有基于请求做出的响应都是对该路由进行监听捕获的结果。举个例子,如果我们请求一个路径为http://localhost:3000/user,那么必须在routes文件夹下面的路径监听(暂且叫做监听吧)的js文件中编写对该请求的响应代码,诸如app.post('/user',function(...){...})之类的代码,如果不存在这样的代码,就会报一个404错误,因为请求没有得到响应,express实例不知道怎么去响应这个请求。