Express(一) ——简单入门
背景:参加的青训营项目,使用Express来实现后端,个人被分配到后端去。于是,简单速通了下Express。项目结束,回头写下笔记,沉淀一下。
个人博客:Express(一) ——简单入门
Express是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
开始前可以先安装Postman,很好用的接口测试工具。
1. Hello World
首先,安装express到项目中npm i express
然后,开始代码世界。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
})
app.listen(8080, () => {
console.log('http://localhost:8080/');
})
最后,命令行执行nodemon app.js 或node app.js 。nodemon 支持热更新。
2. 路由
路由是指服务器端应用程序如何响应特定端点的客户端请求。由一个URI(路径标识)和一个特定的HTTP方法(GET、POST等)组成的。
路由的定义结构:
app.METHOD(PATH, HANDLER);
- app:express实例
- METHOD:是一个HTTP请求方法
- PATH:服务端路径
- HANDLER:当路由匹配到时执行的处理函数。参数:
request 和response 对象分别处理请求和响应数据
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
})
app.post('/', (req, res) => {
res.send('post /');
})
app.put('/user', (req, res) => {
res.send('put user');
})
app.delete('/user', (req, res) => {
res.send('delete user');
})
app.listen(8080, () => {
console.log('http://localhost:8080/');
})
2.1 请求对象
req对象代表HTTP请求。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
console.log("请求地址: ", req.url);
console.log("请求方法: ", req.method);
console.log("请求头: ", req.headers);
console.log("请求参数: ", req.query);
res.end();
})
app.listen(8080, () => {
console.log('http://localhost:8080/');
})
postman测试用:http://localhost:8080/?name=clz
2.2 响应对象
res对象表示收到HTTP请求后发送的HTTP响应。
2.2.1 状态码及状态信息
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.statusCode = 404;
res.statusMessage = "test";
res.end();
})
app.listen(8080, () => {
console.log('http://localhost:8080/');
})
2.2.2 发送多段文本
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.write('hello ');
res.write('world');
res.end();
})
app.listen(8080, () => {
console.log('http://localhost:8080/');
})
2.2.3 cookie
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.cookie('name', 'clz');
res.end();
})
app.listen(8080, () => {
console.log('http://localhost:8080/');
})
2.3 路由路径
可以使用正则表达式语法
app.get("/", function(req, res) {
res.send("root");
});
app.get("/abc", function(req, res) {
res.send("abc");
});
app.get("/test.text", function(req, res) {
res.send("test.text");
});
app.get("/ab?cd", function(req, res) {
res.send("ab?cd");
});
app.get("/ab*cd", function(req, res) {
res.send("ab?cd");
});
app.get("/ab(cd)?e", function(req, res) {
res.send("ab?cd");
});
app.get(/a/, function(req, res) {
res.send("/a/");
});
app.get(/.*fly$/, function(req, res) {
res.send("/.*fly$/");
});
2.4 动态路径
app.get("/users/:userId/books/:bookId", function(req, res) {
res.send(req.params);
});
app.get("/:a(\\d+)", function (req, res) {
res.send(req.params);
});
3. 案例
创建一个简单的CRUD接口服务。增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)
- 查询任务列表:
GET /todos - 根据ID查询单个任务:
GET /todos/:id - 添加任务:
POST /todos - 修改任务:
PATCH /todos - 删除任务:
DELETE /todos/:id
3.1 路由设计
const express = require('express');
const app = express();
app.get('/todos', (req, res) => {
res.send('查询任务列表');
})
app.get('/todos/:id', (req, res) => {
res.send(`根据ID查询单个任务, id是${req.params.id}`);
})
app.post('/todos', (req, res) => {
res.send('添加任务');
})
app.patch('/todos/:id', (req, res) => {
res.send(`修改任务, id是${req.params.id}`);
})
app.delete('/todos/:id', (req, res) => {
res.send(`删除任务, id是${req.params.id}`);
})
app.listen(8080, () => {
console.log('http://localhost:8080/');
})
3.2 获取任务列表
数据文件db.json
{
"todos": [
{
"id": 1,
"title": "express"
},
{
"id": 2,
"title": "笔记"
},
{
"id": 3,
"title": "更新博客"
}
]
}
app.get('/todos', (req, res) => {
fs.readFile('./db.json', 'utf8', (err, data) => {
if (err) {
return res.status(500).json({
error: err.message
})
}
const db = JSON.parse(data);
res.status(200).json(db.todos);
})
})
3.3 根据ID查询单个任务
app.get('/todos/:id', (req, res) => {
fs.readFile('./db.json', 'utf8', (err, data) => {
if (err) {
return res.status(500).json({
error: err.message
})
}
const db = JSON.parse(data);
const todo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id));
if (!todo) {
return res.status(404).end();
}
res.status(200).json(todo);
})
})
3.4 封装db模块
从上面的代码中可以发现,读取数据文件部分逻辑一样,即可以封装成单独的模块db.js
db.js
const fs = require('fs');
const { promisify } = require('util');
const path = require('path');
const readFile = promisify(fs.readFile);
const dbPath = path.join(__dirname, './db.json');
exports.getDb = async () => {
const data = await readFile(dbPath, 'utf8');
return JSON.parse(data)
}
封装后的app.js(后面的路由没有变化)
const express = require('express');
const { getDb } = require('./db.js');
const app = express();
app.get('/todos', async (req, res) => {
try {
const db = await getDb();
res.status(200).json(db.todos);
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
app.get('/todos/:id', async (req, res) => {
try {
const db = await getDb();
const todo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id));
if (!todo) {
return res.status(404).end();
}
res.status(200).json(todo);
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
3.5 添加任务
app.post('/todos', (req, res) => {
console.log(req.body);
res.end();
})
然后,会发现很恐怖的事情
那么,这个时候就需要配置表单请求体来解决上述问题
app.use(express.json())
完美!!!(然而,并不是)
换种形式,就要换汤了。因为express.json()只能解析json形式的
app.use(express.urlencoded())
然后,因为需要保存到db.json中,所以也应该在db.js中封装一个saveDb()方法(app.js自然也要引入saveDb,这部分就不行出来了)
db.js
const fs = require('fs');
const { promisify } = require('util');
const path = require('path');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const dbPath = path.join(__dirname, './db.json');
exports.getDb = async () => {
const data = await readFile(dbPath, 'utf8');
return JSON.parse(data)
}
exports.saveDb = async db => {
const data = JSON.stringify(db);
await writeFile(dbPath, data)
}
添加任务的代码部分
app.post('/todos', async (req, res) => {
try {
const todo = req.body;
if (!todo.title) {
return res.status(422).json({
error: 'The field title is required.'
})
}
const db = await getDb();
const lastTodo = db.todos[db.todos.length - 1];
todo.id = lastTodo ? lastTodo.id + 1 : 1;
db.todos.push(todo)
await saveDb(db);
res.status(200).json(todo);
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
小优化:上面可以发现,添加任务后,db.json格式很丑。其实就是把JavaScript 对象转换为 JSON 字符串时的问题,所以只需要在JSON.stringify() 上下点功夫就行。
JSON.stringify()
exports.saveDb = async db => {
const data = JSON.stringify(db, null, ' ');
await writeFile(dbPath, data)
}
眼尖的同学可能发现,添加的任务id和之前的位置不太一样。那么,有点小强迫症的我自然还是要在微操一手。
终于。。。
3.6 修改任务
app.patch('/todos/:id', async (req, res) => {
try {
const todo = req.body;
const db = await getDb();
const ret = db.todos.find(todo => todo.id === Number.parseInt(req.params.id))
if (!ret) {
return res.status(404).end();
}
Object.assign(ret, todo)
await saveDb(db);
res.status(200).json(ret);
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
修改原有属性:
新增属性
3.7 删除任务
app.delete('/todos/:id', async (req, res) => {
try {
const todoId = Number.parseInt(req.params.id);
const db = await getDb();
const index = db.todos.findIndex(todo => todo.id === todoId);
if (index === -1) {
return res.status(404).end();
}
db.todos.splice(index, 1);
await saveDb(db);
res.status(200).end()
} catch (err) {
res.status(500).json({
error: err.message
})
}
})
4. res.end()和res.send()区别
官方说明:
4.1 res.end()
结束响应流程。用于在没有任何数据的情况下快速结束响应。
- 参数可以是buffer对象、字符串
- 只接受服务器响应数据,如果是中文会乱码
4.2 res.send()
发送HTTP响应。
- 参数可以是buffer对象、字符串、对象、数组
- 发送给服务端时,会自动发送更多的响应报文头,包括Content-Type: text/html;charset=utf-8,所以中文不会乱码
res.send()发送对象响应
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send({
name: 'clz'
})
})
app.listen(3000, () => {
console.log('http://localhost:3000/');
})
改为用res.end()发送
res.send()发送中文(使用浏览器查看,postman可能自动设置了响应头)
res.send("测试")
改为res.edn():
学习参考视频:
Node.js 系列教程之 Express
|