首页 > 编程笔记

Express到底是什么?

在正式进入主题之前,非常有必要先提供一点历史和背景信息。这意味着我们要谈一谈 JavaScript 和 Node。

JavaScript 的革命

JavaScript 的时代已经到来了。当初 JavaScript 作为客户端脚本语言,并不受人待见,可到了现在,它不仅完全统治了客户端,而且早已被用作服务器端语言,这一切多亏了 Node。

全 JavaScript 技术栈的前景是显而易见的:再也没有频繁的语境切换了!

你再也不需要从 JavaScript 切换到 PHP、C#、Ruby 或 Python(或任何其他服务器端语言)了。而且,全 JavaScript 技术栈提升了前端工程师们的能力,使他们可以转到服务器端编程。这并非说服务器端编程只是语言的事,关于服务器端编程还是有很多东西要学的。但是选择使用 JavaScript,至少语言不会是一个障碍了。

如果你做软件工程师的年头和我一样长,就会发现很多语言、框架和 API 曾风靡一时。有些的确发展起来了,而有的最终销声匿迹。

你或许自豪于自己快速学习新语言、新系统的能力。每一种新语言你都觉得有些熟悉,比如:
这种感觉当然不错,但也会让人疲惫。有时你不过是想把任务完成,并不想学一种全新的技术,也不想把搁置了几个月甚至几年的技能重新捡起来。

起初,JavaScript 看起来没有什么胜算。我也是这么看的,毋庸置疑。如果在 2007 年你告诉我说,你不仅会考虑把 JavaScript 作为首选语言,而且会写一套关于 JavaScript 的教程,我会说“你疯了”。

我也和大家一样对 JavaScript 有过偏见,认为它只是一种“玩具”语言,是只有外行和半吊子才会来捣鼓的东西。平心而论,JavaScript 为编程爱好者降低了门槛,加上那时有问题的 JavaScript 代码又太多了,这些都不利于这门语言的名声。用一句流行的话来说:“可恨的是玩家,不是游戏。”

遗憾的是人们受到这种偏见的影响,没能发现 JavaScript 这门语言是多么强大、灵活和优雅。我们现在知道,JavaScript 早在 1996 年就出现了(尽管它很多吸引人的特性是 2005 年加上的),然而,很多人直到现在才开始认真地看待它。

在你看到本文时,你可能没有这种偏见:或许像我一样,偏见成了过去式;又或许是你从来就没有过偏见。不管怎样,你是幸运的,我很乐意为你介绍 Express。

造就 Express 的,正是 JavaScript 这门给人带来惊喜的语言。

到 2009 年,距人们开始认识到 JavaScript 作为浏览器脚本语言的威力和表达力已经有几年了,Ryan Dahl 看到了 JavaScript 作为服务器端语言的潜力,于是 Node.js 诞生了。

此时正是互联网技术蓬勃发展的时代。Ruby 以及 Ruby on Rails 将学院派计算机科学的一些优秀思想,同它们自己的一些原创思想融合起来,向世界展示了一种开发网站和 Web 应用的更快的方式。微软公司为了证明自己在互联网时代的不可或缺而不懈努力,一方面大量地从学术殿堂汲取营养,另一方面从 Ruby 和 JavaScript 的成功及 Java 的失误中学到了不少,于是在 .NET 上取得了卓越的成就。

今天,多亏了像 Babel 这样的转译技术,Web 开发者才得以放心地使用 JavaScript 语言的最新特性,不用担心阻隔了使用老浏览器的用户。

在管理 Web 应用的依赖和保证性能方面,Webpack 成了通用的解决方案。

像 React、Angular 和 Vue 这样的框架,正在改变人们进行 Web 开发的方式,使得声明式 DOM 操作库(例如 jQuery)慢慢地变得不再重要,成了明日黄花。

现在是参与互联网技术发展的激动人心的时刻。让人惊奇的新想法(或重新恢复活力的旧想法)随处可见。很多年以来,创新精神和兴奋感从没有像今天这样强烈。

Express 简介

Express 的官网上把 Express 描述为“最小而又灵活的 Node.js Web 应用框架,为 Web 和移动应用提供了一个健壮的特性集”。可这到底是什么意思?我们把这句话分解一下。

最小

这是 Express 最吸引人的一个方面。很多时候,框架开发者忘记了通常“少即是多”。

Express 的哲学就是,在你的大脑与 Web 服务器之间提供最小的一层。这并不意味着它就不够健壮,或是没有太多有用的特性。相反,这意味着它在为你提供有用东西的同时,对你的限制更少,能够让你的想法得到更充分的表达。

