如何使用 Socket.io 通过 NodeJS 和 ReactJS 制作实时 API
我们都热爱设计模式,也都想知道什么时候使用它们才是最佳选择。我将用其中一种模式来实践一个你在工作中可能会遇到的业务案例。我所说的模式是“发布者-订阅者”。
今天,我将制作一个实时 API,每当数据库上发生操作时,它会更新所有连接的客户端,因此使用仪表板的超级管理员用户可以立即知道其他管理员是否已登录或退出,而无需每隔几秒刷新一次页面,其他情况是立即知道您正在处理的平台上收到了订单。
本教程中我将使用:
- NodeJS 与 Express 用于服务器端逻辑
- 使用 ReactJS 构建简单的客户端应用程序
- Socket.io 用于双方实时连接
为了继续,您可以逐步编写代码,因为我将介绍其中的大部分内容,或者您可以克隆这两个存储库:
首先让我们设置我们的服务器,首先初始化文件夹结构
npm init -y
然后我们添加我们使用的包,在本教程中,我将在后端使用 ES6 语法,所以我们需要 babel 来捆绑我们的代码,以及我们稍后将使用的一些其他库。
npm add nodemon dotenv babel-loader
@babel/preset-env @babel/node @babel/core -D
这些是 devDependencies,这就是我们使用 -D 标志的原因,因为我们不需要它们用于开发以外的用途。
1.nodemon 用于热运行
2.dotenv 用于 .env 配置
3.babel 用于打包
现在轮到举重运动员了
npm add express mongoose socket.io
1.express 设置我们的服务器
2.mongoose 连接到我们的 mongodb
3.socket.io 负责实时连接
现在有点无聊了,让我们写一些 Javascript
index.js
import express from 'express'
import dotenv from 'dotenv'
dotenv.config()
const app = express()
app.get('/', (req,res)=>{
res.send('Hello')
})
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server up and running on port ${PORT}`);
})
在运行此代码之前,您必须设置一些配置
.env
PORT=5000
MONGO_DB_URL=mongodb://localhost:27017
MONGO_DB_DBNAME=store
.babelrc
{
"presets": [
"@babel/preset-env"
]
}
package.json
....
"scripts": {
"start": "babel-node index.js",
"dev": "nodemon --exec babel-node index.js"
},
....
现在,当您输入时npm run dev
,您会发现服务器已启动并正在运行,如果您在浏览器中输入,http://localhost:5000
您将获得以下内容:
现在让我们创建三个文件夹并调整我们的代码如下:
然后为了更好地处理环境变量config/variables.js
import dotenv from 'dotenv'
dotenv.config()
const DB_URL = `${process.env.MONGO_DB_URL}/${process.env.MONGO_DB_DBNAME}`;
const PORT = process.env.PORT;
export {
DB_URL,
PORT
}
初始化并连接数据库config/db.js
import {DB_URL} from '../config/variables'
mongoose.connect(DB_URL, {
useNewUrlParser:true,
useUnifiedTopology:true
}, () => {
console.log(DB_URL);
console.log(`DB up and running`);
})
订单模型models/order.js
import mongoose, {Schema} from 'mongoose'
const schema = new Schema({
customer:{
type:String,
required:true
},
price:{
type:Number,
required:true
},
address:{
type:String,
required:true
}
}, {
timestamps:true
})
const Order = mongoose.model('order', schema)
export default Order;
订单控制器controllers/order.js
import express from 'express'
import Order from '../models/order'
import {io} from '../index'
const router = express.Router()
router.get('/', async (req, res) => {
try {
const orders = await Order.find()
res.send(orders)
} catch (error) {
res.send(error)
}
})
router.post('/', async (req, res) => {
try {
const order = new Order(req.body)
await order.save()
res.status(201).send(order)
} catch (error) {
res.send(error)
}
})
export default router
现在最重要的部分index.js
import express from 'express'
import {PORT} from './config/variables'
import cors from 'cors'
import http from 'http'
// import { Server } from 'socket.io';
import socketIO from 'socket.io';
// import './config/sockets'
import './config/db'
import orderRouter from './controllers/order'
const app = express()
const server = http.createServer(app)
const io = socketIO(server, {
transports:['polling'],
cors:{
cors: {
origin: "http://localhost:3000"
}
}
})
io.on('connection', (socket) => {
console.log('A user is connected');
socket.on('message', (message) => {
console.log(`message from ${socket.id} : ${message}`);
})
socket.on('disconnect', () => {
console.log(`socket ${socket.id} disconnected`);
})
})
export {io};
app.use(express.json())
app.use(cors())
app.use('/orders', orderRouter)
app.get('/', (req,res) => {
res.send('Hello')
})
server.listen(PORT, () => {
console.log(`Server up and running on port ${PORT}`);
})
让我解释一下这里发生了什么
使用 socket.io 时,我们配置服务器的方式会有所不同,因为它处理服务器实例本身,因此
const server = http.createServer(app)
然后我们用 io 包装它,允许一些 cors,它们将在端口 3000 上短暂地成为客户端
const io = socketIO(server, {
transports:['polling'],
cors:{
cors: {
origin: "http://localhost:3000"
}
}
})
配置 io 并导出以在订单控制器中使用
io.on('connection', (socket) => {
console.log('A user is connected');
socket.on('message', (message) => {
console.log(`message from ${socket.id} : ${message}`);
})
socket.on('disconnect', () => {
console.log(`socket ${socket.id} disconnected`);
})
})
export {io};
然后我们进入订单控制器并将代码更改为controllers/order.js
router.post('/', async (req, res) => {
try {
const order = new Order(req.body)
await order.save()
const orders = await Order.find()
io.emit('order-added', orders)
res.status(201).send(order)
} catch (error) {
res.send(error)
}
})
这意味着,每当有人添加订单时,它将被发布到连接套接字的所有客户端,因此将立即使用数据库中的订单数组进行更新
现在我们可以转到客户端并使用这个 API,create-react-app
因为我们不需要复杂的应用程序,我们只需要演示行为
在这里,我制作了一个简单的 UI 组件,称为 Orders,您可以在 repo 中轻松找到代码,但我对这一部分感兴趣
const [orders, setOrders] = useState([])
useEffect(() => {
const getOrders = async () => {
const response = await axios.get('http://localhost:5000/orders')
const ordersData = response.data;
setOrders(ordersData)
}
getOrders()
}, [])
useEffect(() => {
const socket = io('ws://localhost:5000')
socket.on('connnection', () => {
console.log('connected to server');
})
socket.on('order-added', (newOrders) => {
setOrders(newOrders)
})
socket.on('message', (message) => {
console.log(message);
})
socket.on('disconnect', () => {
console.log('Socket disconnecting');
})
}, [])
首先,我们的状态最初是一个空数组
第一个 useEffect 调用是调用我们刚刚创建的获取订单端点来获取所有订单,然后我们用它填充视图
第二次 useEffect 调用,我们使用socket.io-client
将在客户端安装的连接npm i socket.io-client
,然后我们指定在来自套接字的订单添加事件上,我们将通过事件发送订单并将其设置为新数组,因此每当添加新订单时,我们都会收到数据库中新订单数组的通知。
为了测试它,我在端口 3000 上打开浏览器来打开我的 React 应用程序,然后使用 Postman 向端口 5000 上的服务器发送一条消息来添加订单,然后我的 React 应用程序就立即更新了

这是我的第一篇帖子,希望你喜欢它。
文章来源:https://dev.to/omardiaa48/how-to-make-realtime-apis-with-nodejs-and-reactjs-using-socket-io-6ja