Як створити додаток у реальному часі за допомогою Socket.io, React, Node та MongoDB

Ви коли-небудь замислювалися над тим, як будуються програми в режимі реального часу? Ви коли-небудь помічали важливість та випадки використання додатків у реальному часі?

Якщо вас цікавлять вищезазначені запитання і вам потрібна відповідь, то цей допис у блозі для вас.

По-перше, давайте визначимо кілька випадків використання, які потребують додатків у режимі реального часу:

  1. Отримання оновлень місцезнаходження вашої кабіни на карті заявки на бронювання кабіни.
  2. Миттєво отримуйте нові повідомлення у вашому улюбленому додатку для чату.
  3. Інформація про замовлення їжі оновлюється на кухні вашого улюбленого ресторану.

Це все є загальним сценарієм нашого повсякденного життя, коли ми не можемо терпіти затримки оновлення інформації, а отже, потребуємо спілкування в режимі реального часу.

Технології, які можна використовувати для спілкування в режимі реального часу :

  1. Короткий опитування : AJAX, створює інтенсивний трафік.
  2. Тривале опитування : як AJAX, але сервер затримує відповідь, поки не оновиться. Отримавши його, клієнт надсилає ще один запит і потребує додаткового заголовка для переміщення вперед-назад, що спричиняє додаткові накладні витрати.
  3. Веб-розетки : дозволяють відкрити інтерактивний зв’язок між клієнтом та сервером. Можна надіслати запит на сервер і отримувати відповіді, керовані подіями, не опитуючи сервер для відповіді, роблячи веб-сокети найкращим вибором для нашого випадку використання.

Більш детальну інформацію про вищезазначені три технології можна прочитати тут.

Ми навчимося створювати додатки в режимі реального часу, висвітлюючи наступний сценарій.

Уявіть, що ви сидите у своєму улюбленому ресторані і маєте цифрове меню. Ви робите замовлення, і кухня оновлюється щодо вашого замовлення в режимі реального часу. Коли кухня робить замовлення, вони також оновлюють його в режимі реального часу.

Особливості в деталях:

  1. Оформити замовлення : Інтерфейс для вибору кількості та розміщення замовлення для обраного продукту на кухні.
  2. Кухня : Інтерфейс, який можна відкрити на різних кухнях та оновлювати в режимі реального часу кухарів та кухарів щодо загальної кількості створених замовлень та прогнозованої кількості продуктів харчування, надаючи їм гнучкість для їх оновлення. Також є функціонал для завантаження звіту у вигляді аркуша Excel.
  3. Прогнозовані зміни : інтерфейс для оновлення передбачуваної кількості продуктів харчування.

Демонстрацію цього сценарію можна переглянути тут.

Для кращого розуміння відкривайте його одночасно на різних вкладках / пристроях, щоб побачити, як дані змінюються в режимі реального часу.

Вихідний код тут. Не соромтеся робити щось інноваційне / корисне поверх цього.

Тож давайте почнемо.

Стек технологій:

Frontend : React.js, Reactstrap, Socket.io

Бекенд : Node.js (Express), MongoDB, Socket.io

Структура папки:

/* Go to the root directory in the source code and find out the below-mentioned files. This architecture helps in creating a big modular App. */ backend-my-app/ /* Backend code of the app */ server.js /* Socket and backend code resides here*/ build/ /* Optional for deployment of Frontend Build */ package.json /* Backend dependency */ ... public/ src/ /* Frontend Sourcecode */ global/ /* Components getting used everywhere */ header.css header.js main/ Kitchen.js PlaceOrder.js UpdatePredicted.js App.js /* Routing logic and component assembly part */ package.json /* Frontend dependency */ ............

Пояснення вихідного коду:

Фронтальний:

git clone //github.com/honey93/OrderKitchen.git cd OrderKitchen npm install npm start

Пакети, що використовуються:

  1. Reactstrap: прості у використанні компоненти bootstrap4
  2. Socket.io: Socket.io - це бібліотека, яка забезпечує взаємодію між браузером та сервером у режимі реального часу, у двосторонньому режимі та на основі подій.
  3. response-html-table-to-excel: Забезпечує генерацію файлу Excel (.xls) на стороні клієнта з елемента таблиці HTML.
  4. response-router-dom: прив'язки DOM для маршрутизатора з реакцією. Він складається з багатьох важливих компонентів, таких як BrowserRouter, який використовується, коли є сервер для обробки динамічних запитів, Switch, Route тощо.