Express 给你提供一个最小的框架,你可以根据需要加入 Express 的各部分功能,替换任何不符合你需要的功能。这给框架带来了可操作的空间。

太多的框架想要包办一切,你甚至还没开始写一行代码,你的项目就已经变得臃肿、神秘而又复杂。项目的第一项任务,往往就是砍掉不需要的功能或替换不符合需求的功能,结果时间都浪费了。Express 反其道而行之,允许你在需要的时候再加入东西。

灵活

最小的结果就是,Express 所做的事情是非常简单的:从客户端(可以是浏览器、移动设备、另一个服务器、桌面应用……只要能理解 HTTP)接收 HTTP 请求,然后返回 HTTP 响应。这个基本模式几乎可以描述任何到互联网的连接,使得 Express 的应用极其灵活。

Web 应用框架

或许更精确的说法是“Web 应用框架的服务器端部分”。

时至今日,当你想到“Web 应用框架”这个概念的时候,想到的多半是像 React、Angular 或 Vue 这样的单页应用框架。不过,除少数独立应用外,大多数 Web 应用需要分享数据和集成其他服务。这些工作一般需要一套 Web API 来完成,这套 API 就可以看作 Web 应用框架的服务器端组件。

注意,要构建整个只有服务器端渲染的应用还是可以的(有时也需要如此),这种情况下,Express 就是完整的 Web 应用框架。

关于 Express 的特性,除了官方描述中明确提到的,我还想再加两点。

高性能

随着 Express 成为 Node.js 开发的首选 Web 框架,它也吸引了很多大公司的注意,这些大公司运行着高性能、高流量的网站。这给 Express 团队带来了压力,促使他们特别关注性能。现在 Express 可以为高流量网站提供一流的性能。

中立

JavaScript 生态系统的一个特征就是它的规模和多样性。尽管 Express 常常位于 Node.js Web 开发的中心,还是有数百个来自社区的包可以用到 Express 应用里。

Express 团队认识到了这个生态系统的多样性,于是提供了一个极其灵活的中间件体系,使你可以很容易地选择组件来创建自己的应用。你可以看到,在 Express 本身的开发过程中,它摒弃了内建组件的做法,选择了可配置中间件的机制。

前面提到 Express 是 Web 应用框架的“服务器端部分”,因此或许我们应该谈谈服务器端应用和客户端应用之间的关系。

服务器端应用和客户端应用

服务器端应用就是应用中的页面都在服务器上生成(如 HTML、CSS、图片和其他多媒体资源,以及 JavaScript),然后发送给客户端。而客户端应用的大部分用户界面是从一个资源包渲染的,这个资源包只需要在最开始的时候接收一次。

也就是说,一旦浏览器接收到初始的 HTML(通常也非常小),它就使用 JavaScript 来动态地修改 DOM,不需要依靠服务器来显示新页面(虽然原始数据通常还是来自服务器)。

在 1999 年之前,服务器端应用是标准。事实上,Web 应用这个术语是在 1999 年正式引入的。我认为大约 1999 年到 2012 年这段时间是 Web 2.0 时代,在这个时期,最终成为客户端应用的技术正在开发之中。到 2012 年,随着智能手机的稳固发展,通过网络发送尽可能少的信息成为一种普遍做法,这种做法更青睐客户端应用。

服务器端应用常常被称为服务器端渲染(SSR)应用,客户端应用常常被称为单页应用(SPA)。客户端应用完全在诸如 React、Angular 和 Vue 等框架中实现。我总是觉得“单页”的叫法有点儿不妥,因为在用户看来显然有很多个页面。唯一的区别是,页面是服务器发过来的还是在客户端动态渲染的。

在实际中,服务器端应用和客户端应用之间有很多模糊的界线。很多客户端应用有 2 个或 3 个 HTML 资源包可以发给客户端(例如公开可见的界面和登录可见的界面,或普通界面和管理界面)。而且,SPA 常常跟 SSR 结合以便提升第一页加载的性能,这样做也有助于搜索引擎优化(SEO)。

一般来说,如果服务器只发送少量的 HTML 文件(一般 1 至 3 个),而此时用户可以获得基于动态 DOM 操作的丰富的多视图体验,我们就认为这是客户端渲染。各个视图的数据(通常是 JSON 格式)和多媒体资源通常还是要从网络传过来。

当然,对 Express 来说,它并不怎么关心你是在开发一个服务器端应用还是客户端应用,两种角色它都能完成得很好。你要提供 1 个 HTML 资源包还是 100 个,对它来说都一样。

