Cómo crear una aplicación en tiempo real usando Socket.io, React, Node y MongoDB

¿Alguna vez se preguntó cómo se crean las aplicaciones en tiempo real? ¿Alguna vez notó la importancia y los casos de uso de las aplicaciones en tiempo real?

Si siente curiosidad por las preguntas anteriores y necesita una respuesta, esta publicación de blog es para usted.

Primero, identifiquemos algunos casos de uso que necesitan aplicaciones en tiempo real:

  1. Obtener actualizaciones de la ubicación de su taxi en un mapa de una aplicación de reserva de taxis.
  2. Recibir nuevos mensajes al instante en su aplicación de chat favorita.
  3. Actualización de la información de pedidos de alimentos en la cocina de su restaurante favorito.

Todos estos son escenarios comunes de nuestro día a día en los que no podemos tolerar un retraso en la actualización de la información y, por lo tanto, necesitamos una comunicación en tiempo real.

Las tecnologías que se pueden utilizar para la comunicación en tiempo real son:

  1. Sondeo corto : AJAX, crea mucho tráfico.
  2. Sondeo largo : como AJAX, pero el servidor retiene la respuesta hasta que tiene una actualización. Después de recibirlo, el cliente envía otra solicitud y necesita que se recorra un encabezado adicional hacia adelante y hacia atrás, lo que genera una sobrecarga adicional.
  3. Web Sockets : permiten abrir la comunicación interactiva entre el cliente y el servidor. Se puede enviar una solicitud al servidor y recibir respuestas impulsadas por eventos sin sondear el servidor para obtener una respuesta, lo que hace que los sockets web sean la mejor opción para nuestro caso de uso.

Puede leer más información detallada sobre las tres tecnologías anteriores aquí.

Aprenderemos a crear una aplicación en tiempo real cubriendo el siguiente escenario.

Imagina que estás sentado en tu restaurante favorito y tienes un menú digital. Realiza el pedido y la cocina se actualiza con respecto a su pedido en tiempo real. Cuando la cocina termina con el pedido, también lo actualizan en tiempo real.

Características en detalle:

  1. Realizar pedido : interfaz para seleccionar la cantidad y realizar el pedido de un alimento seleccionado en la cocina.
  2. Cocina : Interfaz que se puede abrir en múltiples cocinas y actualiza en tiempo real a los chefs y cocineros con respecto a los pedidos totales creados y la cantidad prevista de alimentos, dándoles la flexibilidad de actualizarlos. También tiene una funcionalidad para descargar el informe en forma de hoja de Excel.
  3. Cambio previsto : interfaz para actualizar la cantidad prevista de alimentos.

Puede encontrar una demostración en vivo de este escenario aquí.

Para una mejor comprensión, ábralo en diferentes pestañas / dispositivos al mismo tiempo para ver el cambio de datos en tiempo real.

El código fuente está aquí. Siéntase libre de hacer algo innovador / útil además de eso.

Entonces empecemos.

Pila de tecnología:

Frontend : React.js, Reactstrap, Socket.io

Backend : Node.js (Express), MongoDB, Socket.io

Estructura de carpetas:

/* 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 */ ............

Explicación del código fuente:

Interfaz:

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

Paquetes utilizados:

  1. Reactstrap: componentes bootstrap4 fáciles de usar
  2. Socket.io: Socket.io es una biblioteca que permite la comunicación en tiempo real, bidireccional y basada en eventos entre el navegador y el servidor.
  3. react-html-table-to-excel: proporciona una generación del lado del cliente de un archivo de Excel (.xls) a partir de un elemento de tabla HTML.
  4. react-router-dom: enlaces DOM para react router. Consiste en muchos componentes importantes como BrowserRouter que se usa cuando hay un servidor para manejar solicitudes dinámicas, Switch, Route, etc.

Componente de la aplicación

Ruta : src / App.js

Este componente contiene la lógica de enrutamiento principal del Frontend. Este archivo se usa en src / index.js dentro del módulo del enrutador del navegador. El siguiente código demuestra uno de los enfoques para mantener su aplicación modular.

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;

Componente de encabezado

Ruta : src / global / header.js

Este componente será común y se utilizará en las secciones como Realizar pedido, Cambio previsto, Cocina. Este enfoque ayuda a evitar la duplicación de código y mantiene la aplicación modular.

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 };

Componente de cocina

Ruta : src / main / Kitchen.js

La lógica de la interfaz de usuario de la pantalla de cocina y el código html residen en este componente:

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;

Componente de pedido

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.