Компонент програми

Шлях : src / App.js

Цей компонент містить основну логіку маршрутизації Frontend. Цей файл використовується в src / index.js всередині модуля браузера маршрутизатора. Наведений нижче код демонструє один із підходів до збереження модульної програми.

import React, { Component } from "react"; import "./App.css"; import { Header } from "./global/header"; import { Switch, Route } from "react-router-dom"; import PlaceOrder from "./main/PlaceOrder"; import UpdatePredicted from "./main/UpdatePredicted"; import Kitchen from "./main/Kitchen"; /*The  component is the main part of React Router. Anywhere that you want to only render content based on the location’s pathname, you should use a  element. */ /* The Route component expects a path prop, which is a string that describes the pathname that the route matches */ /* The  will iterate over routes and only render the first one that matches the current pathname */ class App extends Component { render() { return ( ); } } export default App;

Компонент заголовка

Шлях : src / global / header.js

Цей компонент буде загальним і використовуватиметься в таких розділах, як Розміщення замовлення, Прогнозовані зміни, Кухня. Цей підхід допомагає уникнути дублювання коду та зберігає модульність програми.

import React, { Component } from "react"; import { NavLink } from "react-router-dom"; import socketIOClient from "socket.io-client"; import "./header.css"; // The Header creates links that can be used to navigate // between routes. var socket; class Header extends Component { /* Creating a Socket client and exporting it at the end to be used across the Place Order, Kitchen, etc components*/ constructor() { super(); this.state = { endpoint: '//localhost:3001/' }; socket = socketIOClient(this.state.endpoint); } render() { return (   
  • Place Order
  • Change Predicted
  • Kitchen
); } } export { Header, socket };

Кухонний компонент

Шлях : src / main / Kitchen.js

Логіка інтерфейсу інтерфейсу кухня та HTML-код знаходиться в цьому компоненті:

import React, { Component } from "react"; import { Button, Table, Container } from "reactstrap"; import { socket } from "../global/header"; import ReactHTMLTableToExcel from "react-html-table-to-excel"; class Kitchen extends Component { constructor() { super(); this.state = { food_data: [] // this is where we are connecting to with sockets, }; } getData = foodItems => { console.log(foodItems); this.setState({ food_data: foodItems }); }; changeData = () => socket.emit("initial_data"); /*As soon as the component gets mounted ie in componentDidMount method, firing the initial_data event to get the data to initialize the Kitchen Dashboard */ /* Adding change_data listener for listening to any changes made by Place Order and Predicted Order components*/ componentDidMount() { var state_current = this; socket.emit("initial_data"); socket.on("get_data", this.getData); socket.on("change_data", this.changeData); } /* Removing the listener before unmounting the component in order to avoid addition of multiple listener at the time revisit*/ componentWillUnmount() { socket.off("get_data"); socket.off("change_data"); } /* When Done gets clicked, this function is called and mark_done event gets emitted which gets listened on the backend explained later on*/ markDone = id => { // console.log(predicted_details); socket.emit("mark_done", id); }; getFoodData() { return this.state.food_data.map(food => { return (  {food.name}  {food.ordQty}  {food.prodQty}  {food.predQty}   this.markDone(food._id)}>Done  ); }); } render() { return (  

Kitchen Area

{this.getFoodData()}
NameQuantityCreated Till NowPredictedStatus
); } } export default Kitchen;

Розмістіть компонент замовлення

Path: src/main/PlaceOrder.js