目前 SPA 毫无疑问已经成了统治性的 Web 应用架构。尽管如此,本文还是从符合服务器端应用的示例开始。服务器端应用还是有意义的,而且,提供 1 个 HTML 资源包还是很多 HTML 资源包,在概念上的差异是很小的。

Express 简史

Express 的创造者 TJ Holowaychuk 把 Express 描述为一个受 Sinatra 启发的 Web 框架。

Sinatra 是一个基于 Ruby 的 Web 框架。Express 借鉴一个用 Ruby 写的框架并不奇怪:在 Web 开发方面,Ruby 孵化出了大量卓越的新思路、新方法,使 Web 开发变得更快捷、更高效和更好维护。

就像深受 Sinatra 启发一样,Express 还跟 Connect 深度交织在一起,这是 Node 应用的一个插件库。Connect 最初使用中间件这个术语来描述各种能在不同程度上处理 Web 请求的可插拔的 Node 模块。2014 年,4.0 版本的 Node 移除了对 Connect 的依赖,不过仍然把其“中间件”概念归功于 Connect。

从版本 2.x 到 3.0,Express 经历了大规模的重写;从 3.x 到 4.0,亦如此。本书关注的是版本 4.0。

Node:另一种Web服务器

某种程度上,Node 跟其他几个流行的 Web 服务器,如微软的 IIS 或 Apache,有很多共同之处。不过,更有意思的是它们究竟有哪些不同。让我们从这里开始讲起。

对于 Web 服务器,Node 的做法也是最小化,这一点跟 Express 一样。不像 IIS 或 Apache 那样,需要花上数年时间去掌握,Node 是很容易搭建和配置的。这并不是说在生产环境中为达到性能最大化去调优 Node 服务器是一件很简单的事情,而是说比起那些服务器,Node 的配置选项要简单多了。

Node 跟传统 Web 服务器的另一个显著差异就是,Node 是单线程的。

乍一看去,这似乎是一步倒退。而事实上,这是明智之举。单线程极大地简化了 Web 应用的编程,而当需要达到多线程应用的性能时,你只需简单地多开几个 Node 实例,就能获得多线程的好处。

敏锐的读者可能会想,这听起来像是糊弄人呢。毕竟,通过服务器并行(跟应用内的并行相对)实现的多线程,只是把复杂性转移了,并没有消除它呀。或许如此,但根据我的经验,这种做法是把复杂性转移到了它本来就该在的地方。况且,随着云计算越来越受欢迎,越来越多的人把服务器资源看作普通商品,这种做法就越发显得合理了。

IIS 和 Apache 的确很强大,它们从设计上就是要“榨干”今天强大硬件的最后“一滴”性能。可这是有代价的,要达到那样的性能,就必须有相当专业的人员来搭建和调优。

从编程来说,Node 应用更像 PHP 或 Ruby 应用,而不是 .NET 或 Java 应用。虽然 Node 所使用的 JavaScript 引擎(谷歌的 V8)是把 JavaScript 编译成了原生机器代码(像 C 或 C++ 那样),但这是透明地完成的,所以从用户的角度来看,它就像纯粹解释型语言一样。由于没有单独的编译步骤,维护和部署更省事了:你只需更新 JavaScript 文件,所做的变更会自动生效。

JavaScript 编译成原生机器码的过程,常被称作即时(JIT)编译。

Node 应用还有一个很吸引人的好处:Node 是平台独立的。

Node 不是第一项或唯一一项平台独立的服务器技术,然而,平台独立远不只是二进制包的问题。

例如,因为有了 Mono,你可以在 Linux 服务器上运行 .NET 应用,但是由于文档的缺失和系统不兼容,这会是一项痛苦的工作。同样,你可以在 Windows 服务器上运行 PHP 应用,但是通常来说搭建它没有在 Linux 服务器上那么容易。而在各大主流操作系统(Windows、macOS 和 Linux)上搭建 Node 都轻而易举,不同操作系统协作起来也很容易。

在各个网站设计小组中,混合使用 PC 和 Mac 是寻常事。某些开发平台,如 .NET,就给前端开发和设计人员带来了不少的挑战(因为他们常常使用 Mac),严重影响了他们的协作和工作效率。只要几分钟(甚至几秒钟)就可以在任何操作系统上运行起来一个能正常工作的服务器,这个美梦已经成真了。

Node生态系统

自然,Node 位于这个技术栈的核心位置。正是 Node,使得 JavaScript 可以运行于服务器之上,脱离浏览器而存在,从而允许使用用 JavaScript 编写的框架(如 Express)。另一个重要的组件就是数据库,除非是最简单的 Web 应用,否则都需要一个数据库。

有几种数据库在 Node 中比在其他语言中还要好用。

