Building a beautiful Kanban board with Node.js, React, and Websockets 🦄 ✨
In this article, you'll learn how to build a Kanban Board the same as you have in JIRA, Monday and Trello. We will do it with a beautiful drag-and-drop feature using React, Socket.io, and React beautiful DND. Users will be able to sign in, create and…
Just a quick background about us. Novu is the first open-source notification infrastructure. We basically help to manage all the product notifications. It can be In-App (the bell icon like you have in the Dev Community – Websockets), Emails, SMSs, and so on.
I would be super happy if you could give us a star! It will help me to make more articles every week 🚀 https://github.com/novuhq/novu
Socket.io is a popular JavaScript library that allows us to create real-time, bi-directional communication between web browsers and a Node.js server. It is a highly performant and reliable library optimized to process a large volume of data with minimal delay. It follows the WebSocket protocol and provides better functionalities, such as fallback to HTTP long-polling or automatic reconnection, which enables us to build efficient real-time applications.
Here, we’ll set up the project environment for the project. You’ll also learn how to add Socket.io to a React and Node.js application and connect both development servers for real-time communication via Socket.io.
Create the project folder containing two sub-folders named client and server.
mkdir todo-listcd todo-listmkdir client server
Navigate into the client folder via your terminal and create a new React.js project.
cd clientnpx create-react-app ./
Install Socket.io client API and React Router. React Router is a JavaScript library that enables us to navigate between pages in a React application.
npm install socket.io-client react-router-dom
Delete the redundant files such as the logo and the test files from the React app, and update the App.js file to display Hello World as below.
Navigate into the server folder and create a package.json file.
cd server & npm init -y
Install Express.js, CORS, Nodemon, and Socket.io Server API.
Express.js is a fast, minimalist framework that provides several features for building web applications in Node.js. CORS is a Node.js package that allows communication between different domains.
Nodemon is a Node.js tool that automatically restarts the server after detecting file changes, and Socket.io allows us to configure a real-time connection on the server.
npm install express cors nodemon socket.io
Create an index.js file – the entry point to the web server.
touch index.js
Set up a simple Node.js server using Express.js. The code snippet below returns a JSON object when you visit the http://localhost:4000/api in your browser.
Next, add Socket.io to the project to create a real-time connection. Before the app.get()block, copy the code below.
//New imports.....const socketIO = require('socket.io')(http, { cors: { origin: "http://localhost:3000" }});//Add this before the app.get() blocksocketIO.on('connection', (socket) => { console.log(`\u26a1: ${socket.id} user just connected!`); socket.on('disconnect', () => { socket.disconnect() console.log('\ud83d\udd25: A user disconnected'); });});
From the code snippet above, the socket.io("connection") function establishes a connection with the React app, then creates a unique ID for each socket and logs the ID to the console whenever a user visits the web page.
When you refresh or close the web page, the socket fires the disconnect event showing that a user has disconnected from the socket.
Configure Nodemon by adding the start command to the list of scripts in the package.json file. The code snippet below starts the server using Nodemon.
//In server/package.json"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon index.js" },
You can now run the server with Nodemon by using the command below.
Here, we’ll create the user interface for the application. It is divided into three pages: the Login page, Task page – the central part of the application, and The Comments page – where users can comment on each task.
Navigate into client/src and create a components folder containing the Login.js, Task.js, and Comments.js files.
cd client/srcmkdir componentscd componentstouch Login.js Task.js Comments.js
Update the App.js file to render the newly created components on different routes via React Router.
import { BrowserRouter, Route, Routes } from "react-router-dom";import Comments from "./components/Comments";import Task from "./components/Task";import Login from "./components/Login";function App() { return ( } /> } /> } /> </Routes> </BrowserRouter> );}export default App;
Navigate into the src/index.css file and copy the code below. It contains all the CSS required for styling this project.
Here, I’ll guide you through creating the web layout for the Tasks page. The image below represents the page’s layout.
Divide the layout into three components namely: Nav.js, AddTask.js – the form input section, and TasksContainer.js – containing the tasks.
cd src/componentstouch Nav.js AddTask.js TasksContainer.js
Render the components within the Task.js file.
import React from "react";import AddTask from "./AddTask";import TasksContainer from "./TasksContainer";import Nav from "./Nav";import socketIO from "socket.io-client";/*\ud83d\udc47\ud83c\udffb Pass Socket.io into the required components where communications are made with the server*/const socket = socketIO.connect("http://localhost:4000");const Task = () => { return ( </div> );};export default Task;
Copy the code below into the Nav.js file.
import React from "react";const Nav = () => { return ( Team's todo list</h3> </nav> );};export default Nav;
Here, you’ll learn how to add the drag-and-drop feature using React Beautiful DND and communicate between the React app and a Socket.io Node.js server.
React Beautiful DND is a highly performant library that allows us to select and drag an item from its current position to another position on the page.
The image above explains how to set up React Beautiful DND. You must wrap all the draggable and droppable items within the <DragDropContext/>. The <Droppable/> component holds the draggable items placed within the <Draggable/> component.
Here, you’ll learn how to add React Beautiful DND to the React app and make the tasks movable from one category to another (pending, ongoing, and completed).
Install React Beautiful DND and ensure you are not using React in strict mode. (Check src/index.js).
npm install react-beautiful-dnd
Open the server/index.js file and create an object containing all the dummy data for each task category.
The DragDropContext wraps the entire drag-and-drop container, and Droppable represents the parent element for the draggable elements.
The Draggable and Droppable components accept a draggable and droppable ID. They also accept a child element, provided – that allows us to reference and render each element as a draggable and droppable item.
Feel free to separate the code into different components and click here to learn more about React Beautiful DND.
The DragDropContext accepts a prop onDragEnd, which fires immediately after dragging an element.
//\ud83d\udc47\ud83c\udffb This function is the value of the onDragEnd propconst handleDragEnd = ({ destination, source }) => { if (!destination) return; if ( destination.index === source.index && destination.droppableId === source.droppableId ) return; socket.emit("taskDragged", { source, destination, });};
The code snippet above accepts the destination and source of dragged item, checks if it was dragged to a droppable destination, and if the source and the destination are not the same before sending a message to the Node.js server via Socket.io.
Create a listener to the taskDragged event on the backend.
socketIO.on("connection", (socket) => { console.log(`\u26a1: ${socket.id} user just connected!`); socket.on("taskDragged", (data) => { console.log(data); }); socket.on("disconnect", () => { socket.disconnect(); console.log("\ud83d\udd25: A user disconnected"); });});
Let’s briefly examine the data returned after dragging an item:
The code snippet below shows that the item moved from the Pending category to the Ongoing category. The index also changed from 0 to 1.
Next, make the dragged item remain at its destination. Update the taskDragged listener as below:
socket.on("taskDragged", (data) => { const { source, destination } = data; //\ud83d\udc47\ud83c\udffb Gets the item that was dragged const itemMoved = { ...tasks[source.droppableId].items[source.index], }; console.log("DraggedItem>>> ", itemMoved); //\ud83d\udc47\ud83c\udffb Removes the item from the its source tasks[source.droppableId].items.splice(source.index, 1); //\ud83d\udc47\ud83c\udffb Add the item to its destination using its destination index tasks[destination.droppableId].items.splice(destination.index, 0, itemMoved); //\ud83d\udc47\ud83c\udffb Sends the updated tasks object to the React app socket.emit("tasks", tasks); /* \ud83d\udc47\ud83c\udffb Print the items at the Source and Destination console.log("Source >>>", tasks[source.droppableId].items); console.log("Destination >>>", tasks[destination.droppableId].items); */});
Create a listener for the tasks event within the TasksContainer component.
Create a listener for the createTask event on the backend server and add the item to the tasks object.
socketIO.on("connection", (socket) => { console.log(`\u26a1: ${socket.id} user just connected!`); socket.on("createTask", (data) => { // \ud83d\udc47\ud83c\udffb Constructs an object according to the data structure const newTask = { id: fetchID(), title: data.task, comments: [] }; // \ud83d\udc47\ud83c\udffb Adds the task to the pending category tasks["pending"].items.push(newTask); /* \ud83d\udc47\ud83c\udffb Fires the tasks event for update */ socket.emit("tasks", tasks); }); //...other listeners});
In this section, you’ll learn how to add and retrieve comments on each task.
Update the Comments.js file as below:
import React, { useEffect, useState } from "react";import socketIO from "socket.io-client";import { useParams } from "react-router-dom";const socket = socketIO.connect("http://localhost:4000");const Comments = () => { const { category, id } = useParams(); const [comment, setComment] = useState(""); const addComment = (e) => { e.preventDefault(); /* \ud83d\udc47\ud83c\udffb sends the comment, the task category, item's id and the userID. */ socket.emit("addComment", { comment, category, id, userId: localStorage.getItem("userId"), }); setComment(""); }; return ( Add a comment</label> setComment(e.target.value)} rows={5} id='comment' name='comment' required ></textarea> ADD COMMENT</button> </form> Existing Comments</h2> </div> </div> </div> );};export default Comments;
Recall that the route for the Comments page is /comments/:category/:id; the code snippet above retrieves the item’s category and its ID from the page’s URL, then sends the item’s category, ID, user ID, and the comment to the Node.js server.
Next, create an event listener on the Node.js server that adds the comment to the specific task via its ID.
socket.on("addComment", (data) => { const { category, userId, comment, id } = data; //\ud83d\udc47\ud83c\udffb Gets the items in the task's category const taskItems = tasks[category].items; //\ud83d\udc47\ud83c\udffb Loops through the list of items to find a matching ID for (let i = 0; i < taskItems.length; i++) { if (taskItems[i].id === id) { //\ud83d\udc47\ud83c\udffb Then adds the comment to the list of comments under the item (task) taskItems[i].comments.push({ name: userId, text: comment, id: fetchID(), }); //\ud83d\udc47\ud83c\udffb sends a new event to the React app socket.emit("comments", taskItems[i].comments); } }});
If you want to add notifications to the application when a user adds a comment or a new task, you can do that easily with Novu within the Nav.js component.
Novu allows you to add various notification types, such as email, SMS, and in-app notifications.
To add the in-app notification, install the Novu Node.js SDK on the server and the Notification Center in the React app.
\ud83d\udc47\ud83c\udffb Install on the clientnpm install @novu/notification-center\ud83d\udc47\ud83c\udffb Install on the servernpm install @novu/node
Create a Novu project by running the code below. A personalised dashboard is available to you.
\ud83d\udc47\ud83c\udffb Install on the clientnpx novu init
You will need to sign in with Github before creating a Novu project. The code snippet below contains the steps you should follow after running npx novu init
Now let's setup your account and send your first notification\u2753 What is your application name? Devto Clone\u2753 Now lets setup your environment. How would you like to proceed? > Create a free cloud account (Recommended)\u2753 Create your account with: > Sign-in with GitHub\u2753 I accept the Terms and Condidtions (https://novu.co/terms) and have read the Privacy Policy (https://novu.co/privacy) > Yes\u2714\ufe0f Create your account successfully.We've created a demo web page for you to see novu notifications in action.Visit: http://localhost:57807/demo to continue
Visit the demo web page http://localhost:57807/demo, copy your subscriber ID from the page, and click the Skip Tutorial button. We’ll be using it later in this tutorial.
Update the components/Nav.js file to contain Novu and its required elements for in-app notifications from the documentation.
import React from "react";import { NovuProvider, PopoverNotificationCenter, NotificationBell,} from "@novu/notification-center";import { useNavigate } from "react-router-dom";const Nav = () => { const navigate = useNavigate(); const onNotificationClick = (notification) => navigate(notification.cta.data.url); return ( Team's todo list</h3> {({ unseenCount }) => ( )} </PopoverNotificationCenter> </NovuProvider> </div> </nav> );};export default Nav;
The code snippet above adds Novu notification bell icon to the Nav component, enabling us to view all the notifications from the application.
💡 The NovuProvider component requires your Subscriber ID – copied earlier from http://localhost:57807/demoand your application ID available in the Settings section under API Keys on the Novu Manage Platform.
Next, let’s create the workflow for the application, which describes the features you want to add to the application.
Select Notification from the Development sidebar and create a notification template. Select the newly created template, click on Workflow Editor, and ensure the workflow is as below:
From the image above, Novu triggers the Digest engine before sending the in-app notification.
Novu Digest allows us to control how we want to send notifications within the application. It collects multiple trigger events and sends them as a single message. The image above sends notifications every 2 minutes, and it can be effective when you have many users and frequent updates.
Click the In-App step and edit the notification template to contain the content below.
{{userId}} added a new task.
💡 Novu allows you to add dynamic content or data to the templates using the Handlebars templating engine. The data for the username variable will be inserted into the template as a payload from the request.
Save the template by clicking Update button and head back to your code editor.
So far, you’ve learnt how to set up Socket.io in a React and Node.js application, communicate between a server and a client via Socket.io, and drag and drop items with React Beautiful DND.
This is a demo of what you can build using Socket.io and React Beautiful DND. Feel free to improve the application by adding authentication, the ability to assign tasks to a particular user, and add notifications when a user drops a comment.