import React, { Component } from "react"; import { Button, Table, Container } from "reactstrap"; import { socket } from "../global/header"; class PlaceOrder extends Component { constructor() { super(); this.state = { food_data: [] // this is where we are connecting to with sockets, }; } getData = foodItems => { console.log(foodItems); foodItems = foodItems.map(food => { food.order = 0; return food; }); this.setState({ food_data: foodItems }); }; componentDidMount() { socket.emit("initial_data"); var state_current = this; socket.on("get_data", state_current.getData); } componentWillUnmount() { socket.off("get_data", this.getData); } //Function to place the order. sendOrder = id => { var order_details; this.state.food_data.map(food => { if (food._id == id) { order_details = food; } return food; }); console.log(order_details); socket.emit("putOrder", order_details); var new_array = this.state.food_data.map(food => { food.order = 0; return food; }); this.setState({ food_data: new_array }); }; // Changing the quantity in the state which is emitted to the backend at the time of placing the order. changeQuantity = (event, foodid) => { if (parseInt(event.target.value)  { if (food._id == foodid) { food.order = parseInt(event.target.value); } return food; }); this.setState({ food_data: new_array }); }; // To get the initial data getFoodData() { return this.state.food_data.map(food => { return (  {food.name}   this.changeQuantity(e, food._id)} value={food.order} type="number" placeholder="Quantity" />  this.sendOrder(food._id)}>Order  ); }); } render() { return (  

Order Menu

{this.getFoodData()}
ProductQuantityOrder
); } } export default PlaceOrder;

One more section called Update Predicted Path: src/main/UpdatePredicted.js similar to above section is there in the code repository.

Backend

Starting the Backend:

cd backend-my-app npm install node server.js

Packages used:

  1. Monk: A tiny layer that provides simple yet substantial usability improvements for MongoDB usage within Node.JS.
  2. Socket.io: Socket.io is a library that enables real-time, bidirectional and event-based communication between the browser and the server.

3. Express: Fast, minimalist web framework for node.

Main Code

Path: backend-my-app/server.js

const express = require("express"); const http = require("http"); const socketIO = require("socket.io"); // Connection string of MongoDb database hosted on Mlab or locally var connection_string = "**********"; // Collection name should be "FoodItems", only one collection as of now. // Document format should be as mentioned below, at least one such document: // { // "_id": { // "$oid": "5c0a1bdfe7179a6ca0844567" // }, // "name": "Veg Roll", // "predQty": 100, // "prodQty": 295, // "ordQty": 1 // } const db = require("monk")(connection_string); const collection_foodItems = db.get("FoodItems"); // our localhost port const port = process.env.PORT || 3000; const app = express(); // our server instance const server = http.createServer(app); // This creates our socket using the instance of the server const io = socketIO(server); io.on("connection", socket => { // console.log("New client connected" + socket.id); //console.log(socket); // Returning the initial data of food menu from FoodItems collection socket.on("initial_data", () => { collection_foodItems.find({}).then(docs => { io.sockets.emit("get_data", docs); }); }); // Placing the order, gets called from /src/main/PlaceOrder.js of Frontend socket.on("putOrder", order => { collection_foodItems .update({ _id: order._id }, { $inc: { ordQty: order.order } }) .then(updatedDoc => { // Emitting event to update the Kitchen opened across the devices with the realtime order values io.sockets.emit("change_data"); }); }); // Order completion, gets called from /src/main/Kitchen.js socket.on("mark_done", id => { collection_foodItems .update({ _id: id }, { $inc: { ordQty: -1, prodQty: 1 } }) .then(updatedDoc => { //Updating the different Kitchen area with the current Status. io.sockets.emit("change_data"); }); }); // Functionality to change the predicted quantity value, called from /src/main/UpdatePredicted.js socket.on("ChangePred", predicted_data => { collection_foodItems .update( { _id: predicted_data._id }, { $set: { predQty: predicted_data.predQty } } ) .then(updatedDoc => { // Socket event to update the Predicted quantity across the Kitchen io.sockets.emit("change_data"); }); }); // disconnect is fired when a client leaves the server socket.on("disconnect", () => { console.log("user disconnected"); }); }); /* Below mentioned steps are performed to return the Frontend build of create-react-app from build folder of backend Comment it out if running locally*/ app.use(express.static("build")); app.use("/kitchen", express.static("build")); app.use("/updatepredicted", express.static("build")); server.listen(port, () => console.log(`Listening on port ${port}`));

Database: MongoDB

Mlab: Database as a service for MongoDB

Collection Name: FoodItems

Document format: At least one document is needed in the FoodItems collection with the below mentioned format.

{ "name": "Veg Roll", // Food Name "predQty": 100, // Predicted Quantity "prodQty": 295, // Produced Quantity "ordQty": 1 // Total Order Quantity }

Hope you got the understanding of how to create a modular real time app using the trending MERN stack. If you found it helpful clap below, give stars to the project repo and share with your friends too.