所有主要的关系型数据库(MySQL、MariaDB、PostgreSQL、Oracle、SQL Server)都有 Node 的接口,这不奇怪。这些数据库经过多年发展已经非常强大,忽略它们是愚蠢的。

尽管如此,Node 时代的到来,让一种新数据库存储方式重新焕发了活力:所谓的 NoSQL 数据库。

把某个东西定义为不是什么,无助于人们理解,所以我们补充一下,这些 NoSQL 数据库或许叫作“文档数据库”或“键值对数据库”更为合适。对于数据存储,它们提供了一种概念上更简单的方法。这类 NoSQL 数据库有很多,而 MongoDB 是它们的领跑者。

构建一个实用的网站需要依赖多个技术板块,因此对于网站所基于的技术栈,人们想出了各种缩写词来描画它们。例如,Linux 、Apache、MySQL 和 PHP 的组合就被叫作 LAMP 技术栈。

MongoDB 的一位工程师 Valeri Karpov 造了一个缩写词 MEAN:Mongo、Express、Angular 和 Node。它很易记,这是肯定的,但也有局限:对于数据库和应用框架来说,有太多的选择了,“MEAN”并不能体现这个生态的多样性(何况它没有把渲染引擎包含进来,这是我认为很重要的一个组件)。

想出一个具有包容性的缩写词是一项有意思的练习。当然,其中不可或缺的一个组件就是 Node。尽管还有其他的服务器端 JavaScript 容器,但 Node 自问世以来便统治了一切。

Express 同样不是唯一可用的 Web 应用框架,但它的统治力也接近 Node 了。

对 Web 应用开发来说,还有两个常常至关重要的组件,就是数据库服务器和渲染引擎(或者是模板引擎,如 Handlebars;或者是 SPA 框架,如 React)。对于这两者,没有那么多明显的领跑者,我认为进行限制是有害无益的。

把所有这些技术捆绑到一起的,就是 JavaScript。所以为了更具包容性,我会把它叫作 JavaScript 技术栈。就本文来说,它指的是 Node、Express 和 MongoDB。

开源协议

开发 Node 应用的时候,你可能会发现,自己必须要比以前更加留心开源协议(我当然也是)。

Node 生态系统的一个美妙之处就是有大量的包可用。可是,每个包都有自己的授权方式,更麻烦的是,每个包都可能依赖一些其他的包,这意味着要理解应用各个部分的授权方式会十分困难。

好在有一些好消息。MIT 协议是 Node 包用得最多的协议之一。这是一个非常宽松的协议,你几乎可以做任何事,包括把这个包用到闭源软件里。不过你不能设想你用的每个包都采用 MIT 协议。

在 npm 中有好几个包,可以尝试找出你的项目中每个依赖的授权方式。请在 npm 中搜索 nlf 或 license-report。

除了 MIT 这个最常见的协议,你可能还会遇到以下开源协议。

GNU 通用公共许可(GPL)

GPL 是一个很受欢迎的开源协议,为了保持软件的开源,它经过了精心设计。这意味着如果你在项目中使用了 GPL 授权的代码,你的项目必须也要以 GPL 来授权。自然,你的项目就不能是闭源的。

Apache 2.0

像 MIT 一样,这个协议允许你的项目采用一种不同的协议,包括闭源的协议。不过对于那些采用 Apache 2.0 许可的组件,你必须在项目协议中把它们的授权声明都包含进来。

BSD 许可

类似于 Apache 许可,这个协议允许你的项目采用任何你想用的协议,只要你把采用 BSD 许可的组件的授权声明都包含进来。

有时候软件是双重许可的(按两种不同的协议来授权)。这样做的一个常见原因是,让软件既可以用在 GPL 项目中,也可以用在采用更宽松许可的项目中(如果一个组件要用到 GPL 软件中,那么这个组件必须是 GPL 授权的)。

在我自己的项目中,我经常采用这种授权模式:GPL 和 MIT 的双重许可。

最后,如果你在写自己的包,你应该做一个社区的好公民,选择合适的协议,并进行适当的说明。对开发者来说,在使用别人的包的时候,如果需要深挖代码以便确定所采用的协议,甚至找了半天却发现根本没有采用许可协议,就太让人沮丧了。

小结

对于 Express 是什么以及它在更大的 Node 和 JavaScript 生态系统中处于什么位置,希望本文让你有了更深入的理解。同时,希望本文也阐明了服务器端 Web 应用和客户端 Web 应用的关系。

对于 Express 确切来说是什么,如果你仍旧感到迷惑,也不用担心:有时候直接上手使用一个东西更有助于理解它。

推荐阅读