其实从我自学转前端之后,大概2019年,就有听说 Node.js 了。期间,也尝试过跟着网上的教程,写个 web 服务器之类,写完没多久又忘了~
后来一直在一家公司待着,写业务、写业务、写业务,就逐渐麻痹了自己。
业务是做得还不错了,但是知识面真的太窄了~
都 2022 年了,我居然还不会 Node。深刻反省,决定好好学习实践一下 Node.js 了~
只要开始了,就不算晚吧?!(安慰自己)
一、起个 Vite + Vue3 项目
参考 Vite 官网:vitejs.cn/guide/#scaf…
二、安装依赖
(一)安装 koa
基于 Node.js 的 web 开发框架,支持 async 语法。
文档: wohugb.gitbooks.io/koajs/conte… koa.bootcss.com/#applicatio…
安装:
npm i koa --save
(二)安装koa-router
用来处理 http 请求的 url。
文档: wohugb.gitbooks.io/koajs/conte…
安装:
npm i koa-router --save
(三)安装 koa-body-parser
用来将 post 请求的请求体解析为 json 格式。
文档: www.npmjs.com/package/koa…
安装:
npm install koa-bodyparser --save
三、搭 koa 服务器
先在项目的 src 目录下新建一个文件夹 server,用来放我们的 web 服务器相关的代码。
// src/server/index.js
const Koa = require('koa'); // 引入 koa
const router = require('koa-router')(); // 引入 koa-router
const bodyParser = require('koa-body-parser'); // 引入 koa-body-parser
const app = new Koa(); // 起一个 koa 服务器
app.use(bodyParser()); // 还没派上用场,先放这儿
// 这两个请求处理,是用来测试的
router.get('/', async (ctx, next) => {
// ctx 是对请求的 request 和 response 对象的封装
ctx.response.body = '<h1>Home</h1>';
})
router.get('/api/login', async (ctx, next) => {
ctx.response.body = '<h1>Login</h1>';
})
app.use(router.routes());
app.listen(1129); // 设置服务器的端口
console.log('App started at port 1129...');
接下来,切换(不切换也行)到 server 目录下,运行服务器~
cd src/server
node index.js
可以在终端看到服务器运行了
接下来,测试一下,我们上面写的两个 get 请求有没有成功。
在浏览器打开 http://localhost:1129 和 http://localhost:1129/api/login
很好,没问题~
四、控制器及接口处理函数封装
一般,项目中,接口可以分为好多不同的模块的,比如:处理用户登录/信息、订单、商品、购物车等等。如果把所有的接口请求处理函数,都放在上面的 index.js,文件就会显得非常庞大且杂乱。所以:
-
把每个模块单独拎出来
例如,我的 demo 项目中,在 server/api 目录下,新建了 user.js 和 invoice.js
-
封装一个控制器(controller),将所有的模块里的接口处理函数统一添加到 web 服务器中
// src/server/spi/user.js 处理用户相关请求
// 用户列表(因为没有连数据库,先用假数据)
let userList = [
{ username: 'Aubrey', password: '123456' },
{ username: 'Gabriel', password: '654321' },
];
const login = async (ctx, next) => {
let { username, password } = ctx.request.body;
let user = userList.find(item => item.username === username && item.password === password )
if (user) {
ctx.response.status = 200
ctx.cookies.set(SESSION_ID, cardId)
ctx.response.body = { code: 0, message: '登录成功' }
} else {
ctx.response.body = { code: -1, error: `用户名 ${username} 不存在,或者密码错误` }
}
}
/**
* 处理用户登录成功/失败
*/
const loginSuccess = async (ctx, next) => {
// ctx.body === ctx.response.body
ctx.body = `类型:${ctx.query.type)}`
}
const loginFail = async (ctx, next) => {
ctx.body = `类型:${ctx.query.type}`
}
module.exports = {
'POST /api/login': login,
'GET /api/login/success': loginSuccess,
'GET /api/login/fail': loginFail
}
// src/server/api/invoice.js 处理用户发票信息模块
// 发票列表
const getInvoiceList = [
{ id: '2022031801', name: '淘宝购物', amount: '172.50', time: '2022-03-18 13:53' },
{ id: '2022031802', name: '美团买菜', amount: '112.25', time: '2022-03-17 13:53' },
{ id: '2022031803', name: '考拉海购', amount: '612.67', time: '2022-03-16 13:53' },
{ id: '2022031804', name: '京东吉他', amount: '812.19', time: '2022-03-15 13:53' },
]
const invoiceList = async (ctx, next) => {
ctx.response.status = 200
ctx.response.body = { code: 0, invoiceList }
}
module.exports = {
'POST /invoice/list': getInvoiceList
}
// src/server/controller.js 控制器
const fs = require('fs'); // 引入 node 的 文件操作模块 fs
const router = require('koa-router')(); // 引入 koa-router
/**
* 添加 GET/POST 请求
* @param urlMapping 请求
*/
function handleUrl(urlMapping) {
Object.entries(urlMapping).forEach(([url, handler]) => {
if (url.startsWith('GET')) {
let path = url.substring(4);
router.get(path, handler);
return;
}
if (url.startsWith('POST')) {
let path = url.substring(5);
router.post(path, handler);
return;
}
console.log(`invalid URL: ${url}`);
})
}
/**
* 读取所有 API 处理方法
*/
function addController (dir) {
const files = fs.readdirSync(__dirname + dir);
const jsFiles = files.filter(fileName => {
return fileName.endsWith('.js');
})
for (let file of jsFiles) {
console.log(`Process controller: ${file}...`);
let mapping = require(__dirname + dir + file);
handleUrl(mapping);
}
}
module.exports = function (dir) {
const controllerDir = dir || '/api/'; // 这里可以配置默认文件夹
addController(controllerDir);
return router.routes();
}
同时,需要把 index.js 代码简化了
// src/server/index.js
const Koa = require('koa');
const bodyParser = require('koa-body-parser');
const app = new Koa();
const controller = require('../server/controller');
app.use(bodyParser());
app.use(controller());
app.listen(1129);
console.log('App started at port 1129...')
然后,重启服务器
node index.js
可以看到终端的输出如下:
五、前端发送请求
上面,我们虽然把服务器代码写完了,但是那几个 POST 请求,还没验证过。
接下来,我们就在项目里的 login.vue 文件里发送请求到服务器测试啦~
注意:先 npm i axios --save 安装一下 axios
- 首先,随便创建个 login.vue 页面
<template>
<div class="login">
<h1>Login</h1>
<div class="form">
<label for="username">Username</label>
<input v-model="formData.username" type="text" name="username" class="input" id="username" />
<label for="password">Password</label>
<input v-model="formData.password" type="password" class="input" id="password" />
<button class="btn" @click="login">Login</button>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "Login",
data () {
return {
formData: {
username: null,
password: null,
}
}
},
methods: {
login () {
axios.post('/api/login', this.formData).then(res => {
// 省略校验用户名、密码等代码
if (res.data.code === 0) {
alert(res.data.message);
} else {
alert(res.data.message);
}
}).catch(error => {
console.error(error);
})
}
}
}
</script>
<style scoped>
.form {
display: flex;
flex-flow: column;
max-width: 400px;
margin: 0 auto;
}
</style>
- 配置代理 因为浏览器的同源策略,我们在 localhost:3001 下 请求 localhost:1129,是会被拦截的。 所以我们需要在 vite.config.js 里配置下代理。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const path = require('path')
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
return {
plugins: [vue()],
resolve: {
// 别名
alias: {
'@': path.resolve(__dirname, '/src'),
},
// 导入时想要省略的扩展名列表。注意,不 建议忽略自定义导入类型的扩展名(例如:.vue),因为它会影响 IDE 和类型支持。
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
server: {
port: '3001', // 默认端口是 3000,这里改成了 3001
// 配置代理
proxy: {
'/api': {
target: 'http://localhost:1129',
}
},
}
}
})
-
用正确的用户名、密码发送请求 可以看到,请求成功了,也返回了我们在 user.js 里设置的数据
-
用错误的用户名、密码发送请求
- 测试一下我们写的 GET 请求 将上面 login.vue 中 login 方法 改为如下:
login () {
axios.post('/api/login', this.formData).then(res => {
// 省略校验用户名、密码等代码
if (res.data.code === 0) {
// 一般,这里会用 vue-router 来控制页面的跳转行为
// 但是这里为了触发 web 服务器的 GET 请求做演示
// 因为我们没有登录成功页面,所以会跳转至首页
location.href = `/api/login/success?type=登录成功`;
} else {
// 登录失败也跳转至首页
location.href = `/api/login/fail?type=登录失败`;
}
}).catch(error => {
console.error(error);
})
}
}
我们再次分别用正确/错误的用户名、密码去登录,会分别跳转至下面两个页面。
说明,成功了~
六、总结
学到这里,以后实际开发中,后端接口没通前,我们都可以自己起个服务器,然后 mock 一些数据,实现各种请求,尤其是分页加载数据功能~