Express 介绍

优质
小牛编辑
126浏览
2023-12-01

在前面的 node.js 基础当中介绍许多许多开设 http 的使用方法及介绍,以及许多基本的node.js 基本应用。

接下来要介绍一个套件称为 express [Express](http://expressjs.com/) ,这个套件主要帮忙解决许多 node.js http server 所需要的基本服务,让开发 http service 变得更为容易,不需要像之前需要透过层层模组(module)才有办法开始编写自己的程式。

这个套件是由TJ Holowaychuk 製作而成的套件,里面包含基本的路由处理(route),http 资料处理(GET/POST/PUT),另外还与样板套件(js html template engine)搭配,同时也可以处理许多複杂化的问题。

Express 安装

安装方式十分简单,只要透过之前介绍的 NPM 就可以使用简单的指令安装,指令如下,

这边建议需要将此套件安装成为全域模组,方便日后使用。

Express 基本操作

express 的使用也十分简单,先来建立一个基本的hello world ,

var app = require('express').createServer(),
port = 1337;

app.listen(port);

app.get('/', function(req, res){
    res.send('hello world');
});

console.log('start express servern');

可以从上面的程式码发现,基本操作与node.js http 的建立方式没有太大差异,主要差在当我们设定路由时,可以直接透过 app.get 方式设定回应与接受方式。

Express 路由处理

Express 对于 http 服务上有许多包装,让开发者使用及设定上更为方便,例如有几个路由设定,那我们就统一藉由 app.get 来处理,

    // ... Create http server

app.get('/', function(req, res){
    res.send('hello world');
});

app.get('/test', function(req, res){
    res.send('test render');
});

app.get('/user/', function(req, res){
    res.send('user page');
});

如上面的程式码所表示,app.get 可以带入两个参数,第一个是路径名称设定,第二个为回应函式(call back function),回应函式里面就如同之前的 createServer 方法,里面包含 request, response 两个物件可供使用。使用者就可以透过浏览器,输入不同的url 切换到不同的页面,显示不同的结果。

路由设定上也有基本的配对方式,让使用者从浏览器输入的网址可以是一个变数,只要符合型态就可以有对应的页面产出,例如,

    // ... Create http server

app.get('/user/:id', function(req, res){
    res.send('user: ' + req.params.id);
});

app.get('/:number', function(req, res){
    res.send('number: ' + req.params.number);
});

里面使用到:number ,从网址输入之后就可以直接使用 req.params.number 取得所输入的资料,变成url 参数使用,当然前面也是可以加上路径的设定, /user/:id,在浏览器上路径必须符合 /user/xxx,透过 req.params.id 就可以取到 xxx这个字串值。

另外,express 参数处理也提供了路由参数配对处理,也可以透过正规表示法作为参数设定,

var app = require('express').createServer(),
    port = 1337;

app.listen(port);

    app.get(/^/ip?(?:/(d{2,3})(?:.(d{2,3}))(?:.(d{2,3}))(?:.(d{2,3})))?/, function(req, res){
        res.send(req.params);
    });

上面程式码,可以发现后面路由设定的型态是正规表示法,里面设定格式为 /ip 之后,必须要加上ip 型态才会符合资料格式,同时取得ip资料已经由正规表示法将资料做分群,因此可以取得ip的四个数字。

此程式执行之后,可以透过浏览器测试,输入网址为 localhost:3000/ip/255.255.100.10,可以从页面获得资料,

此章节全部範例程式码如下,

/**
 * @overview
 *
 * @author Caesar Chi
 * @blog clonn.blogspot.com
 * @version 2012/02/26
 */

    // create server.
    var app = require('express').createServer(),
        port = 1337; 

    app.listen(port);

    // normal style
    app.get('/', function(req, res){
        res.send('hello world');
    });

    app.get('/test', function(req, res){
        res.send('test render');
    });

    // parameter style
    app.get('/user/:id', function(req, res){
        res.send('user: ' + req.params.id);
    });

    app.get('/:number', function(req, res){
        res.send('number: ' + req.params.number);
    });

    // REGX style
    app.get(/^/ip?(?:/(d{2,3})(?:.(d{2,3}))(?:.(d{2,3})))?/, function(req, res){
        res.send(req.params);
    });

    app.get('*', function(req, res){
        res.send('Page not found!', 404);
    });

    console.log('start express servern');

Express middleware

Express 里面有一个十分好用的应用概念称为middleware,可以透过 middleware 做出複杂的效果,同时上面也有介绍 next 方法参数传递,就是靠 middleware 的概念来传递参数,让开发者可以明确的控制程式逻辑。

// .. create http server
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session());

