likes
comments
collection
share

写一个服务,从NodeJs到Express到Koa

作者站长头像
站长
· 阅读数 8

写一个NodeJs服务

创建node-service文件夹,初始化项目

mkdir node-service
cd node-service
npm init -y

创建一个简单的HTTP服务器。以下是一个使用Node.js的核心模块http来创建最基础的Node服务的示例:

const http = require('http');

// 创建服务器
const server = http.createServer((req, res) => {
  // 设置响应头
  res.writeHead(200, {'Content-Type': 'text/plain'});

  // 发送响应内容
  res.end('Hello, World!');
});

// 监听端口
const port = 3000;
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

写一个Express服务

创建express-service文件夹,初始化项目

mkdir express-service
cd express-service
npm init -y
npm install express

创建一个简单的HTTP服务器。以下是一个使用Express来创建最基础的Node服务的示例:

const express = require('express');

// 创建Express应用程序
const app = express();

// 定义路由
app.get('/', (req, res) => {
  res.send('Hello, World!');
});

// 启动服务器
const port = 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

相比于原生NodeJs,express增强了什么?

Express是一个基于Node.js的Web应用程序框架,它在原生的Node.js基础上提供了许多增强功能,使得构建Web应用更加简单和高效。下面是Express相对于原生Node.js的一些主要增强点:

1.路由处理:Express提供了简洁而强大的路由处理机制,可以轻松定义和管理各种路由。通过使用Express的路由功能,你可以根据不同的URL路径和HTTP方法来处理请求,使得代码结构更加清晰和可维护。

2.中间件支持:Express引入了中间件的概念,允许你在请求和响应之间插入功能模块。这使得处理请求的过程更加灵活,你可以通过添加中间件来处理身份验证、日志记录、错误处理等功能,而无需在每个路由处理程序中重复代码。

3.模板引擎:Express支持多种模板引擎,如EJS、Handlebars和Pug(以前称为Jade)。这使得在服务器端生成动态HTML页面变得更加方便,你可以使用模板引擎来组织和渲染页面,将数据动态地注入到模板中。

4.错误处理:Express提供了内置的错误处理机制,使得处理和捕获错误变得更加容易。你可以定义错误处理中间件来集中处理应用程序中的错误,从而提高代码的可读性和可维护性。

5.静态文件服务:Express可以轻松地为静态文件(如CSS、JavaScript和图像文件)提供服务。你只需指定静态文件所在的目录,Express将自动处理静态文件的请求,无需手动编写路由。

6.插件生态系统:Express拥有庞大的插件生态系统,你可以使用各种第三方插件来扩展Express的功能。这些插件提供了各种功能,如会话管理、身份验证、数据库集成等,帮助你更快地构建复杂的Web应用。

总的来说,Express提供了一种更加简洁、灵活和高效的方式来构建Web应用。它简化了开发过程,提供了许多有用的功能和工具,使得构建和维护Web应用变得更加容易。

路由处理方面,Express做了什么?

查看Express官方文档关于路由的指南,Express提供了get,post,all方法,匹配路由路径,route方法和router对象,方便我们编写路由相关的代码。我们看看原生Nodejs怎么处理路由:

const server = http.createServer((req, res) => {
  // 获取请求的URL和方法
  const url = req.url;
  const method = req.method;

  // 处理不同的路由和请求方法
  if (method === 'GET' && url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello, World!');
  } else if (method === 'GET' && url === '/about') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('About Page');
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

可以看出想实现跟Express一样的路由功能,使用nodejs会麻烦很多。

额外的,为了更好的处理路由,也就说处理请求-响应。Express增强了res和req对象。Express的req对象具有许多有用的属性和方法,如req.params用于获取路由参数,req.query用于获取查询参数,req.body用于获取请求体等。而Express的res对象也提供了许多常用的方法,如res.send()用于发送响应,res.json()用于发送JSON响应,res.redirect()用于重定向等。我们看看原生Nodejs怎么处理请求参数和给出响应:

// Nodejs获取路由参数、查询参数、请求体要写一下代码, 而express中可以直接从req对象中获取。
const parsedUrl = url.parse(req.url, true);
// routeParams数组包含了所有的路由参数  
const routeParams = parsedUrl.pathname.split('/').slice(1);
// queryParams对象包含了所有的查询参数
const queryParams = parsedUrl.query;
let body = '';
req.on('data', (chunk) => {
    body += chunk;
});
req.on('end', () => {
    const requestBody = JSON.parse(body);
    // requestBody对象包含了请求体参数
    console.log(requestBody);
});

// 原生nodejs怎样实现express的res.json()
res.setHeader('Content-Type', 'application/json');  
const jsonData = { message: 'Hello, world!' };
const jsonStr = JSON.stringify(jsonData);
res.end(jsonStr);

// 原生nodejs怎样实现express的res.redirect()
res.writeHead(302, {
    'Location': 'https://example.com'
});
res.end();

中间件方面,Express做了什么?

我们分析一个中间件body-parser,在不用body-parser的情况下,获取请求体比较麻烦,还要自己解析请求体,但是使用中间件body-parser,再获取请求体就很简单。

// 无body-parser的情况
app.post('/api/user', (req, res) => {
  let requestBody = '';

  req.on('data', chunk => {
    requestBody += chunk;
  });

  req.on('end', () => {
    // requestBody 包含未经解析的请求体数据
    console.log(requestBody);

    // 在这里进行自定义的请求体解析和处理逻辑
    // ...

    res.sendStatus(200);
  });
});

// 使用body-parser的情况
npm install body-parser

const bodyParser = require('body-parser');

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

app.post('/api/user', (req, res) => {
  const user = req.body;
  // 使用解析后的请求体数据进行处理
});

在Express官方文档开发中间件使用中间件中对中间件有详细介绍,想深入了解可以查看。

Express对模板引擎的支持

我们可以通过设置模板引擎和res.render方法完成对模板的支持。

npm install ejs

// 设置模板引擎
app.set('view engine', 'ejs');
// 设置模板文件的存放位置
app.set('views', path.join(__dirname, 'views'));

app.get('/', (req, res) => {
  const title = 'Express with EJS';
  res.render('index', { title });
});

// views/index.ejs模板文件
<!DOCTYPE html>
<html>
  <head>
    <title>Express with EJS</title>
  </head>
  <body>
    <h1>Welcome to <%= title %></h1>
  </body>
</html>

如果想在nodejs中使用模板,代码如下:

const fs = require('fs');
const ejs = require('ejs');

const template = fs.readFileSync('./views/index.ejs', 'utf8');
const renderedHTML = ejs.render(template, { title: 'My Page' });
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write(renderedHTML);
res.end();

Express怎样做错误处理

抛出错误:当您在路由处理程序或其他中间件函数中遇到错误时,可以使用next函数将错误传递给错误处理中间件。

app.get('/', (req, res, next) => {
  // 模拟一个错误
  const err = new Error('Something went wrong');
  next(err);
});

// 创建一个专门用于处理错误的中间件函数,用于处理服务运行过程发生的异常
function errorHandler(err, req, res, next) {
  // 处理错误逻辑
  console.error(err);

  // 设置响应状态码
  res.status(500);
  // 发送错误响应
  res.send('Internal Server Error');
}
app.use(errorHandler);

Express怎样做静态文件服务

// 指定静态文件目录
app.use(express.static('public'));
// 在页面中有静态文件的请求,express就会在public文件夹找匹配的文件进行返回,很方便。
<link rel="stylesheet" href="/css/styles.css">

Express的插件生态系统

插件生态就是利用中间件机制,可以把很多服务功能封装成中间件供开发者使用。我们以后可以做深入的研究。

写一个Koa服务

const Koa = require('koa');
const app = new Koa();

// 定义路由
app.use(async (ctx) => {
  ctx.body = 'Hello, World!';
});

// 启动服务器
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Koa和Express有什么区别?

Koa旨在“修复和替换NodeJs”,而Express则是“增强NodeJs”。

接下来我来解释这句话。

比如请求和响应这两个对象,Express仍然沿用req和res,当开发者想获取请求参数时,Express使用body-parser中间件增强了req对象,使我们可以从req.params req.query req.body获取到相应参数。对于响应方法,一个res.end稍微难用,Express增加了一些res.send res.json方法,方便开发。这就是所谓的增强NodeJs。更近一步,Express支持路由、模板等功能,都是所谓的增强。

对比Koa,处理函数的参数不再是res,req。而是ctx,内部封装了request和response两个对象,request天然就支持参数获取,response.body就设置了响应体内容。同时,可以直接在ctx对象上使用request和response两个对象的方法和属性,这就是所谓的修复和替换NodeJs。更进一步,Koa的核心库并没有路由、模板等功能,这些功能的支持都需要引入如koa-router这种额外的库,也更加复合前端渐进式的思想潮流。

洋葱模型 vs 线性模型

Koa 使用的是洋葱模型(Onion Model)的中间件处理机制。洋葱模型的核心思想是将请求和响应通过中间件层层包裹,形成一个类似洋葱的结构。每个中间件在请求进入时执行一部分逻辑,然后将控制权交给下一个中间件,最后在响应返回时逆序执行剩余的逻辑。

洋葱模型有以下几个好处:

1.简洁明了:洋葱模型将请求和响应的处理逻辑分割成多个中间件,每个中间件只关注自己的逻辑,使得代码结构清晰、易于维护。

2.可复用性:中间件是独立的功能模块,可以在不同的应用程序中复用,提高代码的可复用性和可扩展性。

3.可扩展性:通过添加、删除或修改中间件,我们可以灵活地调整请求和响应的处理流程,实现不同的功能和需求。

4.异步流程控制:洋葱模型中的每个中间件都可以使用async/await或返回 Promise 的方式处理异步操作,方便处理异步任务,例如数据库查询、网络请求等。

5.错误处理:洋葱模型在处理错误时非常方便,每个中间件可以通过try/catch捕获错误并进行相应的处理,或者将错误传递给下一个中间件进行统一的错误处理。

总的来说,洋葱模型使得 Koa 的中间件处理变得灵活、可复用,并且能够更好地控制请求和响应的流程,提高开发效率和代码质量。

Koa怎样处理路由

npm install koa-router

const router = require('@koa/router');

const app = new Koa();
const router = new Router();

// 定义路由
router.get('/', async (ctx) => {
  ctx.body = 'Hello, World!';
});

中间件方面,Koa做了什么?

这里看一个koa-cors插件的使用,跟Express也没有太明显的差异。

const cors = require('@koa/cors');

// 使用 koa-cors 中间件
app.use(cors());

其他方面

Koa在模板引擎、错误处理、静态文件服务方面,跟Express也大同小异,不再赘述。

总结

这篇文章跟大家一起初步认识了一下使用Nodejs写服务及其最流行的两个框架。未来我们还可以针对前端写Node服务进行更多的探索。