Build a Real-Time Code Editor with React, Node.js, Socket.io, Redis & CodeMirror
Build a Real-Time Code Editor with React, Node.js, Socket.io, Redis & CodeMirror
#JavaScript, #LearnToCode, #CodingTips, #WebDevelopment, #Frontend, #Fullstack, #Programming, #CodeNewbie
AI Generated·May contain errors
Video Summary
AI GeneratedThis technical walkthrough demonstrates how to build a real-time collaborative code editor. Using Node.js, Express, and Socket.io, the instructor explains how to synchronize code across multiple clients in isolated rooms, utilizing Redis for state management and CodeMirror for the frontend editing experience.
Hey, so sometimes when you do some coding interviews with some companies
They will give you a link to some real-time like code editor that you and your interviewer can write and see each other code
At the same time like in real time. So this is
In this video, we will implement something similar to that
I'm gonna try to make it as fast as and to the point as much as I can
I don't want to waste time on useless stuff. So this is what we will be doing and
Here is a demo
So first thing you can write your name
So write my name click go this will create a room and now send your this room ID to other people now they can
Join you. So let's actually do that
Go to this URL localhost 3000 now enter any name you want then enter a room ID
This now and notice here how many people are connected. So if I click join now, we have two people connected and
Same thing applies here. So let's go again and go to another tab and
click anything we want join we have now three users connected and anything I type here will be shared between them and
One issue here that I'm gonna leave to you to fix it should be very easy
but I'm gonna leave it to you if you are interested, of course and
You should hear type for example
Just enter the same room as you can see you won't see the previous changes that happen in that room, right?
So if someone you come in I can type this and everything got disappeared
So yeah, I'm using this as the backend
So you should be able to store the current code and just share it each time a new user connected
Should be easy and the cool thing here
When I click when I close this browser window, I lost the connection, right?
So the server will notify all other clients that hey, we now have only three users connected not four
So this still have like there are so many things left
For example, if I if my case are here all of the other clients should actually see this user case of words
Still doable. But yeah, I'm just gonna show you the main idea here how we can do this kind of thing
And another cool thing if you go to localhost 3000 and create another room
these events here or
All of like our web socket connection will be separated into multiple rooms
and each room will have this unique ID and
The events will be like sent from the server to the clients based on that unique ID
So I can actually prove that to you. So let me open the network
Go to WS which means web socket. So I'm gonna refresh
Clear I'm gonna create a new room. So as you can see here, I have my web socket connection and
If I typed here nothing actually being sent here
because that's a different room it won't actually receive it won't be received here and
Yeah, so that's basically that's basically the idea. That's basically what we'll be doing
So let's just get started
So, let me just close the demo and
Open what we have in the code. So I won't waste your time installing a bunch of random packages and all of that
I have this code here
I'm just gonna run npm run dev. This will run the front-end for us
Which will run the same port. So this is the state that we have it and
What we need to implement is the real-time code editor and part of the servers
In our server we have this Redis client I'm connecting here
It's using the default configuration the same port number local or stuff like that
So I don't need to pass anything to the connect and I'm using the Redis adapter for which is a promise best as you can see
you installed it by typing npm install Redis app next and
I have here a bunch. This is for testing that to make sure everything runs correctly
This is a post request this post like this post endpoint will be activated
When you actually click go here, so let me show that to you. So it's in the internal name
Component I am using the RT query. This is a mutation
basically, you will give it a function that retains a promise and
You can attach also some stuff like on success on failure things like that
Reacting query will handle so much
Things for you. It's this video is not about it
But I'm using that to create this user name and I'm sending requests to this a create room with user
Endpoint which will go to Redis first thing and store or before that it will generate any an ID unique ID
based on the UUID package from Node.js and
But after generating that ID it will store in Redis a hash set containing the created and updated at
Dates dead times for that room. And if you think about this is the key for that data
If you think about it, what you can do is just add here the code. This is the current code for
the shared code editor between multiple clients right and each time someone changed that you will just come here and change it and
Each new user connecting to that room. You will just go to this hash set extract the code return it to the user
So that's how we fix that previous issue, but I'm gonna just continue implementing what we saw in the demo
So yeah
that's basically this endpoint and it will return the room ID and
after the room ID will be returned as you can see here on the success and
The go and on the join functions. I have them here both. This is the join and this is the go and
I will just go and
Set the room ID to the retained value or if you are joining one
I'm just gonna set the room ID globally to the value added in the input and
Set the user name and by the way, I'm using the stand and this is basically our global store
It's the easiest state management library. I found it's even easier from the react context. It's super easy
So this is basically global and you will just use this function anywhere and extract these values and that's it. Super easy
So that's this is it for the inter name. You know, you don't even need to look at it
I think if you wanna like watch this video, you will look only on the server and in the real time editor and that's it
So let's close all of that. We already have this code written for us. So let's implement the server
We are creating this hash set that represents that room. So what we need now is
Since this is what will happen now. I
Think I'm not yeah, I did not run the server to run it. You will go to the back to JSON
You will see we have this nodemon
command so npm run dev server
Now it should be working. So click go. We are now like we have a user name
We just start add a new room. What we need to do is to connect to that home. So let's actually come here and
write our code
So first thing if you look to our package.json, I installed the socket.io
That's this will be used in the backend in Express and you will see it here as you can see I'm creating a server and
Assigning it to a variable called IO and on connection this actually
console.log for now
I'm gonna use
yellow
Yeah, yellow bright from chalk
dot bold
new user like this new user
And to use this socket.io
Package on the front end, which is just an abstraction on top of the socket websocket protocol, which allows you to
which allows like the server and the client to
communicate between each other like
Bidirectionally the server could send in at any moment to the client and the client can send at any moment to the server
It's different from HTTP request
If you think about it in HTTP request you are sent the client always the one that starts the communication
In websocket like if the client needs an extra information
But those sockets any one of them can send to the other of course after establishing the connection
Which happens in the front end. The front end will ask for that connection or will ask for establishing a connection after that
The connection will be established and any one of them can send data to each other at any time
So yeah, with all of that out of the way, let's come back to the
Real-time code editor. I will be using CodeMirror
CodeMirror is very nice package and one of the coolest thing I like about it
Let me show you the writer that wrote this package
It's this guy
I just make sure I'm recording but he have this book called
Illicon JavaScript. I actually did this book when I was in university
It's very very good book if you are a beginner
It says it's a modern introduction, but I don't think I think maybe
When you reach here, it's not so easy, but it's really good book
Yeah, he also wrote this package. So I'm gonna use it
Yeah
And also I'm using Chakra UI for the front end, but yeah, it does not matter
So first thing I'm gonna import from React the useEffect and useState
We will use that eventually and I'm gonna import the CSS code from that for the CodeMirror
And I'm gonna import the material ocean theme and I'm gonna import the JavaScript mode
If you want to change to different mode, you may you might have this drop down
you will like
Select a specific language and you will dynamically import
that code
Or that mode like this that mode code like this using dynamic imports to then to catch that kind of stuff
But yeah, it's called dynamic imports. If you don't want to just import everything which will slow your web page so much
Yeah, so dynamically import them, but I only gonna use JavaScript. So I'm gonna import it from the start
I had to import the subline keymaps for some reason and I'm gonna import
I'm not sure why but I'm gonna import also the CodeMirror
I didn't see anywhere in the documentation that they requires you like to import these kind of stuff
If you are dealing with React, for example, or if you installed it through npm
But after using so many packages you kind of get to get like used to it
This is how you usually import most of the like these
Packages for example charting packages. You will keep importing some CSS some JavaScript here randomly
One thing that helps to actually go to the package and see the code
This helps you understand from where you need to import these kind of stuff. So yeah now I'm gonna import
IO from
circuit IO client
And I'm gonna import
from a chakra UI
Text component and I'm gonna import the Zastand store
Again Zastand is super simple. Just look this is the whole documentation this simple page. That's it. That's everything
It's really nice
So I'm just gonna import the store this contains the username and the current room ID which is available globally
So first thing I'm gonna define our state which will our local state on this component
which will have an array of usernames these will be strings and these will come from the web socket connection and
From our store and
This is how our store looks like
So it's just an object containing two properties and two functions so I can destruct anything I want from it, right?
So I'm gonna destruct the username and the room ID just like this
And I'm gonna use use effect to activate or to run a function as soon as this component
mounted like this
so
First thing I'm gonna initialize our
Code mirror, so I'm gonna come like down here and create this text area
with ID code
mirror
Like this
So to initialize the code mirror instance, I'm gonna create
Editor instance call or it will be equal to code mirror dot from text area
You could use document dot get element by ID or I use reference both will work it does not matter so get element by ID
This will be the code mirror
like this
Now you need to give it some configuration. So a line numbers. I'm gonna I need to show some line numbers
I think it will be nice. So I'm gonna give it true
Key map give it
Subline
Theme it will be the theme that we imported here
And their website you will have you will see all the themes that you have and the mode since I imported the JavaScript mode will
be
This one JavaScript
Now this actually go and connect to our web socket client
Also here as soon as we mount
Will say const socket will be equal to IO. I will give it that in like the server URL, which is
localhost port
3001 and
This is what I'm gonna do
And after that give it this it runs
port
With socket and that's it. Now. We should actually be like we should actually connect and
Yeah, I think this should be it so let's actually go and see what will happen now and
Or no, I need to actually look at the server here
Remember, we said each time we have a new connection web socket connection. We will console like this
So let's actually put that one here
This is our node.js logs
So we have a new connecting user and something to remember each web socket connection will have its own ID
This is something we will be using
shortly
dot ID like that and
Yeah, we already got it because the server stops got connected again and your connection happened so yeah
That's very nice. This is our code editor. Now we need to
Like as soon as the user connects with the backend we need to store how many users we actually have so right
So I'm gonna say
so on connection, I'm gonna
Expect from the front end to send
An event called connected to room and in that event, you will send me the room ID and the user name
Okay, and let me just add like give you two notes this I or this
Represents the whole web socket our server that we have and if I type anywhere
I or dot emit this will send an event to all the people connected to my
Web socket server if I want to send events from a specific connection, you will use this socket server
So if I type dot emit this will be sent from this exact socket
Which will help us for example doing something like like this so broadcast
Yeah broadcast so I thought to complete sometimes
It's really annoying dot emit this will send an event to all the connected users in the same
Like web socket server except this like this actual
Connection. So for example, and we will use this in our content change if I change the code
I don't need to this event to be recent to me again, right?
So we'll be using a broadcast broadcast will send an event to all the other web socket connections
Except this one that I called it on and I always send it to everybody in the server
But remember we talked about rooms
So we will I will show you how we can create rooms and how we can like limit this
Event to a specific room to be like emitted to a specific room. I hope it's clear. But yeah, so I'm gonna
Expect from any client connections to at some point
like
Give me or emit an event from the front end to the server
This event will be connected to room and again, it will accept room ID and username so socket dot on
This will be this is the name of the event. This is a call back which will accept the data being sent
I'm gonna make it an asynchronous call back and this will accept a data. I will destruct from it the room ID and
username
Okay. So first thing
There is something you need to remember in the desk
You can't miss that as such as that they provide the list and the hash that you can't miss them
So you can't miss a list inside the hash set. Okay, so what I'm gonna do
I'm gonna create the or the hash that I created here for the room
Will represents a like meta information about the actual room created at dated. Maybe the code the current code
Maybe you can add that and here I'm gonna create a list that have somehow the same room ID
Which I sent here and it will point to all the users names. So let's actually do that. So await
Client this is the latest client with list push
The name of the key will be the current or the room ID the user sent
colon users and the value will be
Or the first value. I'm gonna push its username and by the way
We won't face that but this does not have an empty list it should always have a data
So since and since I'm get telling us to push something and this key does not exist
It will create an array and put to it a bit inside it this element. So yeah, that's what's gonna happen here and
I need to like reference that hey this
This socket ID or this connection is like connected to this user
Remember that I showed each socket will have its own ID. So I need to connect that with the current user name and
If you think about it, I'm pretty sure you already thought about that. Our user names could be duplicated
So I could have the was a specific created a specific room and in another tab
I will join with the same user name. Maybe I will leave that also to you
It should should be straightforward. But again, we are not dealing with like authentication or any kind of these stuff
So I'm just gonna show you how we can do rooms how we can connect specific connection with a specific user
You can do all of that on a much better way, maybe later. So I'm gonna also store in our Redis
The hash set I'm gonna store a set
The key will be the socket ID connection and it will point to the room ID and the user name
Okay. Now, let me actually go and grab all the users we have
That because I could actually connect to already existing room, right?
So this list push will push to an existing list of users or it create a new one, which is very nice
so let's actually go and read all the
Like the users we have you can use for that. I'm kind of
beginner in Redis, so
Maybe that's not the best thing. So
I'm gonna add the room ID and then users you will use the L range for that
It's written like this and give it a start and end at minus one
this will give us the whole list and
Let's now create our actual room name. So room name will be equal
This should be like a dynamic unique string
Okay, or just a string but in our case
It will be dynamic because each one will be will have this its own ID unique ID
And this is our room name. So now to create or
You don't actually create rooms you make web socket connections join a specific room and that's it
And this will look like this join and give it this unique string. And now this web socket actually
Like joined or inside this room and to omit for example events
into this like
Room you will just type
Socket dot in and give it the room name
Dot emit and this should do it
But I want actually to send this event to everyone in the room that hey, we have a new user connected
So update the user count or I'm gonna send you the users list and do whatever you want to with it
So I'm gonna say IO emit or sorry in
For all the people in this room emit to them
this event which is called a room connection and
sent with it the users array that we have which is just a list of strings and
This should be
This should be like a good start to go back in our real-time
JSX component and this actually
Come here. And after I'll be created our web socket connection. Let's
attach an event listener to the connected to room event and
Or sorry, sorry the server
Wants this connection right now. It's this event to be emitted to it. So
We will say hey when we connect to the server emit this event to the server
So I'm gonna say so do it emit
Will give it the same name the same event name that we expect here and I will pass to it the user name and the room
ID which I actually
Got here from the top. Okay
Yeah, and now since we emitted this
Event
So sorry, we should emit this one
Yeah connected to room and now we should listen to this one to get how many users we have
Yeah, I'm just gonna say hey
Socket listen to this event and when you got some data, which would be a list of users
Just set users
And now this will actually give us how many users we have in the current room
And so let me just copy paste some JSX here
So your user name is user name or my D and how many people is connected the users list dot link
So, let's actually go and test that
So, let me create a room let's actually go here and connect to it
Join now, we have two connected users. Yeah, I hope it's clean
I hope I'm not like so fast or making things confusing, but let's just go around that again
In our server in each web socket connection
We have we attach an event listener to some made up like event
We have it's called connected to each time a web socket connection emits that event to us
we are expecting these data the user name and the room ID and
Each time we get the room ID will we will create or push to the same list of users we have in our radius
I'm not sure if I can show you that because I have so many data
Maybe I should have cleared that but yeah, it will look something like this
even this visualization is not that good, but I
Hope like if you are this is the first time you installed it this maybe just keep checking that you will see
Somewhere you have your data
but yeah, we will
Push to an existing list if that does not exist it will create a new one with that user name
So it's a list of users and we will create a hash set linking that socket ID
With a user name and a room ID. Why is that because at some point we will attach this
Disconnect event. So each time a specific web socket
Connection have been disconnected and that could happen when you close the browser
And if you notice I already closed one of the instances from Chrome and we still have two users connected
That's why that's because we did not clear
The room ID colon users list from Redis and this happens here. So maybe we can implement that now and
You can't send any data here as far as I know
I might be wrong
Because the browser already closed how you can send that, right?
And this will be emitted as soon as the connection will be lost from the client
So that's why I actually stored this hash set in Redis. So what I can do. I already have the socket ID
It's the same one that actually got disconnected, right? So what I can say a destruct from the client dot
Hash get all get all the keys get go and get that key
Like get the socket. I get the hash set with the socket ID
key
you close this and
Let me destruct since I added the room ID and a user name inside an object there. I can destruct them. So now
We have a new list of users. Oh, let me first grab the existing list. I need to remove one of them, right?
That's why I also
Stored the user name because now I can look over that remove the user name that actually got
Disconnected of course in a realistic example, this could be through maybe a JWT, right?
because here we have some duplicate like the user could have or some people could
Like enter a room with the same user name and this could break our system at the moment
So client dot
I'll range and just give it the same. It's the same thing we did here. So I'm just that's gonna be best
Now in your users will be equal to users dot filter
This is the user
User does not equal to user name. So I'm filtering the user name that actually got
went out
So if we have
Remember that in Redis we don't have an empty list. So if we have
A new user's list or yeah, if it's not empty, I'm just gonna
delete
the previous one completely and
I'm just gonna await client. I'm just gonna do the same thing here, but I'm gonna pass to it the new users
right and else
I'm just gonna delete that
and that's
Should be it now
const room name will be equal to the same thing here and
I could say for all the people in this room
Remember, we don't create rooms
we just attach socket connections to rooms and these rooms are just like you can think about them just
Random strings and all of these people are connecting
Through the web socket connection in the this random thing, which we call rooms. So I'm just gonna emit to them
Hey we have
Well, I'm just gonna make them emit to them the same connection or the same event here the room connection
I'll just pass to them the new users lists list and this should be it
Let's actually go and test that
Yeah, we should have two browsers links we have instances so go
Okay, we have an error
Yeah this because I'm using await and this should be a synchronous function. It should work now. So go
It's actually I
Think we have some errors
Room ID is not defined
It's here
Oops, my bad
Yeah, it's like this so go we have one connected user
So let's actually connect again, so we have two connected users now if I click close we have only one now this works
So to visualize things a little bit like in a better way
Let me go into the inspect click network. Click the video asked which means web socket click join
You will see we have now a web socket connection
And this is all of the data that actually got sent between the server and the client
and as you can see the server sent to us and
Green means we sent to the server red means the server
Returned to us some data or sent us some data not returned and as you can see this is the name of the event
This is the data. This is the two user names that we have so like keeping track of this or opening this web socket
Inspect tool in Chrome really helpful. So highly recommended. So yeah now we handled connecting and disconnecting now
We need to handle typing here, right?
This should be very easy now
Yeah
so let me just
Look at the code
So first thing we have this editor instance what we can
Like have is here is to attach
the change the change event and this will get an
instance and the changes
I'm gonna get from the
Changes the origin one of the nicest thing in about about code mirror
It gives you the origin that
Changes that or changes the text editor. Let me actually just console like that
Which is very nice. I mean in some packages like not text editors
Like in a lot of packages that they don't give you the origin and let me just show you the origin, right?
So as you can see it tells us that hey there is it's plus input
Which means the user actually type if you program like using JavaScript you change that
It will tells you. Hey, this have been using copy paste as you can see cut paste
Paste it will gives you what happened. Actually, I'm not sure about JavaScript, but it will give you something different
But yeah, I don't care about that. Only the only origin I care about
and so if
Origin does not equal to set value and I had to test that multiple times. I'm just gonna emit an event called
code it changed and
the server should actually expect that and
This is for example, if imagine if we have 20 clients one of them will send to the server
Hey code it changed the server now have to emit if like to every other connected
Circuits to that in that room. Hey, the code it changed. Here is the new value
Except I want to send the same event to the same circuit connection again
Okay. So now the server should actually expect that. So let's go implement that
So I'm gonna have it here at the top
so circuit.on
and
It will be a sitemask function. It will accepts the code and
Lists let's get
await
That it is client
Hash get all the circuit.id. I'm getting here the room ID for that user
now, let's actually get the room name just like we did here and
Let's say hey circuit to send to all the people in the in this room
send them
This event it's the same event
code
Now one of the client send this code it changed the event to the server the server
Emitted that to all the connected clients expect except this one
And now what we need to go what we need to do is to handle receiving this on the client
We can add that here so sorry at the end after defining the circuit
Circuit.on code change
This will accepts a code
I'm just gonna say editor.sidValue
code and
This will actually add the code there and this should be it
Yes, let's start from the beginning
join there and
As you can see here it is
So I hope this was useful in any way. I
Hope I did not make it confusing
This is basically this is idea. I think the most important thing here is the idea behind rooms
You don't create them. You just create this random string. Oh, it should be unique. I think
events in your use case and just
Join a specific socket connection to that rule and then emit events to that connection using to and in
In will give it to all other people to will give it only to the people connecting
sorry in
Will sub will send an event to all the people in that room and you will use it
And you will use this like the global name space you have in the web socket
And if you used socket.to this will send an emit
Sorry, this will emit an event to all the connected sockets in that room except myself and just let me actually show you this
it's the
socket I
Socket IO and if you go to the documentation
events
Emit cheat sheet as you can see here it is. So
To all the clients in room one expect the sender
This is the two and somewhere we will see the in to all the clients in room one
There is multiple kind there is multiple ways to do it. Let me do this. But yeah, yeah, I hope it's useful
I think the next step just like I said if someone entered the same room
Like after we did some changes we should show them that
We should maybe so like
Display the cursors for each others to do that in CodeMirror. We have this thing called widgets
Sorry, not widgets, I think
Bookmarks. Yes. We have this thing called bookmarks. You will create a widget add it inside a bookmark display it anywhere
So you can do basically anything you want
Yeah, I hope I hope it was useful and I will in the description you will find a link where you can see the code
Yeah, and that's pretty much it. Bye