上面都是一种 middleware 的使用方式,透过 app.use 方式里面载入函式执行方法,回应函式会包含三个基本参数,response, request, next,其中next 表示下一个 middleware 执行函式,同时会自动将预设三个参数继续带往下个函式执行,底下有个实验,

上面的片段程式执行后,开启浏览器,连结上 localhost:1337/,会发现伺服器回应结果顺序如下,

first middle ware
second middle ware
execute middle ware
end middleware function

从上面的结果可以得知,刚才设定的 middleware 都生效了,在 app.use 设定的 middleware 是所有url 皆会执行方法,如果有指定特定方法,就可以使用 app.get 的 middleware 设定,在 app.get 函式的第二个参数,就可以带入函式,或者是匿名函式,只要函式里面最后会接受 request, response, next 这三个参数,同时也有正确指定 next 函式的执行时机,最后都会执行到最后一个方法,当然开发者也可以评估程式逻辑要执行到哪一个阶段,让逻辑可以更为分明。

Express 路由应用

在实际开发上可能会遇到需要使用参数等方式,混和变数一起使用,express 里面提供了一个很棒的处理方法 app.all 这个方式,可以先採用基本路由配对,再将设定为每个不同的处理方式,开发者可以透过这个方式简化自己的程式逻辑,

/**
 * @overview
 *
 * @author 
 * @version 2012/02/26
 */

    // create server.
    var app = require('express').createServer(),
        port = 1337, 
        users = [
            {name: 'Clonn'},
            {name: 'Chi'}
        ];

    app.listen(port);

    app.all('/user/:id/:op?', function(req, res, next){
        req.user = users[req.params.id];
        if (req.user) {
            next();
        } else {
            next(new Error('cannot find user ' + req.params.id));
        }
    });

    app.get('/user/:id', function(req, res){
        res.send('viewing ' + req.user.name);
    });

    app.get('/user/:id/edit', function(req, res){
        res.send('editing ' + req.user.name);
    });

    app.get('/user/:id/delete', function(req, res){
        res.send('deleting ' + req.user.name);
    });

    app.get('*', function(req, res){
        res.send('Page not found!', 404);
    });

    console.log('start express servern');

内部宣告一组预设的使用者分别给予名称设定,藉由 app.all 这个方法,可以先将路由雏形建立,再接下来设定 app.get 的路径格式,只要符合格式就会分配进入对应的方法中,像上面的程式当中,如果使用者输入路径为 /user/0 ,除了执行 app.all 程式之后,执行next 方法就会对应到路径设定为 /user/:id 的这个方法当中。如果使用者输入路径为 /user/0/edit ,就会执行到 /user/:id/edit 的对应方法。

Express GET 应用範例

我们準备一个使用GET方法传送资料的表单。

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf8">
		<title>Node.js菜鸟笔记(1)</title>	
		<link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
	</head>
	<body> 
		<h1>Node.js菜鸟笔记-注册</h1>
		<form id="signup" method="GET" action="http://localhost:3000/Signup">
			<label>使用者名称:</label><input type="text" id="username" name="username" /><br>
			<label>电子邮件:</label><input type="text" id="email" name="email" /><br>
			<input type="submit" value="注册我的帐号" /><br>
		</form>
	</body>
</html>

这个表单没有什幺特别的地方,我们只需要看第9行,form 使用的 method 是 GET,然后 action 是 http://localhost:3000/Signup,等一下我们要来撰写 /Signup 这个 URL Path 的处理程式。

处理 Signup 行为

我们知道所谓的 GET 方法,会透过 URL 来把表单的值给带过去,以上面的表单来说,到时候URL会以这样的形式传递

http://localhost:3000/Signup?username=xxx&email=xxx

所以要能处理这样的资料,必须有以下功能:

  • 解析URL
  • 辨别动作是Signup
  • 解析出username和email

一旦能取得username和email的值,程式就能加以应用了。

处理 Signup 的程式码雏形,

// load module
var url  = require('url');

urlData = url.parse(req.url,true);
action = urlData.pathname;
res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"});

if (action === "/Signup") {
   user = urlData.query;
   res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>");
}

首先需要加载 url module,它是用来协助我们解析URL的模组,接着使用 url.parse 方法,第一个传入url 字串变数,也就是req.url。另外第二个参数的用意是,设为ture则引进 querystring模组来协助处理,预设是false。它影响到的是 urlData.query,设为true会传回物件,不然就只是一般的字串。url.parse 会将字串内容整理成一个物件,我们把它指定给urlData。

