Πώς να δημιουργήσετε μια εφαρμογή σε πραγματικό χρόνο χρησιμοποιώντας Socket.io, React, Node & MongoDB

Αναρωτηθήκατε ποτέ πώς κατασκευάζονται οι εφαρμογές σε πραγματικό χρόνο; Έχετε παρατηρήσει ποτέ τη σημασία και τις περιπτώσεις χρήσης εφαρμογών σε πραγματικό χρόνο;

Εάν είστε περίεργοι για τις παραπάνω ερωτήσεις και χρειάζεστε απάντηση, τότε αυτή η ανάρτηση ιστολογίου είναι για εσάς.

Αρχικά, ας εντοπίσουμε μερικές περιπτώσεις χρήσης που χρειάζονται εφαρμογές σε πραγματικό χρόνο:

  1. Λήψη ενημερώσεων τοποθεσίας για την καμπίνα σας σε χάρτη μιας αίτησης κράτησης ταξί.
  2. Λήψη νέων μηνυμάτων άμεσα στην αγαπημένη σας εφαρμογή συνομιλίας.
  3. Ενημέρωση πληροφοριών παραγγελίας φαγητού στην κουζίνα του αγαπημένου σας εστιατορίου.

Όλα αυτά είναι τα κοινά σενάρια της καθημερινής μας ζωής όπου δεν μπορούμε να ανεχθούμε την καθυστέρηση στην ενημέρωση των πληροφοριών και ως εκ τούτου χρειαζόμαστε επικοινωνία σε πραγματικό χρόνο.

Τεχνολογίες που μπορούν να χρησιμοποιηθούν για επικοινωνία σε πραγματικό χρόνο είναι:

  1. Σύντομη ψηφοφορία : AJAX, δημιουργεί μεγάλη κίνηση.
  2. Long Polling : Όπως το AJAX, αλλά ο διακομιστής διατηρεί την απόκριση έως ότου έχει ενημερωθεί. Αφού το παραλάβει, ο πελάτης στέλνει ένα άλλο αίτημα και χρειάζεται επιπλέον κεφαλίδα για να περάσει μπρος-πίσω προκαλώντας επιπλέον επιβάρυνση.
  3. Web Sockets : επιτρέψτε το άνοιγμα διαδραστικής επικοινωνίας μεταξύ του πελάτη και του διακομιστή. Κάποιος μπορεί να στείλει ένα αίτημα στον διακομιστή και να λάβει απαντήσεις βάσει συμβάντων χωρίς να ρωτήσει τον διακομιστή για απάντηση, καθιστώντας τις πρίζες Ιστού την καλύτερη επιλογή για την περίπτωση χρήσης μας.

Μπορείτε να διαβάσετε περισσότερες σε βάθος πληροφορίες σχετικά με τις παραπάνω τρεις τεχνολογίες εδώ.

Θα μάθουμε να δημιουργούμε μια εφαρμογή σε πραγματικό χρόνο καλύπτοντας το ακόλουθο σενάριο.

Φανταστείτε ότι κάθεστε στο αγαπημένο σας εστιατόριο και έχετε ένα ψηφιακό μενού. Κάνετε την παραγγελία και η κουζίνα ενημερώνεται σχετικά με την παραγγελία σας σε πραγματικό χρόνο. Όταν η κουζίνα τελειώσει με την παραγγελία, την ενημερώνουν και σε πραγματικό χρόνο.

Χαρακτηριστικά λεπτομερώς:

  1. Παραγγελία : Διεπαφή για να επιλέξετε την ποσότητα και να κάνετε την παραγγελία για ένα επιλεγμένο είδος φαγητού στην κουζίνα.
  2. Κουζίνα : Διεπαφή που μπορεί να ανοίξει σε πολλές κουζίνες και ενημερώσεις σε πραγματικό χρόνο, οι σεφ και οι μάγειρες σχετικά με τις συνολικές παραγγελίες που δημιουργήθηκαν και την προβλεπόμενη ποσότητα τροφίμων, δίνοντάς τους την ευελιξία να το ενημερώσουν. Επίσης, έχει τη δυνατότητα λήψης της αναφοράς με τη μορφή φύλλου excel.
  3. Αλλαγή προβλεπόμενης : Διεπαφή για ενημέρωση της προβλεπόμενης ποσότητας ειδών διατροφής.

Μπορείτε να βρείτε μια ζωντανή επίδειξη αυτού του σεναρίου εδώ.

Για καλύτερη κατανόηση, ανοίξτε το σε διαφορετικές καρτέλες / συσκευές ταυτόχρονα για να δείτε τα δεδομένα να αλλάζουν σε πραγματικό χρόνο.

Ο πηγαίος κώδικας είναι εδώ. Μη διστάσετε να κάνετε κάτι καινοτόμο / χρήσιμο.

Ας ξεκινήσουμε λοιπόν.

Τεχνολογία στοίβα:

Frontend : React.js, Reactstrap, Socket.io

Backend : 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. react-html-table-to-excel: Παρέχει μια δημιουργία αρχείου Excel (.xls) από πλευράς πελάτη από στοιχείο πίνακα HTML
  4. react-router-dom: Συνδέσεις DOM για αντιδραστήρα αντιδράσεων Αποτελείται από πολλά σημαντικά στοιχεία όπως το BrowserRouter που χρησιμοποιείται όταν υπάρχει διακομιστής για τη διαχείριση δυναμικών αιτήσεων, εναλλαγής, διαδρομής κ.λπ.

Στοιχείο εφαρμογής

Διαδρομή : 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

Η λογική UI της οθόνης κουζίνας και ο κώδικας 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()}
Name Quantity Created Till Now Predicted Status
); } } 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()}
Product Quantity Order
); } } 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

Μορφή εγγράφου : Απαιτείται τουλάχιστον ένα έγγραφο στη συλλογή FoodItems με την παρακάτω αναφερόμενη μορφή.

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

Ελπίζω να έχετε κατανοήσει πώς να δημιουργήσετε μια αρθρωτή εφαρμογή πραγματικού χρόνου χρησιμοποιώντας την τάση MERN stack. Αν και τη βρήκε χρήσιμη χειροκρότημα παρακάτω, δίνουν αστέρια στο repo του έργου και μοιραστείτε με τους φίλους σας πάρα πολύ.