Nodejs-Express框架入门到构建简单留言板项目

Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。

文章只是初步学习,务必关注 《Express官网》

Express 框架的核心特性如下:

  • 通过设置中间件来处理HTTP请求。
  • 通过路由来执行不同的HTTP请求操作。
  • 通过模板来渲染HTML页面。

1️⃣ Hello Express

1.1❀ 普通搭建

1.初始化项目

npm init
npm i express --save

2.创建服务器

  • 创建应用实例
  • 设置路由
  • 开启监听
//app.js

//导入express模块
const express = require("express");
//创建应用
const app = express();
//设置路由
app.get("/", (req, res) => {
  //输出响应
  res.json(req.headers);
  console.log("发生请求!");
});
//开启监听
app.listen(8080, () => {
  console.log("listen on 8080");
});

3.运行应用

node app.js

此时,已开启监听本地8080端口

1.2❀ 脚手架搭建

通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。 更具体参数和模板请看:Express 应用程序生成器

npx express-generator --view=pug myapp
cd myapp
npm install
set DEBUG=myapp:*
npm start

通过 Express 应用生成器创建应用只是众多方法中的一种。你可以不使用它,也可以修改它让它符合你的需求。

在这里插入图片描述
在这里插入图片描述

2️⃣ 路由

路由是指应用程序如何根据指定的路由路径和指定的HTTP请求方法(GET和POST等)来处理请求。

在Express 应用中,每个路由可以有一个或多个处理函数,这些函数会在路由匹配时执行。

路由采用以下结构定义:

app.METHOD(PATH, HANDLER)
  • app:应用实例。
  • METHOD:小写get和post等的HTTP请求方法。
  • PATH:路由路径。
  • HANDLER:路由匹配时执行的函数。

2.1❀ 路由方法

Express 支持所有的HTTP请求方法

以下是官网提供的HTTP请求方法,实在太多了,估计用得上的也就几个!

在这里插入图片描述

通过app.all()方法可以只使用一个路由来处理所有指定path的请求方法

app.all('/',(req,res)=>{res.send('拦截了路径为/的所有方法');})

2.2❀ 路由路径

路由路径可以是字符串,字符串模式或正则表达式。

查询字符串(一般称为GET参数):类似?param=xx 不是路由路径的一部分。

(1)字符串

app.get("/about.txt", (req, res) => {});

(2)字符串模式

?表示前面的单元最多出现一次 +表示前面的单元最少出现一次

app.get('/ab?c', (req, res) => {});

(3)正则表达式

只要路径包含user即可

app.get(/user/, (req, res) => {});

完完全全是/admin才可以

app.get(/^\/admin$/, (req, res) => {});

2.3❀ 路由参数

路由参数(:var)用于捕获URL中个位置的值。捕获的值将填充到req.params对象中。

app.get('/users/:userID(\\d+)/:userName', (req, res) => {
  res.send(req.params);
 });

(\\d+)可选项 跟在路由参数后面,限定变量格式
在这里插入图片描述

2.4❀ 路由函数

function(request,response,next)

next是可选的,指的是下一个路由函数。一个路由可以有多个路由函数来处理。执行next()方法才会处理后面一个路由函数。

next()是可以有参数的,参数可以是:字符串、数字、Error对象等等;

假如没有没有下一个路由函数,却调用next()不加参数,将访问不到!如果加了参数,比如next(1),相当于调用send(1)。

app.all(
  "/route",
  (req, resp, next) => {
    console.log(`${req.method}-${next}`);
    next(); //执行下一个路由函数 因为next指向的就是下一个路由函数
  },
  (req, resp) => {
    resp.send("here");
  }
);

在这里插入图片描述

还有一种等价写法:

app.all(path,[func1,func2])//同理func1函数体需要执行next()

2.5❀ 公共路由路径

路由路径相同,请求方法不同。使用app.route()方便模块化管理

app
  .route(/^\/admin$/)
  .get((req, res) => {
    res.send(`${req.method}`);
  })
  .post((req, res) => {
    res.send(`${req.method}`);
  });

在这里插入图片描述
在这里插入图片描述

2.6❀ 模块化路由

使用Express的Router对象可以创建模块化的对象,实现路由和入口JS的解耦。
优点:

  • 统一的路由前缀
  • 便于维护
  • 模块化

user.js 与user相关的路由配置

const express =require('express');
const router=express.Router();

router.post('/login',(req,resp)=>{
    resp.send(req.path)
})

router.post('/register',(req,resp)=>{
    resp.send(req.path)
})

module.exports=router

timeline.js 与timeline相关的路由配置

const express =require('express');
const router=express.Router();

router.get('/list',(req,resp)=>{
    resp.send(req.path)
})

module.exports=router

index.js路由入口,统一管理其他子路由

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

const user=require('./user');
const timeline=require('./timeline');

app.use('/user',user);
app.use('/timeline',timeline);

app.listen(8080,()=>{console.log('listen on 8080');})