action 变数作为记录pathname,这是我们稍后要来判断目前网页的动作是什幺。接着先将 html 表头资讯 (Header)準备好,再来判断路径逻辑,如果是 /Signup 这个动作,就把urlData.query里的资料指定给user,然后输出user.username和user.email,把使用者从表单注册的资料显示于页面中。

最后进行程式测试,启动 node.js 主程式之后,开启浏览器就会看到表单,填写完毕按下送出,就可以看到结果了。

完整 node.js 程式码如下,

var http = require('http'),
    url  = require('url'),
    fs   = require("fs"),
    server;

server = http.createServer(function (req,res) {
    var urlData,
        encode   = "utf8",
        filePath = "view/express_get_example_form.html",
        action;

    urlData = url.parse(req.url,true);
    action = urlData.pathname;
    res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"});

    if (action === "/Signup") {
       user = urlData.query;
       res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>");
    }
    else {
        fs.readFile(filePath, encode, function(err, file) {

            res.write(file);
            res.end();
        });
    }
        
});

server.listen(3000);

console.log('Server跑起来了,现在时间是:' + new Date());

Express POST 应用範例

一开始準备基本的 html 表单,传送内容以 POST 方式, form 的 action 属性设定为 POST,其余 html 内容与前一个範例应用相同,

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf8">
		<title>Node.js菜鸟笔记(1)</title>	
		<link rel="stylesheet" href="css/style.css" type="text/css" media="all" />
	</head>
	<body> 
		<h1>Node.js菜鸟笔记-注册</h1>
		<form id="signup" method="POST" action="http://localhost:3000/Signup">
			<label>使用者名称:</label><input type="text" id="username" name="username" /><br>
			<label>电子邮件:</label><input type="text" id="email" name="email" /><br>
			<input type="submit" value="注册我的帐号" /><br>
		</form>
	</body>
</html>

node.js 的程式处理逻辑与前面 GET 範例类似,部分程式码如下,

qs   = require('querystring'),

if (action === "/Signup") {
    formData = '';
    req.on("data", function (data) {

        formData += data;

    });

    req.on("end", function () {
        user = qs.parse(formData);
        res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>");
    });
}

主要加入了’querystring’ 这个moduel,方便我们等一下解析由表单POST回来的资料,另外加入一个formData的变数,用来搜集待等一下表单回传的资料。前面的GET 範例,我们只从req 拿出url的资料,这次要在利用 req 身上的事件处理。

JavaScript在订阅事件时使用addEventListener,而node.js使用的则是on。这边加上了监听 data 的事件,会在浏览器传送资料到 Web Server时被执行,参数是它所接收到的资料,型态是字串。

接着再增加 end 的事件,当浏览器的请求事件结束时,它就会动作。

由于浏览器使用POST在上传资料时,会将资料一块块地上传,因为我们在监听data事件时,透过formData 变数将它累加起来< 不过由于我们上传的资料很少,一次就结束,不过如果日后需要传的是资料比较大的档案,这个累加动作就很重要。

当资料传完,就进到end事件中,会用到 qs.parse来解析formData。formData的内容是字串,内容是:

username=wordsmith&email=wordsmith%40some.where

而qs.parse可以帮我们把这个querystring转成物件的格式,也就是:

{username=wordsmith&email=wordsmith%40some.where}

一旦转成物件并指定给user之后,其他的事情就和GET方法时操作的一样,写response的表头,将内容回传,并将user.username和user.email代入到内容中。

修改完成后,接着执行 node.js 程式,启动 web server ,开启浏览器进入表单测试看看,POST 的方式能否顺利运作。

完整程式码如下,

var http = require('http'),
    url  = require('url'),
    fs   = require("fs"),
    qs   = require('querystring'),
    server;

server = http.createServer(function (req,res) {
    var urlData,
        encode   = "utf8",
        filePath = "view/express_post_example_form.html",
        formData,
        action;

    urlData = url.parse(req.url,true);
    action = urlData.pathname;
    res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"});

    if (action === "/Signup") {
        formData = '';
        req.on("data", function (data) {

            formData += data;

        });

        req.on("end", function () {
            user = qs.parse(formData);
            res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>");
        });
    }
    else {
        fs.readFile(filePath, encode, function(err, file) {

            res.write(file);
            res.end();
        });
    }
        
});

server.listen(3000);

console.log('Server跑起来了,现在时间是:' + new Date());

Express AJAX 应用範例

在Node.js要使用Ajax传送资料,并且与之互动,在接受资料的部份没有太大的差别,client端不是用GET就是用POST来传资料,重点在处理完后,用JSON格式回传。当然Ajax不见得只传JSON格式,有时是回传一段HTML码,不过后者对伺服器来说,基本上就和前两篇没有差别了。所以我们还是以回传JSON做为这一回的主题。

