🔽 前言
经过前面的学习,我们已经对NodeJS 的基础内容有了一定的了解,本篇文章将从0开始带大家去创建一个简易的NodeJS路由应用,这个应用中我们会使用/收获到以下知识:
- 内置模块
http 、fs 、path 的使用 NodeJS 开发GET 、POST 请求NodeJS 获取请求参数NodeJS 静态资源处理NodeJS 模块化开发、业务分层- 初步探索
express 框架的开发思想
相信通过本篇的综合训练,你一定能收获很多,让我们开始吧!
如果你对NodeJS 之前的基础内容不是很熟悉,可移步我的【Node.js从入门到精通】专栏进行学习
1?? 案例最终效果演示
GIF动画演示:
这个简易的路由应用只有三个页面:
- 登录页:匹配
/login - 首页:匹配
/ 和/home - 404页面:匹配未定义的路由
其中登录页中引入的CSS 文件和JS 文件我们需要当成NodeJS 的静态资源进行处理,应用的favicon.ico 图标我这里引用的是B站 的图标。在登录页中能够调用NodeJS 的GET 和POST 请求。
应用最终的目录结构如下:
这个路由应用虽然简单,但是它依旧能让我们收获许多,特别是对基础知识的应用和一些模块化开发、业务分层的思想等。
2?? 搭建基础服务器
先在项目根目录创建一个static 文件夹,用来存放我们前端的静态文件,static 文件夹下创建好以下文件:
favicon.ico 是网页的图标文件,随便找一个就行,我这里是用的B站的ico图标
在login.html 中引入外部css 和js 并添加一个基础表单:
<link rel="stylesheet" href="./css/login.css">
<body>
<div class="main">
<div>
用户名:<input type="text" id="username">
</div>
<div>
密码:<input type="password" id="password">
</div>
<button id="getApi">Get请求</button>
<button id="postApi">Post请求</button>
</div>
<script src="./js/login.js"></script>
</body>
其它的html 页面随便写点东西就行
login.js 中先不添加内容,login.css 中随便加点样式:
.main {
background-color: pink;
}
之后在根目录下创建服务器文件server.js :
const http = require("http");
const fs = require("fs");
http.createServer((req, res) => {
const url = new URL(req.url, "http://127.0.0.1:3000");
switch (url.pathname) {
case "/favicon.ico":
res.writeHead(200, {
"Content-Type": "image/x-icon;charset=utf-8",
});
res.end(fs.readFileSync("./static/favicon.ico"));
break;
case "/":
res.writeHead(200, {
"Content-Type": "text/html;charset=utf-8",
});
res.end(fs.readFileSync("./static/home.html"));
break;
case "/home":
res.writeHead(200, {
"Content-Type": "text/html;charset=utf-8",
});
res.end(fs.readFileSync("./static/home.html"));
break;
case "/login":
res.writeHead(200, {
"Content-Type": "text/html;charset=utf-8",
});
res.end(fs.readFileSync("./static/login.html"));
break;
default:
res.writeHead(404, {
"Content-Type": "text/html;charset=utf-8",
});
res.end(fs.readFileSync("./static/404.html"));
break;
}
}).listen(3000, () => {
console.log("服务器启动成功!");
});
上面我们通过http 内置模块搭建了一个NodeJS 服务器,之后在服务器中通过req 来获取路由路径并在switch 中进行各个路径的匹配。
通过node 指令运行server.js 可以看到我们应用的基础骨架就搭建好啦:
3?? 抽离页面路由
上面我们是直接在http 服务器中通过switch 分支来匹配路由的,这显然不够优雅,所以我们将页面路由以对象的形式抽离出去,创建route.js :
const fs = require("fs");
function render(res, path, state = 200, type = "text/html") {
res.writeHead(state, {
"Content-Type": `${type};charset=utf-8`,
});
res.end(fs.readFileSync(path, "utf-8"));
}
const route = {
"/favicon.ico": (req, res) => {
render(res, "./static/favicon.ico", 200, "image/x-icon");
},
"/": (req, res) => {
render(res, "./static/home.html");
},
"/home": (req, res) => {
render(res, "./static/home.html");
},
"/login": (req, res) => {
render(res, "./static/login.html");
},
"/404": (req, res) => {
render(res, "./static/404.html", 404);
},
};
module.exports = route;
这样抽离后代码就比较赏心悦目了,之后修改一下server.js :
const http = require("http");
const route = require("./route");
http.createServer((req, res) => {
const url = new URL(req.url, "http://127.0.0.1:3000");
try {
route[url.pathname](req, res);
} catch (error) {
route["/404"](req, res);
}
}).listen(3000, () => {
console.log("服务器启动成功!");
});
在server.js 中我们巧妙的使用try catch 来捕获路由,若用户请求的路由路径不在我们定义的route 中,则一律执行/404 的处理函数。
4?? 静态资源处理
经过上面的操作后,我们访问/login ,会发现login.html 中的login.css 与login.js 未被正常引用:
我们要怎么去处理这些静态资源呢? 别急,让我们慢慢来分析:
- 访问
/login 时因为login.html 文件引用了login.css 与login.js ,所以会向服务器发起/css/login.css 和/js/login.js 的请求 - 因为我们并没有手动去定义
/css/login.css 和/js/login.js 这两个路由,所以在http 服务器中try catch 的catch 分支会捕获这两个路径,最终会进入到/404 路由的处理函数中
为了印证我们的分析,我们在/404 的处理函数中打印一下:
"/404": (req, res) => {
console.log(req.url);
render(res, "./static/404.html", 404);
},
浏览器重新访问一下/login :
可以看到这两个请求确实是进入到了 /404 路由的处理函 数中,所以我们在这里进行这些静态资源路径的处理就好:
"/404": (req, res) => {
const url = path.join(__dirname, "static", req.url);
if (fs.existsSync(url)) {
render(res, url, 200, mime.getType(url));
return;
}
render(res, "./static/404.html", 404);
},
这里用到了mime这个小模块,它的作用就是根据文件扩展名来生成对应请求的Content-Type ,只有这样我们才能同时方便的处理不同文件格式的静态资源,免去了一一判断
安装mime :
npm i mime
在route.js 中引入:
const mime = require("mime");
const path = require("path");
之后重新访问/login 可以看到静态资源css 和js 被成功引入了(不报错且样式生效了就是引入成功了):
到这里之后,我们就不需要在页面路由对象中对/favicon.ico 进行单独处理了,因为/favicon.ico 也可以被当作是静态资源在 /404 的处理函数中被处理掉:
const route = {
}
5?? API接口开发
遵循模块化开发的理念,我们的Api 接口肯定是需要在一个独立的文件内的,创建api.js :
function renderApi(res, data, state = 200, type = "application/json") {
res.writeHead(state, {
"Content-Type": `${type};charset=utf-8`,
});
res.end(JSON.stringify(data));
}
const apiRoute = {
"/api/getlogin": (req, res) => {
},
"/api/postlogin": (req, res) => {
},
};
module.exports = apiRoute;
这里定义了两个请求,一个代表get请求 ,一个代表post请求 ,现在我们去实现这两个请求并获取这两种请求的参数。
🔹 获取请求参数
获取get 请求的参数很简单,只需要通过new URL 返回实例的searchParams 属性即可获取:
全局的URL 构造函数具体可见:Node.js | 搭建后端服务器(含内置模块 http | url | querystring 的使用) 中 URL格式转换
"/api/getlogin": (req, res) => {
const apiUrlParams = new URL(req.url, "http://127.0.0.1:3000");
const username = apiUrlParams.searchParams.get("username");
const password = apiUrlParams.searchParams.get("password");
if (username === "ailjx" && password === "123456") {
renderApi(res, {
ok: 1,
});
} else {
renderApi(res,{
ok: 0,
});
}
},
获取POST 请求的参数,需要我们在req 参数上监听data 事件和end 事件,因为POST 请求的参数数据并不是一次性全部返回的,而是 “一点一点” 的返回,所以需要我们进行持续接收:
"/api/postlogin": (req, res) => {
let data = "";
req.on("data", function (chunk) {
data += chunk;
});
req.on("end", function () {
data = JSON.parse(data);
if (data["username"] === "ailjx" && data["password"] === "123456") {
renderApi(res, {
ok: 1,
});
} else {
renderApi(res,{
ok: 0,
});
}
});
},
🔹 API路由与页面路由合并
现在我们的api.js 就算是写好了,但现在面临一个问题:我们怎样引用api.js呢?
在server.js 中我们可以使用Object.assign 合并对象的方法来将api.js 导出的对象合并到route 中:
const route = require("./route");
const apiRoute = require("./api");
Object.assign(route, apiRoute);
Object.assign(route, apiRoute) 相当于是将apiRoute 中的属性添加到了route 中,会改变route ,但不会改变apiRoute
🔹 接口调用
之后我们在login.js 中调用接口:
getApi.onclick = () => {
fetch(`/api/getlogin?username=${username.value}&password=${password.value}`)
.then((res) => res.json())
.then((res) => {
console.log(res);
if (res.ok) {
location.href = "/home";
} else {
alert("输入错误!");
}
});
};
postApi.onclick = () => {
fetch("/api/postlogin", {
method: "POST",
body: JSON.stringify({
username: username.value,
password: password.value,
}),
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json())
.then((res) => {
console.log(res);
if (res.ok) {
location.href = "/home";
} else {
alert("输入错误!");
}
});
};
这样后接口功能就完美实现啦,效果演示:
6?? 业务分层
从前面一路走到这里,我们这个路由应用其实已经算是完成了,但上面的代码耦合度有点高,业务分工不够明确。
比如server.js 文件本应该只是存放服务器搭建相关逻辑代码的,但它也存放了路由处理(路由合并)的逻辑代码,这显然不太优雅,那我们就尝试按照业务分层的思想去拆分server.js ,将各个不同的业务逻辑抽离到一个单独的文件中。
我们先修改一下server.js :
const http = require("http");
const Route = {};
function use(route) {
Object.assign(Route, route);
}
function start() {
http.createServer((req, res) => {
const url = new URL(req.url, "http://127.0.0.1:3000");
try {
Route[url.pathname](req, res);
} catch (error) {
Route["/404"](req, res);
}
}).listen(3000, () => {
console.log("服务器启动成功!");
});
}
exports.start = start;
exports.use = use;
上面我们导出了两个函数,一个use 用来注册路由,一个start 用来启动服务器。
然后我们在项目根目录新建一个index.js 代替server.js 成为整个项目的入口文件:
const server = require("./server");
const route = require("./route");
const apiRoute = require("./api");
server.use(route);
server.use(apiRoute);
server.start();
之后我们使用node 指令运行该index.js 启动项目即可,到此整个路由应用实战的教程就全部结束啦!
🔼 结语
从开头看到这里,你应该就能明白业务分层的意义和优势所在!
我们最后实现的这个index.js 中对use 函数的调用像极了express 框架的中间件应用,这为我们之后学习NodeJS 的express 框架奠定了基础,这也就是我说学习这个综合案例能够初步探索到express 框架开发思想的原因。
至此NodeJS 的基础内容就更新完毕啦!后续我们就将步入NodeJS express 框架和mongodb 以及koa 的学习,关注博主,订阅专栏,学习Node不迷路!
如果本篇文章对你有所帮助,还请客官一件四连!??
|