最终生成的路由:

  • POST /user/login
  • POST /user/register
  • GET /timeline/list

3️⃣ 请求对象

每个路由函数都有一个request对象,以下是request对象的常用属性名:

属性名说明备注
method请求方法
path请求路径不包含GET参数
url请求URL除域名外的完整URL,包含GET参数
queryGET参数对象
params路由参数对象
headers请求报头
cookie请求cookie需要使用cookie-parser中间件
ip客户端ip
bodypost请求体需要使用body-parser中间件

3.1❀ 获取请求cookie

Express默认不解析请求报头的Cookie。需要借助cookie-parser中间件。

npm install cookie-parser --save

const express = require('express');
const cookieParser = require('cookie-parser');

const app=express();
app.use(cookieParser());// 使用中间件
app.get('/',(req,resp)=>{
resp.json({ cookies:req.cookies });
});

app.listen(8080,()=>{
console.log('listen on 8080');
});

3.2❀ 获取请求体

不能像cookie-parser一样直接调用构造函数来使用,因为在webpack4中bodyParser()构造器已经弃用。

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

const app=express();
app.use(bodyParser.urlencoded({
    extended: true
  }));
  app.use(bodyParser.json());
app.post('/',(req,resp)=>{
resp.json({ body:req.body });
});

app.listen(8080,()=>{
console.log('listen on 8080');
});

在这里插入图片描述

4️⃣ 响应对象

每个路由函数都会接收一个响应对象,通过调用响应对象的方法,可以将响应发送到客户端,并结束请求处理。如果路由函数调用响应对象的任何方法,则客户端请求将被挂起,直到客户端超时。

4.1❀ 响应状态码

resp.status(statusCode)

app.get("/", (req, res) => {
  res.status(200).end();
});

4.2❀ 响应头

resp.set(filed[,value])
resp.set({[field],value})

  res.set('Content-Type','text/plain')
  res.send('hello');

4.3❀ 下载文件

通过Content-Disposition 响应报头提示客户端下载文件。
resp.download(path[, filename][, options][,callback])

  • path:需要提供给客户端下载的服务端文件路径。
  • filename:客户端下载文件时的别名。
  • options:下载选项。
  • callback:回调函数。
app.get("/download", (req, res) => {
  res.download("x.txt", "xxx.txt", (err) => {
    if (err) {
      console.warn("下载失败", err);
    }
  });
});

4.4❀ 结束响应过程

resp.end([chunk][,encoding][,callback])//chunk相应数据

resp.end('hello world')

4.5❀ 重定向

resp.redirect([statusCode,]path)默认状态码为302

app.get("/test", (req, res) => {
 res.redirect(301,'http://www.ivotoo.com')
});

4.6❀ 渲染HTML模板页面

resp.render(view[,locals][,callback])

  • view:视图名称。
  • locals:传递到视图的变量对象,视图可以访问到这些变量并进行渲染。
  • callback:回调函数。如果提供,该方法将返回可能的错误和HTML 字符串,但不是自动发送HTML到客户端

以下是渲染用户主页的示例:

app.get('/user/home',(reg,resp)=>{
  resp.render('user/home',{name:'joe'}  )
});

4.7❀ 设置 Cookie

resp.cookie(name,value[,options])

options :

  • domain:域名。默认为当前域名。
  • expires GMT:到期时间。如果未设置或者设置为0,则浏览器关闭后Cookie失效。
  • httpOnly:将 Cookie标记为只有HTTP服务器能访问(客户端JS无法访问)。
  • maxAge:以毫秒为单位的过期时间。通常比expires选项使用方便。
  • path: Cookie 路径。
  • secure:标记为仅在HTTPS协议下才发送。
  • signed:是否对Cookie签名。
app.get("/cok", (req, res) => {
  res.cookie('name','joe',{
    httpOnly:true,
    maxAge:24*3600*1000
  }).end()
 });

4.8❀ 发送HTTP响应

resp.send([body])
body 响应内容。支持string、Buffer、Object、Array.

  • String: Content-Type将自动设置为 text/html.
  • Buffer: Content-Type将自动设置为 application/octet-stream.
  • Object或Array:Content-Type将自动设置为 application/json.

5️⃣中间件

中间件是一个函数,在应用的请求-响应周期中,能够访问请求对象、响应对象和next函数。

中间件可以执行以下任务:

  • 执行逻辑代码。
  • 更改请求和响应对象。
  • 结束请求-响应周期。
  • 调用下一个中间件。

如果当前的中间件没有调用next(),也没有结束请求-响应周期,则该请求将被挂起。

中间件有路由中间件和全局中间件两种。全局中间件对任何请求都会生效,路由中间件只对特定的路由生效。

5.1❀ 全局中间件

function logger(req,resp,next){
  console.log(`${req.method}`);
  next()
}
app.use(logger)

所有访问都会通过logger函数

5.2❀ 路由中间件

function logger(req,resp,next){
  console.log(`${req.method}`);
  next()
}
app.get("/", logger,(req, res) => {//挂载中间件
...
})