这一回其实大多数的工作都会落在前端Ajax上面,前端要负责发送与接收资料,并在接收资料后,撤掉原先发送资料的表单,并将取得的资料,改成HTML格式之后,放上页面。

首先先準备 HTML 静态页面资料,

<!DOCTYPE html>
<html>
    <head>
        <title>Node.js菜鸟笔记(3)</title>   
            <link rel="stylesheet" href="/css/style.css" type="text/css" media="all" />
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
    </head>
    <body> 
        <h1>Node.js菜鸟笔记-注册(用Ajax)</h1>
            <form id="signup" action="/Signup" method="POST">
                    <label>使用者名称:</label><input type="text" id="username" name="username" /><br>
                    <label>电子邮件:</label><input type="text" id="email" name="email" /><br>
                    <input type="submit" value="注册我的帐号" /><br>
            </form>
            <div id="result">
            </div>
            <script type="text/javascript">
                $(function(){
                   $('#signup input[type="submit"]').click(function(e){                          
                       var user = {};
                       user.username = $("#username").val();
                       user.email = $("#email").val();

                       $.post("/Signup",user,function(data){
                            greet(data)                             
                       });

                       e.preventDefault();
                    });
                           
                    var greet = function(msg){
                        var resultNode = $('#result'),
                            greeting = $('<h2>Hi,'+msg.username+',你的会员id是:'+ msg.id +'我们会将会员启动信寄至'+msg.email+'</h2>');

                        $('#signup').hide();
                        resultNode.html("");
                        resultNode.append(greeting);
                    };       
                });      
            </script>           
    </body>
</html>

HTML 页面上準备了一个表单,用来传送注册资料。接着直接引用了 Google CDN 来载入 jQuery,用来帮我们处理 Ajax 的工作,这次要传送和接收的工作,很大的变动都在 HTML 页面上的 JavaScript当中。我们要做的事有(相关 jQuery 处理这边不多做赘述,指提起主要功能解说):

  • 用jQUery取得submit按钮,绑定它的click动作
  • 取得表单username和email的值,存放在user这个物件中
  • 用jQuery的$.post方法,将user的资料传到Server
  • 一旦成功取得资料后,透过greet这个function,组成回报给user的讯息
  • 清空原本给使用者填资料的表单
  • 将Server回传的username、email和id这3个资料,组成回应的讯息
  • 将讯息放到原本表单的位置

经过以上的处理后,一个Ajax的表单的基本功能已经完成。

接着进行 node.js 主要程式的编辑,部分程式码如下,

var fs   = require("fs"),
    qs   = require('querystring');

if (action === "/Signup") {
    formData = '';
    req.on("data", function (data) {

        formData += data;

    });

    req.on("end", function () {
        var msg;

        user = qs.parse(formData);
        user.id = "123456";
        msg = JSON.stringify(user);
        res.writeHead(200, {"Content-Type":"application/json; charset=utf-8","Content-Length":msg.length});
        res.end(msg);
    });
}

这里的程式和前面 POST 範例,基本上大同小异,差别在:

  • 帮user的资料加上id,随意存放一些文字进去,让Server回传的资料多于Client端传上来的,不然会觉得Server都没做事。
  • 增加了msg这个变数,存放将user物件JSON文字化的结果。JSON.stringify这个转换函式是V8引擎所提供的,如果你好奇的话。
  • 大重点来了,我们要告诉Client端,这次回传的资料格式是JSON,所在Content-type和Content-Length要提供给Client。

Server很轻鬆就完成任务了,最后进行程式测试,启动 node.js 主程式之后,开启浏览器就会看到表单,填写完毕按下送出,就可以看到结果了。

最后 node.js 本篇範例程式码如下,

var http = require('http'),
    url  = require('url'),
    fs   = require("fs"),
    qs   = require('querystring'),
    server;

server = http.createServer(function (req,res) {
    var urlData,
        encode   = "utf8",
        filePath = "view/express_ajax_example_form.html",
        formData,
        action;

    urlData = url.parse(req.url,true);
    action = urlData.pathname;

    if (action === "/Signup") {
        formData = '';
        req.on("data", function (data) {

            formData += data;

        });

        req.on("end", function () {
            var msg;

            user = qs.parse(formData);
            user.id = "123456";
            msg = JSON.stringify(user);
            res.writeHead(200, {"Content-Type":"application/json;","Content-Length":msg.length});
            res.end(msg);
        });
    }
    else {
        fs.readFile(filePath, encode, function(err, file) {
            res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"});
            res.write(file);
            res.end();
        });
    }
        
});

server.listen(3000);

console.log('Server跑起来了,现在时间是:' + new Date());