Node——Connect和Express

前言

ConnectExpress是两个热门的Node模块。Express就是在Connect的基础上,通过添加高层糖衣扩展和搭建出来的。

博客地址:https://delaprada.com

Connect

创建简单的Connect程序

指令:

npm install connect@3.4.0

在要安装的模块后加@可以指定版本号

简单的Connect程序应该是这样的:

const app=require('connect')();
app.use((req,res,next)=>{
	res.end('Hello, world!');
});
app.listen(3000);

这个程序会用Hello, World!做出响应。也就是访问http://localhost:3000的时候,页面显示Hello, world!。传给app.use的函数是个中间件,它以文本Hello, world!作为响应结束了请求处理过程。中间件是所有ConnectExpress程序的基础。

Connect中间件的工作机制

Connect中间件就是JavaScript函数。这个函数一般会有三个参数:请求对象、响应对象、以及一个名为next的回调函数。一个中间件完成自己的工作,要执行后续的中间件时,可以调用这个next回调函数。

在中间件运行之前,Connect会用分派器接管请求对象,然后交给程序中的第一个中间件。

借助中间件API,可以把一些小的构件块组合到一起,实现复杂的处理逻辑。

组合中间件

Connect中的use方法就是用来组合中间件的。我们先来定义两个中间件函数,然后把它们都添加到程序中。

const connect=require('connect');
function logger(req,res,next){
	console.log('%s %s', req.method, req.url);
	next();
}
function hello(req,res){
	res.setHeader('Content-Type', 'text/plain');
	res.end('hello world');
}
connect()
	.use(logger)
	.use(hello)
	.listen(3000);

这两个中间件的名称签名不一样:一个有next,一个没有。因为后面这个中间件完成了HTTP响应,再也不需要把控制权交还给分派器了。

use()函数返回的是Connect程序的实例,支持方法链。也可以不写成链式调用的形式:

const app=connect();
app.use(logger);
app.use(hello);
app.listen(3000);

需要注意的是,如果某个中间件不调用next(),那链在它后面的中间件就不会被调用。所以如果logger和hello两个函数的调用顺序反过来,则logger是不会执行的。

使用错误处理中间件

Connect中有一种用来处理错误的中间件变体。跟常规的中间件相比,除了请求和响应对象外,错误处理中间件的参数中还多了一个错误对象。

//Connect中的错误处理中间件
const env=process.env.NODE_ENV||'development';
function errorHandler(err,req,res,next){
	res.statusCode=500;
	switch(env){
        //开发模式和生产模式的响应不同
		case 'development':
			console.error('Error:');
			console.error(err);
			res.setHeader('Content-Type','application/json');
			res.end(JSON.stringify(err));
			break;
		default:
			res.end('Server error');
	}
}
module.exports=errorHandler;

开发环境(development):开发同学开发时使用的环境,每位开发同学在自己的dev分支上工作,开发到一定程度后,各位同学会合并代码,进行联调。

生产环境(production):线上环境,用户使用的环境。由特定人员来维护。

设置环境变量

UNIX系统中设置环境变量的指令:
$ NODE_ENV=production node app

Windows中用这个:

$ set NODE_ENV=production

$ node app

产品在开发环境中运行时,你可能想要看到尽可能详细的日志;但在生产环境中,你可能想让日志尽量精简,可能还要用gzip进行压缩。

用NODE_ENV设定程序的模式,Connect一般会根据环境变量NODE_ENV(process.env.NODE_ENV)来切换不同服务器环境(比如生产环境和开发环境)下的行为。

Connect遇到错误时,它会切换,只去调用错误处理中间件。

假设有一个允许用户登录到管理区域的博客程序。如果负责用户路由的中间件引发了一个错误,则中间件blogadmin都会被跳过,因为它们不是错误处理中间件(只有三个参数)。然后Connect看到接受错误参数的errorHandler,就会调用它。中间件看起来像下面这样:

connect()
	.use(router(require('./routes/user')))
	.use(router(require('./routes/blog'))) //跳过
	.use(router(require('./routes/admin')))  //跳过
	.use(errorHandler);

Express

Express是非常流行的Web框架,以前是在Connect的基础上搭建的。尽管提供了一些基本的功能,比如静态文件服务、URL路由和程序配置等,但它依然是极简的Web框架。

简单的Express程序

const express=require('express');
const app=express();

app.get('/',(req,res)=>{
	res.send('Hello');
});

app.listen(3000);

生成程序框架

安装指令:

npm install -g express-generator

生成程序

-e或(--ejs)指定要使用的模板引擎是EJS(EJS是一个嵌入JavaScript模板引擎,通过编译生成HTML代码)。执行express -e shoutboxshoutbox是文件名。一个功能完备的程序会出现在shoutbox目录中。其中会有描述项目和依赖项的package.json文件、程序主文件、public目录,以及一个放路由处理器的目录。


Express路由入门

根据官网定义:

Routing refers to determining how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, and so on).

路由的定义:

app.METHOD(PATH, HANDLER)
  • app是express的一个实例
  • METHODHTTP请求方法(小写)
  • PATH是服务器上的路径
  • HANDLER是在这个路径上发起HTTP请求时执行的回调函数

Respond with Hello World! on the homepage:

app.get('/', function (req, res) {
  res.send('Hello World!')
})

Respond to POST request on the root route (/), the application’s home page:

app.post('/', function (req, res) {
  res.send('Got a POST request')
})

Respond to a PUT request to the /user route:

app.put('/user', function (req, res) {
  res.send('Got a PUT request at /user')
})

Respond to a DELETE request to the /user route:

app.delete('/user', function (req, res) {
  res.send('Got a DELETE request at /user')
})
做个简单的留言板程序

效果:

步骤:

  • 创建消息模型
  • 添加与消息相关的路由
  • 创建消息表单
  • 添加业务逻辑,用提交上来的表单数据创建消息
创建消息模型

在创建消息模型之前,需要先安装Node redis模块。执行命令npm install --save redis。而且要安装Redis,Windows系统可以使用Redis Chocolately

安装教程:https://www.cnblogs.com/julyluo/p/6646155.html

官网没有windows版本,github下载速度太慢。

Redis就是一个数据库,可以将我们post的留言存储起来。

  1. 创建保存在线留言板消息条目的模型。

    创建models/entry.js文件:

    'use strict';
    const redis = require('redis');
    const db = redis.createClient();
    
    class Entry {
      constructor(obj) {
        for (let key in obj) {
          this[key] = obj[key];
        }
      }
    
      static getRange(from, to, cb) {
        db.lrange('entries', from, to, (err, items) => {
          if (err) return cb(err);
          let entries = [];
          items.forEach((item) => {
            entries.push(JSON.parse(item));
          });
          cb(null, entries);
        });
      }
    
      save(cb) {
        const entryJSON = JSON.stringify(this);
        db.lpush(
          'entries',
          entryJSON,
          (err) => {
            if (err) return cb(err);
            cb();
          }
        );
      }
    
      static count(cb) {
        db.llen('entries', cb);
      }
    }
    
    module.exports = Entry;
    
  2. 创建消息表单

    app.js中添加如下路由部分

    app.get('/post', entries.form);
    app.post('/post', entries.submit);
    

    即:使用get向/post发起http请求时,执行entries.form回调函数。使用post向/post发起http请求时,执行entries.submit回调。


    这个entries是从routes中导入的模块。看一下定义:

    表单模块部分:

    //routes/entries.js
    exports.form = (req, res) => {
      res.render('post', { title: 'Post' });
    };
    

    这里是用views/post.ejs,即表单,去渲染页面。

    //views/post.ejs
    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
      </head>
      <body>
        <% include menu %> 
        <h1><%= title %></h1>
        <p>Fill in the form below to add a new post.</p> 
        <form action='/post' method='post'>
          <p>
            <input type='text' name='entry[title]' placeholder='Title' />
          </p>
          <p>
            <textarea name='entry[body]' placeholder='Body'></textarea>
          </p>
          <p>
            <input type='submit' value='Post' />
          </p>
        </form>
      </body>
    </html>
    

    表单中用了形如entry[title]之类的输入控件名称,需要用扩展的消息体解析器来解析。找到app.js

    app.use(bodyParser.urlencoded({ extended: true }));
    

    改为:

    app.use(bodyParser.urlencoded({ extended: true }));
    

  3. 实现消息提交功能

    //将代码添加到文件routes/entries.js中,实现用表单提交上来的数据创建消息
    exports.submit = (req, res, next) => {
      const data = req.body.entry;
      const user = res.locals.user;
      const username = user ? user.name : null;
      const entry = new Entry({
        username: username,
        title: data.title,
        body: data.body
      });
      entry.save((err) => {
        if (err) return next(err);
        res.redirect('/');
      });
    };
    

    这里使用了Common JS进行模块化开发


  4. 添加显示消息的首页

    routes/entries.js中添加:

    const Entry = require('../models/entry');
    
    exports.list = (req, res, next) => {
      const page = req.page;
      Entry.getRange(0, -1, (err, entries) => {
        if (err) return next(err);
        res.render('entries', {
          title: 'Entries',
          entries: entries
        });
      });
    };
    

  5. 视图entries.ejs

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
      </head>
      <body>
        <% include menu %> 
        <% entries.forEach((entry) => { %>
          <div class='entry'>
            <h3><%= entry.title %></h3>
            <p><%= entry.body %></p>
            <p>Posted by <%= entry.username %></p>
          </div>
        <% }) %>
      </body>
    </html>
    

    在程序运行之前先在同一目录下创建menu.ejs创建菜单模板文件,后续会用到。目前空白着先。


  6. 添加与消息相关的路由

    app.js中:

    const entries = require('./routes/entries');
    

    添加路由:

    app.get('/', entries.list);
    

  7. 运行程序

    先以管理员身份进入CMD,运行redis-server启用Redis

    terminal中运行npm start,访问http://localhost:3000/post

    点击post后跳转到首页:

完成。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值