Express框架搭建项目

发布时间:2024年01月15日

1. Express简介

EXpress: 精简的、灵活的Node.js Web程序框架,为构建单页、多页及混合的Web程序提供了一系列健壮的功能特性。

  • 精简: Express在你的想法和服务器之间充当薄薄的一层,尽量少干预你,在你充分表达自己思想的同时,提供一些有用的东西。
  • 灵活: 可扩展。Express提供了一个非常精简的框架,可以根据自己的需求添加Express功能中的不同部分,替换掉不能满足需求的部分。
  • Web程序框架: 网站是 Web 程序,网页也是 Web 程序。但 Web 程序的含义不止这些, 它还可以向其他 Web 程序提供功能(还有别的)。一般而言,“程序”是具有功能的, 它不止是内容的静态集合(尽管这也是非常简单的 Web 程序)。
  • 单页Web程序: 之前的网站,用户每次访问不同的页面都要发起网络请求,单页Web程序把整个网站都下载到客户端浏览器上。经过初始下载后,用户访问不同页面的速度加快了,几乎不需要或只需要很少的服务端通信。单页程序的开发可以使用 Angular 或 Ember 等流行框架,Express 跟它们都 配合得很好。
  • 多页和混合的Web程序: 多页Web程序是更传统的方式。网站上的每个页面都是通过向服务器发起单独的请求得到的。这种方式确实比较传统,但这并不意味着它没有优点,或者说单页程序更好。 只是现在有更多选择了,你可以决定哪些内容应该作为单页程序提供,哪些应该通过不 同的请求提供。“混合”说的就是同时使用这两种方式的网站

2. 先导知识

2.1 开发必备环境条件

  • 本地有Node、npm包管理工具
  • 有开发工具,例如VS code

2.2 使用Node实现简单的Web服务器

  • 当访问localhost:3000端口时,页面会打印Hello world,终端会输出console结果
var http = require('http'); 
http.createServer(function(req,res){ 
	res.writeHead(200, { 'Content-Type': 'text/plain' });
	res.end('Hello world!'); 
}).listen(3000);
console.log('Server started on localhost:3000; press Ctrl-C to terminate....');
  • 多路由时,页面书写
var http = require('http'); 
http.createServer(function(req,res){ // 规范化 url,去掉查询字符串、可选的反斜杠,并把它变成小写
	var path = req.url.replace(/\/?(?:\?.*)?$/, '').toLowerCase();
	switch(path) {
		case '':
			res.writeHead(200, { 'Content-Type': 'text/plain' }); 
			res.end('Homepage');
			break;
		case '/about': 
			res.writeHead(200, { 'Content-Type': 'text/plain' }); 
			res.end('About');
			break;
	default:
			res.writeHead(404, { 'Content-Type': 'text/plain' }); 
			res.end('Not Found');
			break; 
	}
}).listen(3000);
console.log('Server started on localhost:3000; press Ctrl-C to terminate....');
  • 静态资源服务
var http = require('http'), 
		fs = require('fs');
function serveStaticFile(res, path, contentType, responseCode) {
	if(!responseCode) responseCode = 200;
	fs.readFile(__dirname + path, function(err,data) {
		if(err) {
			res.writeHead(500, { 'Content-Type': 'text/plain' }); 
			res.end('500 - Internal Error'); 
		} else {
			res.writeHead(responseCode, { 'Content-Type': contentType }); 
			res.end(data); 
		} 
	});
}
http.createServer(function(req,res){ // 规范化 url,去掉查询字符串、可选的反斜杠,并把它变成小写
	var path = req.url.replace(/\/?(?:\?.*)?$/, '') .toLowerCase();
	switch(path) {
		case '':
			serveStaticFile(res, '/public/home.html', 'text/html');
			break;
		case '/about': 
			serveStaticFile(res, '/public/about.html', 'text/html');
			break;
		case '/img/logo.jpg': 
			serveStaticFile(res, '/public/img/logo.jpg', 'image/jpeg');
			break;
		default:
			serveStaticFile(res, '/public/404.html', 'text/html', 404);
			break; 
	} 
}).listen(3000); 
console.log('Server started on localhost:3000; press Ctrl-C to terminate....');