只有访问指定路由才会经过中间件

🌰响应时长中间件

const express = require("express");

const app = express();
function responseTime(req, resp, next) {
  const start = Date.now();
  resp.once("finish", function () {
    console.log("处理时长", Date.now() - start);
  });
  next();
}
app.use(responseTime);
app.get("/abc", (req, res) => {
  setTimeout(() => {
    res.end();
  }, 1000);
});
app.listen(8080, () => {
  console.log("listen on 8080");
});

6️⃣错误处理

6.1❀ 同步错误

app.get('/',(req,resp)=>{
	if(!req.query.name)
		throw new Error('缺少参数name')
	resp.end('ok');
})

在这里插入图片描述
这是Express报错默认模板

6.2❀ 异步错误

一般是指发生在回调函数中的错误,需要通过next(err)才能捕获异步错误

const fs = require("fs");

app.get("/abc", (req, res,next) => {
fs.readFile("G:/nodejs/wordps.txt", "utf-8", function (err, data) {
  if (err) {
    next(err);
    return;
  }
  next(data);
});
})

在这里插入图片描述

6.2❀ 捕捉错误日志并输出到日志文件

错误处理函数

function(err,req,resp,next)

错误处理中间件需要挂载在应用末尾,这样才能保证捕获前面产生的错误

const fs = require("fs");
const express =require('express');

const app=express();

app.get("/abc", (req, res,next) => {
throw new Error('发生错误')
})

app.use((err,req,resp,next)=>{
    fs.appendFile('error.txt',`${req.method} ${req.url} Error: ${err.message}`,'utf-8',(err)=>{
        next(err);
    })
})
app.use((err,req,resp,next)=>{
    resp.json({
        path:req.path,
        message:err.message
    })
})

app.listen(8080,()=>{
    console.log('listen on 8080');
})

7️⃣模板渲染

模板引擎能够在应用中使用静态模板文件,在运行时,可以根据实际变量替换模板中的变量,并将渲染后的HTML 发送给客户端。

Express 可以使用jade、pug、mustache 和ejs 作为模板引擎,默认的是jade。但目前用ejs的比较多。

ejs官网

ejs模板

要渲染模板文件,需要安装对应的模板引擎,还需要更改app设置。

app.set('views', './templates');//设置模板文件目录
app.set ('view enqine', 'ejs');//设置模板引擎

🌰
tenplate/site/index.ejs

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <% if(show) {
        for (let index = 0; index < people.length; index++) {
           %>
    <%=people[index]%>
    <%
        }}
    %>
</body>

</html>

index.js

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

app.set('views','./templates');
app.set('view engine','ejs');

app.get('/',(req,resp)=>{
    resp.render('site/index',{show:true,people:['Joe','Jane']})
})

app.listen(8080,()=>{
    console.log('8080');
})

在这里插入图片描述

8️⃣留言板项目开发

此项目暂未使用数据库,后面的文章我会介绍结合数据库使用。

项目功能:

  • 首页呈现留言列表和发布留言按钮(点击跳转到留言填写界面)
  • 留言填写界面提供信息录入表单和发布按钮(点击发布浏览并跳至首页)
mkdir express-board
cd express-board
npm init -y
npm i express ejs body-parser --save

templates/publish.ejs

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>发布留言</title>
</head>

<body>
    <form action="/publish" method="POST">
        <fieldset>
            <legend>发表留言</legend>
            <div>
                <label>姓名:</label><input type="text" name="username" required />
            </div>
            <div>
                <label>内容:</label><textarea name="content"></textarea>
            </div>
            <div>
                <button type="submit">发表</button>
                <button type="reset">重置</button>
            </div>
        </fieldset>
    </form>
</body>
</html>

templates/index.ejs

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言板</title>
</head>

<body>
    <a href="/publish"><button>发表留言</button></a>
    <% if(message.length==0){ %>
    <p>当前没有留言</p>
    <% }else{ %>
    <table border="1" cellspacing="0" cellpadding="0">
        <thead>
            <tr>
                <th>留言时间</th>
                <th>留言人</th>
                <th>留言内容</th>
            </tr>
        </thead>
        <tbody>
            <% message.forEach(it=>{%>
            <tr>
                <td><%= it.time%></td>
                <td><%= it.username%></td>
                <td><%= it.content%></td>
            </tr>
            <% }) }%>
        </tbody>
    </table>
</body>

</html>

index.js

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

const app=express();

let message=[]

app.set('views','./templates');
app.set('view engine','ejs');

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

app.get('/',(req,resp)=>{
    resp.render('index',{message});
})

app.route('/publish').get((req,resp)=>{
    resp.render('publish');
}).post((req,resp)=>{
    const now =(new Date()).toLocaleString();
    if(!req.body.username||!req.body.content){
        throw new Error('信息缺失');
    }
    message.push({username:req.body.username,content:req.body.content,time:now})
    resp.redirect('/')
})

app.listen(8080,()=>{
    console.log('listen on 8080');
})

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值