3. 省时省力的Express

3.1 安装环境

  • npm init 生成一个package.json文件
  • npm install --save express 安装express框架

3.2 .gitignore

忽略无需添加到代码库的内容

# ignore packages installed by npm 
 node_modules 
 # put any other files you don't want to check in here, 
 # such as .DS_Store (OSX), *.bak, etc.

3.2 创建入口文件

可以使用index.js,也可以使用与项目相关的[有意义名称].js。

var express = require('express');  // 引入express框架
var app = express(); 
app.set('port', process.env.PORT || 3000);  // 设置执行端口
// 定制 404 页面 
app.use(function(req, res){ 
	res.type('text/plain'); 
	res.status(404); 
	res.send('404 - Not Found'); 
}); 
// 定制 500 页面 
app.use(function(err, req, res, next){ 
	console.error(err.stack); 
	res.type('text/plain'); 
	res.status(500); 
	res.send('500 - Server Error'); 
}); 
// 监听端口,执行内容
app.listen(app.get('port'), function(){ 
	console.log( 'Express started on http://localhost:' + app.get('port') + '; press Ctrl-C to terminate.' ); 
});

3.3 添加路由

  • app.get(路径, 函数)
app.get('/', function(req, res){ 
	res.type('text/plain'); 
	res.send('Meadowlark Travel'); 
}); 
app.get('/about', function(req, res){ 
	res.type('text/plain'); 
	res.send('About Meadowlark Travel'); 
});
NodeExpress
res.endres.send
res.writeHeadres.set、res.status、res.type

Tip:
我们对定制的404页面和500页面的处理与对普通页面的处理有所区别,普通页面路由使用的是app.get(),而404和500页面使用的是app.use()。app.use()是Express添加中间件的一种方法,可以看做处理所有没有匹配路由路径的处理器。在Express中,路由和中间件的添加顺序至关重要。如果将404处理器放在所有路由的上面,则所有路由都不能访问了,访问得到的URL都是404.通配符匹配也要放在下面的位置,避免影响了其他子路由等。例如/about*就要放在/about/contact等下面。

3.4 视图和布局

3.4.1 引入模板框架

模板框架Handlebars,不会对HTML进行抽象,编写的是带特殊标签的HTML,Handlebars 可 以借此插入内容。

  • npm install --save express3-handlebars 安装视图模板框架
	var app = express(); // 设置 handlebars 视图引擎
	var handlebars = require('express3-handlebars') 
		.create({ defaultLayout:'main' });
	app.engine('handlebars', handlebars.engine); 
	app.set('view engine', 'handlebars');

这段代码创建了一个视图引擎,并对 Express 进行了配置,将其作为默认的视图引擎。接 下来创建 views 目录,在其中创建一个子目录 layouts。如果你是一位经验丰富的 Web 开发 人员,可能已经熟悉布局的概念了(有时也被称为“母版页”)。在开发网站时,每个页面 上肯定有一定数量的 HTML 是相同的,或者非常相近。在每个页面上重复写这些代码不仅 非常繁琐,还会导致潜在的维护困境:如果你想在每个页面上做一些修改,那就要修改所 有文件。布局可以解决这个问题,它为网站上的所有页面提供了一个通用的框架。

3.4.2 创建views/layouts/main.handlebars文件

{{!-- 给网站创建模板 --}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="/css/main.css">
</head>
<body>
    <header>
        <img class="logo" src="/img/moon.jpg" alt="图片加载失败">
    </header>
    {{{body}}}
</body>
</html>

入口js文件中.create({ 'defaultLayout': 'main' });意味着除非你特别指明,否则视图都用这个布局。之后不同页面的代码都会插入到body处。

3.4.3 创建views/home.handlebars页面

在layouts同级,可以创建自定义的home页面、about页面、404页面、500页面等

home.handlebars
<h1>Welcome to Meadowlark Travel</h1>

因为html相关内容在main.handlebars布局页面都已经存在了,所以我们需要要写body处想要的内容即可。同时,可以修改一下访问home的路由,从直接send内容改为引入某个具体的文件

// get 方法配置路由
app.get('/', function(req, res) {
    // res.type('text/plain')
    // res.send('Home Page')
    res.render('home')
})
// 定制500页面
app.use(function(err, req, res, next) {
    // console.log(err.stack)
    // res.type('text/plain')
    // res.status(500)
    // res.send('500 - Server Error')
    console.log(err.stack)
    res.status(500)
    res.render(500)
})

此时,我们普通页面已经不需要返回状态码和指定内容了,视图引擎默认会返回 text/html 的内容类型和 200 的状态码。在 catch-all 处理器(提供定制的 404 页面)以及 500 处理器中, 我们必须明确设定状态码。

不使用模板的配置:

app.get('/nolay', function(req, res) {
    res.render('nolay', {layout: null})
})

3.4.4 视图和静态文件

Express 靠中间件处理静态文件和视图。static 中间件可以将一个或多个目录指派为包含静态资源的目录,其中的资源不经过任何 特殊处理直接发送到客户端。你可以在其中放图片、CSS 文件、客户端 JavaScript 文件之 类的资源。

存放静态文件,我们可以在根目录中创建public文件夹,文件夹内放置一张我们需要的图片信息。

在入口文件.js中修改

// 静态资源目录,应该放在所有路由之前,内部所有文件对外开放
app.use(express.static(__dirname + '/public'));

static 中间件相当于给你想要发送的所有静态文件创建了一个路由,渲染文件并发送给客 户端。接下来我们在 public 下面创建一个子目录 img,并把 logo.png 文件放在其中。 现在我们可以直接指向 /img/logo.png(注意:路径中没有 public,这个目录对客户端来说是 隐形的),static 中间件会返回这个文件,并正确设定内容类型。接下来我们修改一下布 局文件,以便让我们的 logo 出现在所有页面上

main.handlebars中引入图片

<img class="logo" src="/img/moon.jpg" alt="图片加载失败">

3.4.5 视图中动态内容

  • 视图并不只是一种传递静态 HTML 的复杂方式(尽管它们当然能做到)。视图真正的强大 之处在于它可以包含动态信息。

  • 我们可以创建一个用来保存模块的目录。名称任意,但一般被称为lib(library的缩写),与public、views同级,可以在该目录下创建一个fortune.js文件

lib/fortune.js
// 动态数据
var fortuneCookies = [
    "Conquer your fears or they will conquer you.",
    "Rivers need springs.",
    "Do not fear what you don nott know.",
    "You will have a pleasant surprise.",
    "Whenever possible, keep it simple."
]
// 处理动态数据的方法,随机返回某个数据
exports.getFortune = function() {
    const idx = Math.floor(Math.random() * fortuneCookies.length);
    return fortuneCookies[idx]
}

上述的数据我们想要在about页面中进行使用,

  • 第一步:需要在入口文件.js中引入这个文件信息
var fortunes = require('./lib/fortune.js')
  • 第二步:在入口文件.js对于about页面的路由进行修改,将值传入进去
app.get('/about', function(req, res) {
    // res.type('text/plain')
    // res.send('About Page')
    res.render('about', {fortune: fortunes.getFortune()})
})
  • 第三步:修改about页面
<h1>About Meadowlark Travel</h1>

<p>Your fortune for the day:</p>
<blockquote>{{fortune}}</blockquote>
  • 第四步: 访问about页面,或刷新页面时我们可以看到效果,每次都会随机展示fortune值

3.4.6 代码结构

上述路由、路由对应页面、布局组件最终形成一个简单的express项目,项目结构如下图
在这里插入图片描述

4. 请求和响应对象

在这里插入图片描述

  • 协议: 协议确定如何传输请求。我们主要是处理http和https。其他常见的协议还有file和ftp。
  • 主机名: 主机名标识服务器。运行在本地计算机(localhost)和本地网络的服务器可以简单地表 示,比如用一个单词,或一个数字 IP 地址。在 Internet 环境下,主机名通常以一个顶 级域名(TLD)结尾,比如 .com 或 .net。另外,也许还会有子域名作为主机名的前缀。子域名可以是任何形式的,其中 www 最为常见。子域名通常是可选的。
  • 端口: 每一台服务器都有一系列端口号。一些端口号比较特殊,如80端口和443端口。如果省略端口值,那么默认80端口负责HTTP传输,443端口负责HTTPS传输。如果不使用这两个端口的话,那么就需要一个大于1023的端口号。
  • 路径: 路径是应用中的页面或其他资源的唯一标识。
  • 查询字符串: 查询字符串是一种键值对集合,是可选的。它以?开头,键值对则以&区分开。所有名称和值都必须是URL编码的。
  • 信息片段: 信息片段(或散列)被严格限制在浏览器中使用,不会传递到服务器。用它控制单页应用或AJAX富应用越来越普遍。最初,信息片段只是用来让浏览器展现文档中通过锚点标记<a id="chapter06">指定的部分

4.1 请求报头

我们在浏览网页时,发送到服务器的并不只是URL。当你访问一个网站时,浏览器会发送很多“隐形”信息。服务器会因此得知优先响应哪种语言的页面,它也会发送“用户代理”信息(浏览器、操作系统和硬件设备)和一些其他信息。可以通过以下代码查看请求头信息

app.get('/headers', function(req,res){ 
	res.set('Content-Type','text/plain');
	var s = '';
	for(var name in req.headers) s += name + ': ' + req.headers[name] + '\n';
	res.send(s); 
});

4.2 响应报头

  • 正如浏览器以请求报头的形式发送隐藏信息到服务器,当服务器响应时,同样会回传一些 浏览器没必要渲染和显示的信息,通常是元数据和服务器信息。我们已经熟悉内容类型头 信息,它告诉浏览器正在被传输的内容类型(网页、图片、样式表、客户端脚本等)。特 别要注意的是,不管 URL 路径是什么,浏览器都根据内容类型报头处理信息。因此你可 以通过一个叫作 /image.jpg 的路径提供网页,也可以通过一个叫作 /text.html 的路径提供图 片。(这样做并不合情理,这里要讲的重点是路径是抽象的,浏览器只根据内容类型来决 定内容该如何渲染。)除了内容类型之外,报头还会指出响应信息是否被压缩,以及使用 的是哪种编码。响应报头还可以包含关于浏览器对资源缓存时长的提示。
  • 响应报头还经常会包含一些关于服务器的 信息,一般会指出服务器的类型,有时甚至会包含操作系统的详细信息。返回服务器信息 存在一个问题,那就是它会给黑客一个可乘之机,从而使站点陷入危险。非常重视安全的 服务器经常忽略此信息,甚至提供虚假信息。
  • 禁用 Express 的 X-Powered-By 头信息: app.disable('x-powered-by');

4.3 互联网媒体类型

内容类型报头信息非常重要,没有它客户端很难判断如何渲染接收到的内容。内容类型报头就是一种互联网媒体类型,由一个类型、一个子类型及可选参数组成。在这里插入图片描述
例如, text/html;charset=UTF-8 说明类型是 text,子类型是 html,字符编码是 UTF-8。互联网编号分配机构维护了一个官方的互联网媒体类型清单(http://www.iana.org/assignments/media- types/media-types.xhtml)。我们常见的 content type、Internet media type 和 MIME type 是可 以互换的。MIME(多用途互联网邮件扩展)是互联网媒体类型的前身,它们大部分是相同的。

4.4 请求体

除请求报头外,请求还有一个主体(就像作为实际内容返回的响应主体一样)。一般 GET 请求没有主体内容,但 POST 请求是有的。POST 请求体最常见的媒体类型是 application/ x-www-form-urlendcoded,是键值对集合的简单编码,用 & 分隔(基本上和查询字符串的 格式一样)。如果 POST 请求需要支持文件上传,则媒体类型是 multipart/form-data,它是 一种更为复杂的格式。最后是 AJAX 请求,它可以使用 application/json。

4.5 参数

对于任何一个请求,参数可以来自查询字符串、会话、请求体或指定的路由参数。在 Node 应用中,请求对象的参数方法会重写所有的参数。

4.6 请求对象

请求对象(通常传递到回调方法,这意味着你可以随意命名,通常命名为 req 或 request)的生命周期始于 Node 的一个核心对象 http.IncomingMessage 的实例。

  • req.params:
    一个数组,包含命名过的路由参数。
  • req.param(name):
    返回命名的路由参数,或者GET请求或POST请求参数。
  • req.query:
    一个对象,包含以键值对存放的查询字符串参数(通常称为GET请求参数)。
  • req.body:
    一个对象,包含post请求参数。这样命名是因为POST请求参数在REQUEST正文中传递,而不像查询字符串在URL中传递。要使req.body可用,需要中间件能够解析请求正文内容类型
  • req.route:
    关于当前匹配的路由信息。主要用于路由调试
  • req.cookies/req.signedCookies:
    一个对象。包含从客户端传递过来的cookie值
  • req.headers:
    从客户端接收到的请求报头
  • req.accepts([types]):
    一个简便的方法,用来确定客户端是否接受一个或一组指定的类型(可选类型可以是 单个的 MIME 类型,如 application/json、一个逗号分隔集合或是一个数组)。写公共 API 的人对该方法很感兴趣。假定浏览器默认始终接受 HTML。
  • req.ip:
    客户端的ip地址
  • req.path:
    请求路径(不包含协议、主机、端口号或查询字符串)
  • req.host:
    一个简便的方法,用来返回客户端所报告的主机名。这些信息可以用来伪造,所以不应该用户安全目的
  • req.xhr:
    一个简便属性,如果请求由 Ajax 发起将会返回 true。
  • req.protocol:
    用于标识请求的协议(http或https)
  • req.secure:
    一个简便属性。如果连接是安全的,则返回true。等同于 req.protocol===‘https’
  • req.url/req.originalUrl:
    有点用词不当,这些属性返回了路径和查询字符串(它们不包含协议、主机或端口)。 req.url 若是出于内部路由目的,则可以重写,但是 req.orginalUrl 旨在保留原始请求 和查询字符串。
  • req.acceptedLanguages:
    一个简便方法,用来返回客户端首选的一组(人类的)语言。这些信息是从请求报头中 解析而来的。

5. 响应对象

响应对象(通常传递到回调方法,这意味着你可以随意命名它,通常命名为 res、resp 或 response)的生命周期始于 Node 核心对象 http.ServerResponse 的实例。Express 添加了一 些附加功能。

  • res.status(code):
    设置HTTP状态码。Express默认为200(成功),所以你可以使用这个方法返回状态码404(页面不存在)或500(服务器内部错误),或任何其他一个状态码。对于重定向(状态码301、302、303、307),有一个更好的方法redirect
  • res.set(name,value):
    设置响应头。这通常不需要手动设置
  • res.cookie(name,value,[options]),res.clearCookie(name,[options]):
    设置或清除客户端cookie值。需中间件支持
  • res.redirect([status],url):
    重定向浏览器。默认重定向代码是 302(建立)。通常,你应尽量减少重定向,除非永 久移动一个页面,这种情况应当使用代码 301(永久移动)。
  • res.send(body),res.send(status,body):
    向客户端发送响应及可选的状态码。Express 的默认内容类型是 text/html。如果你想改 为 text/plain,需要在 res.send 之前调用 res.set(‘Content-Type’,‘text/plain’)。如 果 body 是一个对象或一个数组,响应将会以 JSON 发送(内容类型需要被正确设置), 不过既然你想发送 JSON,我推荐你调用 res.json。
  • res.json(json), res.json(status,json):
    向客户端发送JSON以及可选的状态码。
  • res.jsonp(json), res.jsonp(status,json):
    向客户端发送jsonp及可选的状态码。
  • res.type(type):
    一个简便的方法,用于设置 Content-Type 头信息。基本上相当于 res.set(‘Content- Type’,‘type’),只是如果你提供了一个没有斜杠的字符串,它会试图把其当作文件的 扩展名映射为一个互联网媒体类型。比如,res.type(‘txt’) 会将 Content-Type 设为 text/plain。此功能在有些领域可能会有用(例如自动提供不同的多媒体文件),但是 通常应该避免使用它,以便明确设置正确的互联网媒体类型。
  • res.format(object):
    这个方法允许你根据接收请求报头发送不同的内容。这是它在 API 中的主要用途.这里有一个非常简单的例子:res.format({‘text/plain’:‘hi there’,‘text/html’:‘hi there’})。
  • res.attachment([filename]),res.download(path,[filename],[callback]):
    这两种方法会将响应报头 Content-Disposition 设为 attachment,这样浏览器就会选 择下载而不是展现内容。你可以指定 filename 给浏览器作为对用户的提示。用 res. download 可以指定要下载的文件,而 res.attachment 只是设置报头。另外,你还要将 内容发送到客户端。
  • res.sendFile(path,[option],[callback]):
    这个方法可根据路径读取指定文件并将内容发送到客户端。使用该方法很方便。使用静 态中间件,并将发送到客户端的文件放在公共目录下,这很容易。然而,如果你想根据 条件在相同的 URL 下提供不同的资源,这个方法可以派上用场。
  • res.links(links)
    设置链接响应报头。这是一个专用的报头,在大多数应用程序中几乎没有用处。
  • res.locals,res.render(view,[locals],callback)
    res.locals 是一个对象,包含用于渲染视图的默认上下文。res.render 使用配置的模板引擎渲染视图(不能把 res.render 的 locals 参数与 res.locals 混为一谈,上下文 在 res.locals 中会被重写,但在没有被重写的情况下仍然可用)。res.render 的默认响 应代码为 200,使用 res.status 可以指定一个不同的代码。

6. express源码路径说明

  • lib/application.js Express 主接口。如果想了解中间件是如何接入的,或视图是如何被渲染的,可以看 这里。
  • lib/express.js 这是一个相对较短的 shell,是 lib/application.js 中 Connect 的功能性扩展,它返回一个 函数,可以用 http.createServer 运行 Express 应用。
  • lib/request.js 扩展了 Node 的 http.IncomingMessage 对象,提供了一个稳健的请求对象。关于请求对 象属性和方法的所有信息都在这个文件里。
  • lib/response.js 扩展了 Node 的 http.ServerReponse 对象,提供响应对象。关于响应对象的所有属性和 方法都在这个文件里。
  • lib/router/route.js 提供基础路由支持。尽管路由是应用的核心,但这个文件只有不到 200 行,你会发现它 非常地简单优雅。

7. 处理表单

7.1 简单表单处理

  • 处理基础表单
// 必须引入中间件 body-parser 
app.post('/process-contact', function(req, res){ 
	console.log('Received contact from ' + req.body.name + ' <' + req.body.email + '>'); // 保存到数据库…… 
	res.redirect(303, '/thank-you'); 
});
  • 更强大的表单处理
// 必须引入中间件 body-parser 
app.post('/process-contact', function(req, res){ 
	console.log('Received contact from ' + req.body.name + ' <' + req.body.email + '>');
	try { // 保存到数据库……
		return res.xhr ?
			res.render({ success: true })
			 : 
			res.redirect(303, '/thank-you');
	} catch(ex) {
			return res.xhr ? 
				res.json({ error: 'Database error.' })
				 : 
				res.redirect(303, '/database-error'); 
	} 
});

参考书籍-Node与Express开发

如果有用,点个赞呗~

总结用法,希望可以帮助到你,
我是Ably,你无须超越谁,只要超越昨天的自己就好~

文章来源:https://blog.csdn.net/weixin_44593720/article/details/125367324
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。