hey everyone in this video i'm going to show you a step-by-step how to build mobile friendly websocket-based real-time web chat application completely from scratch and then how to deploy it to aws so it can be accessed from any place in the world on any device with internet connection to build the app we're gonna be using typescript node.js aws dynamodb react.js and tailwind css and to deploy it we're going to use serverless framework to create a lambda function behind api gateway websockets okay let's get started so the first step as usual i'm gonna talk about the requirements so what you need is visual studio code ide node.js installed on your machine aws account with aws cli configured and a serverless framework installed as a npm global package this is why we need node.js by the way and installation setup and configuration of all of these tools have been already covered by me in my other video you can find the link to it in the description below optionally one additional requirement is the command line a websocket client you can use websocket for example that's going to be the one i will use to test my code right now that we know all of the requirements let's briefly talk about the solution itself so let's start from the infrastructure the front-end bit which is the ui and the back-end bit which is gonna be the websocket server are gonna be separated the front-end is gonna sit on s3 as a static web page and the back-end is going to use api gateway with a websocket events pointed at a single lambda function that's going to handle all types of websocket events the database is going to consist of two tables we're gonna have a clients table that's gonna keep a real-time information about actually connected users and then the messages table that's going to keep all of the conversation history between any pair of users regarding the websocket event types we're going to have a five of them first two are connect and disconnect and these are required by api gateway websockets connect happens when a new client connects to the websocket server and this is when we're going to create a new document in the client's table in dynamodb and this is to keep a connection id something that we're gonna get from api gateway when the new client connects so we can use that id later to send a message to that client and there for the disconnect what we're going to do is to delete that document from a dynamodb a clients table right so when the client disconnects we don't want that client in the table anymore because we don't also want to display it on the list of all of the connected users to the chat and that actually leads us to the fed type of event and that's going to be a custom one get a client this is for any client to receive of a list of all the of the users actually connected to the chat and the last two also custom websocket events are going to be a send message this is to send a message from one client one user to another and get messages this is to get a whole conversation right all of the chat history between two users and this is how the back end is going to work now just quickly about the front end so for the front there's gonna be two views the first one is when the user opens the chat for the first time and then we're gonna display an input box with the submit button where the nickname has to be provided there's not going to be any password so no authentication in this video i might add it in the future in this chat application once you set your nickname write the ones you provided in the in the form and click on the submit that nickname is going to be saved in the local storage of the browser and it won't be possible to remove it unless you manually go to the local storage and just remove it in the chrome tools or firefox tools or any other browser and then once the nickname is set what we're going to show is something like a facebook messenger so on the left we'll have a list of nicknames right all of the users actually connected to the chat and once you click on any of the users on the right you will see the message history and on the bottom you will be able to write a message and send it to the user and that's gonna just it's gonna be sent instantly the same happens when when someone else sends a message to you you will see it on the screen instantly okay so that's the front end and now finally let's move to the implementation so i'm gonna open visual studio code now so here i've got it opened and now we're going to create a new serverless project in order to do that i'm gonna go to terminal new terminal and then here i'm going to type cd projects to change directory to the one where i keep all of my projects and in here i'm going to run a following command which is serverless create template url that's the url to my github repository with the aws node.js typescript template for serverless framework and the path that is going to be the directory this command is going to create in a project directory right and for me this is serverlesschat but you can name it however you want okay i'm gonna hit enter and the project is successfully installed so now i can close this terminal and let's go to a file open folder and let's find that folder that i've just created with that command it should be serverless chat okay gonna click on open and there we go this project is opened and now let me just close get started and i'm going to go to terminal new terminal again and here i'm gonna run npm install command to install all of the packages from packages json right that's gonna create the node modules folder okay done all the dependencies installed and now in this project the newly created serverless framework project we are interested only in two files the first file is serverless.yaml and the second one is in src and that is handlers.ts okay let me start from serverless.yaml this is where we keep all of the infrastructure configuration and here i'm going to first look at functions section where we have defined and default hello handler that points to src handlers that is the handlers.ts file and then hello and that is the name of the function hello over here but yeah on its own it doesn't do anything yet because we don't have any events associated to this a hello handler and what i'm gonna do next is to define all of the websocket events i have some configuration i prepared beforehand so let me just copy paste and that's gonna be faster so instead of having this hello handler we're going to have a websocket handler that points to src handler dot handle so we have a handle function now that means we have to rename that function from hello to handle otherwise this is not gonna this is not gonna work and then going back to serverless.yaml we have defined events for websockets and we have five different routes we have connect and disconnect i've already talked about these websocket events right this is when new client connects to the websocket server or disconnects from the websocket server and these three are the custom defined get messages send messages get clients as you can see they are all associated with this handler handle function so there is only one function all of these events point to and this is that function right we have some example code here how will we be able to distinguish it how will we be able to tell what type of event is this it's quite easy because we can read it from the event itself if we go and check the event on the event we have something like a request context property and on that property we have another property that's called route key and the route key is going to have a string with the name of that route right so if it's connect we will see we will get basically on the route key we will get uh dollar connect right if it's disconnect dollar disconnect get messages and so on okay so that's how this can be defined and depending on that we can have some sort of a switch case statement and then call different functions depending on what the route key is gonna be and that's how i'm gonna implement it but before i start the implementation i'm going to go back to serverless.yaml to define two missing pieces resources which is going to be dynamodb tables clients and conversations and permissions to these resources right because once we have the the tables defined and that's going they're going to be created basically by this serverless framework we will need to give permissions for this handler to access these resources these tables okay let's start from defining tables so for that i have again have some configuration prepared beforehand to speed this up a little bit so let me copy paste it okay yeah that's quite a lot of configuration and i'm just gonna briefly go through it so for the resources we have a clients table the type of the table is dynamodb table and the following properties table name clients provisioned throughput read and write capacity units this is how many operations can be done on that table in a second for read and write i'm gonna go for one because this is just an example project then for the attribute definitions we have a connection id this is where we're gonna keep the connection id we're getting from a api gateway when a new client connects to the websocket server this is going to be required to then be able to send the messages right to send the websocket messages to that client to that connection and the second one is nickname this is where we keep all of the nicknames from our users i have defined both because the first one is our partition key so that's going to be a primary key of that table and the second one is used in the secondary index what's secondary index in dynamodb table just quickly let me explain so with an example of clients table and partition key being connection id you can affect a single document using connection id only so only partition key to get a single document in a constant time this is using a get operation on dynamodb client and that get operation cannot be done on a non-key attribute like a nickname if you want to do that using a nickname without having a global secondary index you need to use a scan operation that gives you all of the documents and then you have to compare every single one of them to find the one with the nickname you're looking for which is not really efficient especially when you have lots of documents in the table and this is where secondary index a global secondary index comes with a help what it allows you to do is to use a different kind of operation on dynamodb client it's called query and that is going to be a constant time of getting a document or set of documents based on that field so basically what we will be able to do is to get a document by a nickname instead immediately without having to do scan like comparing every single one it's just like a query on a relational database like postgres or mysql right so i hope it explains it a bit the secondary index needs some configuration so let's go back to serverless.yaml and let's go through it and to define global secondary index you need to add a global secondary indexes property under the properties for the clients table and there we've got an index name i call it nickname index right because we wanna be able to look for a document using nickname and a key schema is basically it has to point to the attribute that you want to be able to look by and that is a nickname for the key type we use a hush so what happens here is a bit like we're treating a nickname in the context of global secondary index as a primary as a partition key so you can get it in a constant time right you can get the document by nickname and then we've got the provision throughput as well as on the table and i i put the same values right so one operation one write one read per second and the last property is projection and projection type the the one i've got is all what does it do is basically giving you all of the fields on that document when you use the query and you search for something by the nickname so all of the documents returned are going to have all of the fields on that document there are two other options you can have a keys only and that would give you only you know our in our case we only have a nickname and connection id there's not gonna be any other fields but imagine there are two extra fields so with projection type all when you query by nickname you will get a document with all of the fields so nickname connection id and these two extra fields if you have a keys only you will only get the nickname and connection id so if you want to get the rest you need to do additional get operation using connection id and there is another option include that allows you to specify fields to include in the document you have to define them so yeah again uh let's say we have a four fields nickname connection id two others and you want to have only one of them along with the nickname and connection id you have to specify it in an additional property for more details i refer you to documentation you can find the link in the description below okay so yeah let's move to the messages table now and for messages a table the configuration is very similar so we've got table name messages then the provision throughput one and for read and one from write and attribute definitions we've got message id created art and nickname to nickname message id is our partition key right and created at is our sort key so that is new here what is sort key in dynamodb it basically sorts all of the documents by the values of that attribute because as a default all of the documents in the dynamodb table are not sorted they're not ordered by anything so if we want to have them ordered which is the case for the messages table right we want them to be ordered by the time of creation um you have to define a sort key and the key type for sort key is range so that that means basically all of the messages in that table will be sorted again by created art which is a number filled by by the way so that's just going to be some sort of a time stamp and then nickname to nickname this is the field we have a global secondary index for and um notice also that this global secondary index have created add as a range key this is because again as a default this is not sorted right so it's not ordered by anything the i mean the documents in the in the table in the index so if you want to be able to get a document or a list of documents in this case i'm going to explain in a second why it's going to be a list then and you want them to be sorted by something you have to add a wrench key to it but yeah what is nickname to nickname so how i would like to keep the messages in the table is to have them between two users so there's going to be two users let me give you an example how the value of the nickname to nickname is going to look like so let's say we have a bob and john and this is how the value is going to look like for the single message between these two users bob and john right what is important i'm gonna restrict a possibility of doing the other way around so this won't happen right i will sort these two nicknames and i make sure there is only one way of storing these values in the table and basically you know all of the bob john messages with you know different values and different message message ids they will have the same nickname to nickname so if you want to get the conversation right the whole message history for these two users you just need to provide for a query operation you need to provide you know bob hush john and that global secondary index is gonna make sure that the retained documents it's gonna be list of documents because basically query is not just for getting a single document it's because you know nickname to nickname is not a unique field as the you know message id or connection id is in case of a nickname this is going to be a list of the messages so basically that's going to be the message history and again provision throughput one and one and projection type all because i don't want to do an extra operation okay yeah i think this is going to become a clear once i move to the code and i start using this operations on a dynamodb client okay right so that is the resources definition for the serverless.yaml and we've got one more missing section actually two more because there's gonna be environment variable i i have to define as well but let's start from the permissions that should be quick so let me copy paste it somewhere here and that should be quicker so yeah for the role statements that is to make sure this lambda function that is going to be triggered by all of these events will have access to clients table and messages table so what we do here we allow for the for that lambda function to perform put item get item delete item and scan on clients table so that basically gives us the unique identifier of clients table arn from aws and the same thing happens for the query but you see the query is a separate effect this is because for the resource you need to provide the index so that it doesn't end on the table arn you need a rn but you also need forward slash index and forward slash the index name or a white card symbol write the star star sign that means any sort of index so we can do query on any index there's only one index but if i add more this will be possible for that lambda function to to do a query on and then for the messages table it's identical right we've got put it item get item delete item and scan for messages table and we have a query for all of the indexes and the final step is that extra bit of configuration there is an environment variable which this lambda function will have access to the name of the the variable is wss api gateway endpoint and it is needed to send websocket messages to clients to connections i'm going to tell you more details about it once i start the implementation of sending this websocket messages so basically when i get to this in the code right so that's it in terms of infrastructure configuration i'm not really gonna go back to this file except for maybe extra bit of explanation on this environment variable um so yeah so let's move to the handle sts now and in the handlers.ts i'm going to focus first on the route key that can have one of these values from from the server is the diamond from events so this can be connect disconnect get messages send messages or get clients i mentioned it before and what we want to do first is to handle that value and depending on that value do something else maybe call a function so i will have a function for every single type of event and this function will be called depending on what value of the router key is so first of all let me define a typescript type so we have a so we have this nicely typed not like this that's just going to be a typescript type i'm going to call it action and this action can be one of these values again so we will have a connect or disconnect or get messages or send message or get clients so that's gonna be the action and for the route key i'm gonna define a new const var let's just call it route key and that's gonna have assigned this value from the event as action so when i use the drought key for example in a switch case statement it's gonna be easier to handle values so when i have a case and i try to open a double quote as you can see intellisense tells me what kind of values this can be so let me handle all of those um yeah actually first we want to focus on connect disconnect and get clients i'm gonna implement these event types first and handling these events so let's do connect and then for the connect let's create a function let's call it maybe handle connect connect and that handle connect is gonna take something like um yeah let me just think about it in a second so what we know for sure is that we want to return api gateway proxy a result just like that so i can return it straight away so that's what we're gonna have handle connect just like that okay yeah for now this one and let's make a default one but let's not return 200 because if there is if this is a different action that that means we have something wrong with the infrastructure because it should never happen should have never we should never have a value different than one of these according to the infra configuration however if for some reason it happens let's just written a 4 or 3 or even maybe it can be four or three without any body because it doesn't really make sense i think so we'll just um you know what 500 it seems to be more elegant nice okay so handle connect and now let's think about what this connect is going to need so we know we don't need to pass the route key because yeah that's that's just to to choose the right function but then what the the connect handler what it should do um again as i explained in the solution we want to create a new document new record in the client table right this is going to be the table with the real time data on actually connected clients and this is where um that new client has to be created so so what we're gonna need to do is to get a connection id and that is something i can also get from here that's event request context there should be something like a connection id which might be new according to this type or it can be undefined actually according to the type specification of aws lambda package but i know this is going to be a string so i'm going to change it to string and that's going to be the first argument required by handle connect okay then the second argument is how we going to capture a nickname and with web sockets we could use headers but i know it's a bit problematic to pass headers custom headers we've built in browser websocket client i'm not sure actually sure if this is even possible so instead of that i'm going to use query parameters or query string basically when someone connects to like when the client connects to the to our websocket server that that client open connection is supposed to pass a query parameter so you know in the end this websocket url is going to look something like that so we have something like aws com or something like this and what we're going to require is a query parameter called the nickname something like that and if it doesn't exist we won't connect that a client because the nickname is non-existent and we we shouldn't allow for that right we need to have a nickname so um let me just capture that so for that um i think the easiest way is just going to be passing that query parameters that we have query string parameters that we have on on the event and the type of it is api gateway proxy event query string parameters so that's going to be the second not like that something like that so query params api let me just gateway event uh do we have anything here and not yet so apa gateway maybe we've got something like a query there we go okay and but this can be null i think as well yeah this could be known for some reason i guess you know when no parameters have been provided so yeah so basically these two are what we require and then in the handle connect first instruction that we want to do is a check on if these parameters exist because if they don't or if these query params don't have a nickname this is where we want to just return a status code and this time this is going to be 403. so we won't allow this client to connect to our websocket server otherwise if we have that nickname then we can establish the connection but before i establish the connection which is very simple you just need to return a status code of 200 and empty body and then you know that that's just gonna establish the connection the api gateway is gonna handle that for us but what i would like to do is to capture connection id this is going to be required to be able to send a websocket message later on like you know when the the clients exchange messages or the client asks for the client's list of things like that we need to have that connection id to be able to send the data to that client and this is the purpose actually of this clients table so let me create a yeah i need to import aws sdk first aws from aws sdk and this is where we need to initialize dynamodb document client so i'm going to create something like a doc client instance which is going to be new aws i think it is dynamodb and document client exactly you don't need to pass any parameters just document client instance creation and then on this dock client this is where i'm going to i use a weight though because that's going to be asynchronous operation a dock client put this is the method a put item that creates a new document a new record in dynamodb table it requires some parameters one of them is table name which is clients you probably remember from here table name clients that's what i'm going to use and then all we need to provide i think it is an item yes so this is the item the actual document that we want to create and this is going to contain a connection id something that we already have let me check if this is aligned with yet connection id and the second one is a nickname so that's just going to be something like nickname and that is the query params nickname so yes that should do it but let's not forget about promise method this is an aws method by the way this is the way of turning this callback approach which is a default approach is a callback approach for the put method you need to provide a callback here that is going to be called once the operation is done but you can turn this operation into a promise by using a promise method what i've done and that's why i've got a weight in front of dog client put because this whole expression now is a promise so it's gonna it's basically more convenient for me to handle that instead of you know having callback and then probably another callback if i have more operations there okay anyway so that creates a new a new client and once this is done so i'm gonna assume this is all fine and i will return a status code of 200 and empty body so the handle connect is finished if that face for some reason then the status is going to be 500 and the client won't connect and then the log message is going to appear in a cloud watch nice okay so that's the handle connect let's do a disconnect now and for the disconnect the code is going to be similar except for the case where instead of creating a new item a new document i'm going to remove it right this is supposed to be a real-time data so real-time clients so for that i am going to create a handle disconnect first you know what to speed this up i can copy this and paste and then just change this to disconnect just like that but we don't need query patterns there's no need for nickname because the client already exists we just need connection id and then the doc client is gonna be is gonna be doing delete on a clients table and there is no item that in the options there is a key instead and that key takes an object that requires a partition key which is a connection id so that's how we're going to delete that client after that yeah again status code 200 and body that is empty and yeah this is essentially it this is how to delete it and yeah perform the disconnect one more thing actually two more things the first thing is to add handle disconnect where we just pass the connection id and the second thing is the duplication that we've got in here don't we so we return exactly the same message same object right the api gateway proxy result and is there any other duplication yeah there is other duplication which is a table name i don't want to repeat myself so many times especially you know if in the future there is a change to the table name it is going to be a bit painful so i'm going to define something like clients table name let's make make it looking like a constant like an actual constant and this is this is gonna be what i'm gonna use instead of clients value here and there then for the okay response let's create something like a response okay and this is where we gonna have 200 and body so just a smaller factoring and then this is gonna be a response okay and this is also response okay nice okay so we have a connect and disconnect let's do let's do get clients now remember there is still still one more functionality that needs to be added to connect and disconnect i mentioned it before basically when a client connects we want to notify all of the clients already connected about this new connection this new client connected same happens for disconnect but i'm going to do it right after i do get clients because these are very related it's basically same thing but instead of sending the all the clients to one connection we need to send it to all of the connections all of the clients let's so let's do the the get messages fast so yeah let me just start from case get clients and again what we want to do is to create a new handle function handle get client and that is going to take a connection id as well and yeah that's that's actually gonna be like that right nice okay so why do we need connection id can connection id is needed to send a message back to that client that's asking for the client's list okay but yeah first before we do that let's do the dynamodb operation so this is where i am gonna use scan so let's do something like clients equals away doc client and scan which is going to happen on a client's table name remember to change that to promise and yeah this is actually not going to be clients but output it's not that simple and then once we have output from the resolved promise we will get it as items okay so let's do items or an empty array why i'm doing this is basically because items might be undefined for some reason i guess this might happen if there are no items in the table so just to be on the safe side because you know this type this type indicates that this there might be undefined value so if there might be undefined let's handle it by setting clients to an empty array because you know if items are undefined i'm just going to treat it as an empty clients list and having those clients this is where what i need to do what this function needs to do is to send this data back to the connection that is asking for it or a client right you see these are these are used as equivalent of each other right connection client really are the same thing here so to send something back to connection we need another class an instance of object called api gateway management api but you know what let's just call it api gw and this is aws.org gateway i think it's the api gateway management api but this one contrary to document client it requires us to provide an end point and what is an endpoint endpoint is what i have defined in serverless.aml i told you i'm gonna talk about this and explain so this is our wss api gateway endpoint and that's essentially url to our api gateway but since it's not created yet i haven't deployed anything we don't know under what url this api gateway is going to be this websocket api gateway basically how can we get that value is by using this sort of syntax with ref and that you just need to provide web sockets api so that's gonna give you this unique hash and then dot execute api dot region that really depends on what sort of region you set in your configuration as a default is us east one and then amazon aws.com and stage which is as a default is death what stages stage basically allows us to have different environments like depth staging production and so on but yeah with this sort of syntax um yeah join basically let me explain so join here is really joining all of these values into one string by an empty string so this is just you know concatenating it and what we're going to get in the end is just a url to api gateway and that is going to be an environment variable so in here i can just use process dot and and just pass that value over there and that's it that's how you configure that management api to send something to that connection to that client so having this set up i can now go to that and use that which is going to be post to connection post to connection method and this method requires connection id something we already have and the second parameter is a data and what we want to post is just the clients right um you know what let me just confirm if this can because this is an array of objects let me just confirm what is the data type of data data buffer you in a right blob or string so yeah it cannot be like that we need to stringify it stringify clients because yeah it's better if it's string we can then pass it on the front end and handle it as object nice right let's return a response okay and that's how can't get clients is going to work and get clients connection id good good good some error handling to be added there though so one thing that is important about posting to connection is the fact that we may have a stale connection in our table that might be the case where the client connects and it's it doesn't really disconnect just stop it just stops responding without properly disconnecting from the websocket server and in that case this method is going to throw an error which is a very specific error with 410 status code i think it's called state connection or something like that i'm going to find out in a second and because this might happen what we want to do is to handle that error without throwing another like re-throwing this error we just want to stop that from failing and removing that connection because since that connection is stale we cannot send anything to it anymore we just want to remove it from our real-time clients so i need to add a try catch statement basically wrap this statement into it and then let me check what sort of error this is going to be i've got documentation here oh okay this is just going to be status code 410 so really what i need to do is to check if e as aws error has a status quo another stack status code of 410 and if this is the case what we want to do is to use a dog client again and delete basically let me just copy this code we're gonna have some sort of duplication again so maybe a little refactoring would be nice okay so that's what it does it just it just deletes it and otherwise yeah that's just gonna return do we want to return we don't really want to return we just want to check if this is not yeah let's just turn it around so if this is not 410 let's just throw an error otherwise let's just delete it that connection id that we posting it to nice okay yeah so that's just gonna proceed to to written response okay you know this might not be the case for handling get clients i'm just thinking and this is because when this is very like unlikely to happen if we have a client that is asking for all of the clients um it's very unlikely that it disconnects by the time this message is handled i'm just thinking but you know what i'm gonna i'm gonna proceed with that because we're gonna need that code to handle posting to all connections notifying all of the all of the clients which is going to be the next step so let's keep it here this probably is not going to happen for this particular case but since uh from the beginning i was thinking about having a separate function to post something to connection i'm going to keep it like that because that's just going to be a single way of handling posting to connection in a few seconds let me just let me just build a notify functionality first so again yeah for handle connect and disconnect what we want to do is to sort of send like do a scan to get all of the clients and then to post to all of the existing clients this clients list that's what we want to do when connect or disconnect happens let me create a function for that because that's definitely going to be used in these two places so let's create something like a notify clients function and for this one we don't really want to take any parameters i think or maybe we actually want yeah we want to do that i will explain you in a second we need the actual connection id of the client uh maybe let's call it excluded excluded a connection id to exclude let's call it yeah naming things is always difficult exclude connection id to exclude that will be the name right and as you may probably already know based on that name when we notifying the clients when client connects at this point of time where we want to notify all of the clients about the new client which is right before this actual client connects it won't connect until we return okay response so technically this client is still not there it's still not connected but we still want to notify all of the other clients already connected so that's why i added a connection id to exclude because i don't want to send a message to a connection with this connection id it doesn't exist yet technically at that point it's gonna it's gonna exist after the response okay return that's handed by api gateway by the way also for the disconnect if you think about that you remove you're removing that connection you're removing that client so when you notifying all of the rest of the clients you don't want to notify this client with that connection id so again the the subject here the client who initiated this operation is is not supposed to receive that a message about the change so that's the point of it okay so notify clients needs a connection id to exclude and that's going to be the actual connection id of of the client that initiated this action this event so yeah i mean you know we can take null if we want to really notify everyone but that's not going to be the case for handle connect and disconnect nice so notify clients this is where we will have a common code with get clients because first of all what we need is to get all of the clients and this is what that code does so instead of doing a copy paste here what i'm gonna do is to create another function let's call it get all clients and let's just do something like that yeah then return easy we don't need that deadline and this will just become a way to get all clients nice so we can reuse that in here get all clients good okay now the difference is for handle get clients there is only one connection we posting the message to for handicap clients is that connection that is asking for it for notify clients is all of the connections all of the clients except for the client with connection id to exclude so what you want to do is to iterate over all of the clients except the one that should be excluded so connection id to exclude should sorry that is wrong that's not how you do filter that's how you do filter so there is a client dot uh we don't know what the type of client is that's because we don't really have a type of client defined let's let's let's define let's define it so you go to action and now i'm going to add client and we know the client has a connection id which is string and nickname which is also string as you remember from here so get all clients is just returning a promise of client array that needs to be cast to it some issue with that though and i it's not assignment to type client uh you know what i think the issue here is with the oh we're not able to pass any template argument to the scan so it looks like i need to handle it differently so the easiest way is just gonna be something like that yeah typescript so let's go back there and now we have a nice intellisense so if the client connection id is different than connection id to exclude this is where we want to do a map and that map is going to happen on the client right let me explain what this code does so this filters all of the clients from the clients array that have connection id to exclude which is going to be one client so basically after executing this expression we will get an array of the clients without client with a connection id to exclude and then map is just gonna run a function on every single on every single client and what we want to do for the map is what happens in in here so that's this whole logic and yeah again i don't want to duplicate myself so i'm going to extract it out to a separate function that was my plan from the beginning by the way post to connection that's why i'm going to call it asynchronous function so some changes needs to be done to the map and what does it need that needs a connection id and there is a string and it needs data let's call it data and let's require a string so this bit is just gonna be a data and i will move it to there so this will be removed and instead of that i will do await post to connection then connection id and data is just the message that we want to send so that's how positive connection is going to look like nice okay okay and now going back to notify clients don't worry i will go through that code in a second step by step again so everyone can catch up on this okay i need to turn it into asynchronous function for the map and that changes the whole table that is the whole area sorry the whole alright is going to be returned from the map to arri of promises and because this arrive of promises to await for the results i need to wrap it all into promise all and await for the promise all not sure if you're familiar with this trick but essentially that's gonna run all of these functions for every single client sort of in parallel they not really really in parallel because the node.js event loop doesn't really do parallelization but um they they will go in the random order basically so you're asking clients and that client is going to be this is where we're going to do post to connection and what we want to post is client connection id that's the first argument required and the data is json stringify and this is where we're going to pass all of the clients so that's what happens essentially so that's how notify clients work yeah if you are a bit confused about this syntax you can do equivalent of it with just the four on the on the clients so we can do something like a client const client of clients and then if client connection id equals connection id to exclude you don't want to you don't want to continue with the code just you want to go to the next iteration otherwise you just want to do a weight and this is really what you want to do so that is the imperative approach to to that code that does exactly the same thing as as this one but they not like in parallel this is gonna be just sequentially waiting for every single post to connection so so yeah this is more parallel basically and i'm gonna go with this with this option okay let's add this function execution to to handle connect so this is where we do notify clients connection id to exclude let's wait for it and we want to do the same thing in disconnect so wait notify clients and connection id cool yeah okay i think at this point i'm gonna go through the code step by step so everyone can catch up on what's going on here then i will do a simple test so i'm gonna deploy it do a simple test with the cli with the command line websocket client and then we're going to proceed to sending the messages and getting the message history so let's start from the beginning and the beginning is the handle function because this is the starting point this is what api gateway is going to execute um and now when we have a client on the front end the client which is going to be the browser and that client connects to websocket server this function will be called with an event that has a request context connection id as dollar connect and when this happens handle connect is going to be called with connection id and query string parameters in the connection id we're checking for the query params these are the query string parameters this is because we require a nickname something that our client has to send from the browser as a query param called nickname if that param doesn't exist we want to return four of three status code forbidden we don't allow this client to connect because nickname is required then otherwise if the nickname has been provided what we want to do is to create a new document in the clients table with the connection id from that client that is trying to connect and the nickname captured from the query params from the url to the websocket a server that this client provided and trying to connect to again and once this record is created in the table we notify all of the other clients so not that client that is trying to connect order already connected clients if there are any about that change and and this is because like and because we've just created this new client in the client table it will be included in the clients list this is what the get all clients does it scans entire clients table and retrieve all of the records from there then return as a client and that's what we assign to the clients variable in notify clients and then we iterate over this client's list excluding connection id to exclude so in the context of client that is trying to connect that client that is trying to connect is going to be excluded from the list of receivers right of that message that we want to send and the message that we want to send it happens here in post to connection is this client's list and how the post to connection works if we look at this again we use the api gateway instance this is the api gateway management api we use post to connection method to the connection id so you know this is going to happen on all of the clients so all the connections and we send the data this is more generic here so we can use it in many different contexts and the data we send in a context of notifying clients is the client's list then this try catch statement here is to handle a stale clients so these are the clients that didn't disconnect in a correct way so they're sort of like still pending they're still on the in the in the table but they they not really physically connected anymore and this is when api gateway management api post to connection method throws an error with the status code of 410 this is a state connection i think it's gone actually the status code is gone and if that happens notice that we check if this doesn't happen if this doesn't happen this is a different error and we want to throw it if this happens we just want to make sure our data is real-time data the data in the clients table and we delete that from the table so that that this is what it does this is what post connect post to connection does so yes so going back to the beginning disconnect when disconnect happens handle disconnect is called with connection id and this is where we delete that client from the clients table to be in line right client is just disconnecting we don't want that client to be in a client's table anymore and we also notify clients about this change so now the list is going to be without that client that is disconnecting and finally the last the last one the last event type get clients this is just to handle a single client asking for the clients list and the handle get clients function is executed which is basically what it does it gets all of the clients again this is gonna be the client's list and that just posts to that connection that is asking for that piece of data it sends the the message the data this clients list and just written response okay this is a default success message for the websocket server with api gateway so yeah so this is how that code works um yeah additionally we have a default action which is status code 500 but this should never happen okay let's test this code before i i move to sending the messages and now to deploy this code we want to go to terminal new terminal and type in serverless deploy and let's see what happens if we have any issues oh there is an error it complains about cannot locate handler src handler not found right so this error is telling me that i have a mistake in here and i i do have because the file name is not handler is handlers so let's try again now it seems to be correct so yeah i guess it's gonna take a few minutes okay deployed here is the end point that we are interested in and that's that's the one i i want to test with websocket with the with the cli websocket client so let me just connect a minus v helps me with getting the it's a verbose mode so i'm going to get more information yeah let's try without nickname fast it should fail the connection should fail basically um and also let's make it a slightly bigger so yeah let's see what happens that's what we got right we've got unexpected status code 403 forbidden which is correct because i didn't provide a nickname let's make it right now so nickname equals thomas and let's see there we go we are connected but there's only there is only one client at the moment as we can see we didn't receive anything and that's also correct because the client that is connecting is excluded from receiving a client's list right you remember the excluded connection id to exclude and this is why we have get clients by the way so what i could do is to read is to send a message websocket message to our websocket server to get the clients i think the action is the field that that that tells the the websocket server what the action is this obviously it doesn't apply to connect and disconnect is just for the custom for the custom types of events let's let's see what happens there we go so what we received back is the that document that record from dynamodb table nicknamed thomas connection id is just this hash over here or base64. okay okay nice let's test it with another client so what happens if i add another terminal and let's maybe do something like that so yeah so we can see both of them nice and let's try to connect again with a different nickname okay so that new client connected nicknamed joe and we can see over here we got a new message we've got this is essentially new clients list nickname joe connection id and nickname thomas what happens if joe disconnects again we received these messages nice okay so that seems to be working well i see a one edge case for that we don't really have anything implemented to prevent someone impersonate impersonating other person so imagine there is someone with one nickname connected and then someone else tries the same nickname we should prevent this sort of situation so what i wanna do uh maybe first of all let's test what happens actually if i do that so if i do this and connect us thomas and i try to connect us thomas again then we basically have like two yes we have two connections but the same nickname this may cause some issues when sending the messages we don't really want to have a multiple connections i think um yeah this this is probably an unnecessary complication at this point i mean it is possible like if you think about messenger the facebook messenger you can have messenger or running on multiple devices or in multiple browser windows and you see sort of like okay you can receive like you can receive all the messages and send all the messages but that introduces a slightly more complication to the project because now i'm dealing with the list of connections instead of one connection so for this particular project for now i'm going to restrict that so i won't allow for someone to connect on two devices okay so let me just add a code for that so i'm going to disconnect um and before before i close the command line i see there is one more thing that could be improved and that is the message itself at the moment it just gives us a list of messages it doesn't tell us what type of message is this that would be nice for our client on the front end to be able to tape to tell what type of message is this so two improvements to be done one is one is to handle this edge case where there is a someone with the same nickname or you know the same person trying to open the the chat with the same nickname in a second browser or second browser windows second tab something like that so um let's handle that first this is gonna happen in here so what i wanna do is to check if there is a connection with the same nickname as the the ones the one provided in query parents okay and in order to do that i'm gonna have to oh yeah that's gonna be slightly different because what i have to do let me just show you so i will have to query clients table by nickname so this is where i need to use a secondary index defined in the resources as you remember that is because i instead of you know getting a client by connection id i want to do it by nickname which is not possible like i said before it's not possible to do it with a get operation because nickname is not a primary is not a partition key but since we have that defined as a global secondary index what i can do is to run query and for the query again what i have to specify is the table name and then some extra properties and these properties are index name that is exactly the same as the one defined in serverless.yaml then we need key condition expression this is the the expression for the comparison so it looks more like that so you just you're supposed to provide the field name in our case this is a nickname with a hash at the beginning and then the value there is a nickname with a colon at the beginning you essentially you can put anything in here if you use hash and anything in here if you use a colon this is because this is this syntax allows you to swap that values with whatever you want let me just show you these are just placeholders basically but yeah one second let me just define both of properties and this is gonna be our query params nickname and that has to be promise okay i think this is it yeah so you know this is this is essentially recommended syntax from aws documentation um this is recommended because sometimes the the name that you use for the field is somehow restricted by aws and it's not allowed so you cannot do something like nickname stride away and instead instead of that they recommend to use placeholders so this hash nickname is just placeholder i could i could call it n for example and then as long as i have this in line with the expression attribute names that's just going to be fine because what's going to happen it's going to swap that a hash n with that value and the same applies for a column nickname this could be anything again and this is essentially swapped with value of this expression attribute values okay i think this is this is clearly enough so so we're swapping that into that and we're swapping that with that which is the value for the for the nickname okay and then for the output what we going to receive um let's actually see what we're going to receive i'm not sure what is there okay it's items so yeah it's multiple items because there might be multiple connections multiple clients with the same nickname might be according to the types but we don't want that that's why i i'm gonna check it so let's just have let's just do something like like that maybe if output items and output items length maybe actually have something better do we okay this is just based on count so if count is greater than zero um something oh it's possible undefined so if it exists first and is greater than zero that means there is already a connection for that nickname what we want to do then um we have a few options if you think about it but the option we want to go for so one option is to disconnect existing client connect the new one that's not what i want to go for the second option would be [Music] yeah i think the ideal option is to verify if that a nickname is already is actually connected so we can send some sort of a ping to that client and if that client is really connected then we're dealing with impersonator and for that impersonator we just want to return forbidden so if the count is greater than 0 that means we will get a client on the output item 0 i think if i just go with sort of oh yeah i need to deal with that so let's just do something like this okay because i'm i'm sure there is a one a client there based on based on that condition and that client for that client we want to post to that connection so post to connection client connection id and we want to send a message let's send something like type pink and if this is fine then we we won't actually get anything i'm just thinking because that handles the state connections yeah let me tell you so so the point of this what i what i was thinking about is that if the post to connection succeeds that means we do have a connection already we do have a client with that nickname already connected and we don't want to allow for this new connection to be established written forbidden if that post-connection fails with a connection gone 410 this is where what i would like to do is to go ahead and create that a created client so just allow for someone to to connect with the new connection because that might be the same person but just new a connection and the last connection is the last sorry that this new client right with the new connection but the last connection is state yeah it was correct so so you know what um you know what i will go to post connection and let's just add an extra an extra bit here let's make it return a boolean i promise that resolve to resolves to boolean so if that if that is fine if that was okay we just returned true but if we had an error this the state connection error this is when i return false so with that i can have another condition over there and what i could do essentially is to check if this is not right then i return 403 and again because 403 is already there i think i have too many brackets or not enough brackets yes it was not enough brackets yeah this is this duplication so let's just do the same thing i did with with the response okay let's call it response forbidden response forbidden that's what we return this would return if this is the case so yeah let me explain it to you again because there are two cases i see two cases with i'm trying to prevent from happening um i think this would be more visible on the front end so i might show you this again once we have the front end bit finished on that but yeah imagine one case where we have a client connecting to the chat let's say it's tom that's the nickname client connected to the chat and then for some reason that client lost internet connection or something like that and didn't disconnect properly so that connection is still that client is still in our database in our table and now he tries to to connect again he just opens the chat tries to connect again a storm because the client is already there what we want to do is to make sure that this person is is not impersonators so it's not someone who tries to connect to an existing nickname that is already connected i mean we we cannot really prevent impersonators with this design there is not there's not going to be any passwords but still i want to restrict um that you know in the real time there should be only one person with a given nickname at the time so if that happens um if there is impersonator and someone is already connected and that connection is active then that ping is going to is going to ah this is actually going this should be all the way around so good that i explained it so if that is successful we can ping that connection and that connection is active we don't want to proceed for the other client to connect and then for the case where we have tom trying to connect again after being disconnected not in the correct way this is when the ping is going to fail and because ping is going to fail there is no active connection at the moment we're going to allow for that to happen and that a client is going to connect cool so there is one one one thing that i improved an edge case that i handled and the second improvement is gonna be on the message itself for get all clients uh sorry that is on the handle get clients and notify clients so post to connection that's not what we want to do we want to do something like like that so let's say this is a type clients let's just call it clients because we're getting the clients and the second attribute maybe let's use a let's use something like that so we will always have a message having a type property and a value i think that makes sense and let's wrap it into another object so yeah that is gonna be the format of of getting that um you know what because this is going to be the same for going through yes so this is the same for notify clients we also want to send the same type of message let's create a function to um maybe let's have something like create clients message and that's going to take a client client and it will return a string so that essentially is going to handle that bit with json stringify so we have that in one place create a client message and let's just pass the client in there and that's just gonna return this json stringify yeah so easier to handle those because that is exactly what we have to do in the notify clients as well yeah so now we should have a better format for our clients message so the front-end should be able to recognize it based on the type of the event like the case with the pink great um let me deploy it again so i'm gonna go to terminal uh i actually have a terminals two terminals opened here so let's um let's do you know what let's use the new terminal to deploy it and then we use the the previous terminals to test that new code okay deployed let's go back to these terminals and let's just try again so first of all i'm going to connect a stomas connected to web sockets then let me just verify if get clients if this is in the right format nice so now we get type clients value clients and all of the the clients there and let's see what happens if i connect as myself again good so we see ping and we've got 4 or f3 i'm not sure how could i assimilate not not disconnecting properly i think i would have to wait 15 minutes for for the for the apa gateway websockets to kick me out i think that's what's what happens uh and then like this is what happens after 15 minutes as a default it kicks me out and then i'm not really disconnected so that would be one option but i might not do it on this video um but you know what let's just see if the other message format is correct so let me connect us joe so that's what we're gonna do instead all right yeah we see there is a jaw here and now instead of disconnecting joe i'm gonna disconnect thomas and i have here a new list just with the job so yeah that works well let me disconnect now and i'm going to close this terminals nice so we have the basic functionality for connecting disconnecting getting clients list and notifying all of the clients about the change about the new client or a client that's just left so that is that is done now sending the messages and getting the message history this shouldn't be more complicated than what we already have implemented but yeah let's see cool um let's add another handle function so i'm gonna start from case and let's think about it which one is better to have first i think send message is better yeah send message seems to be better at the beginning always like creating new things new documents records in the table let's create a handle function so let's have something like handle send message and yeah let's think about the format of this so what we know is that we want to return promise apa gateway approximate is out but now let's think about the input data of a handler sent a message yeah this is probably where we need to capture the body from the from the event itself this is where we want not just the action as i i demonstrated with getting the clients we also need some extra fields defined by our client that's going to be on the front-end side feels like a target client or message itself right the fields that we can get from event body again so this property on the event that is a string or null but we can we can use json and you know require a json format from the front end from our front-end client and then parse it here on the back-end side and that's what i'm gonna do so you know what let me first define a type for it so let's use something like for the name let's use something like send message body i think that's good idea so let's do something like send message body and let's add two properties there so one property is definitely a message itself that's gonna be string and then the second one let's add the recipient a nickname we could potentially use the connection id but i think nickname is better in this case because connection id is something that is temporary if you think about it right connection id is created when new client connects then we add it to the clients table but it can simply disappear when client disconnects and when that client with the same nickname connects again he or she is given a new connection id so you see that connection id changes so i wouldn't rely on a connection id let's rely on the nickname instead and that's also something that we're using in the message history in the messages table we have a nickname to nickname field because you know connection ids are not are not stable basically they ephemeral right they they they just you know appear and disappear and they just temporary so yeah so send message body i think with recipient nickname is better right let's validate that now so um maybe over there i'm just going to create something a function let's call it a parse send message body and that is gonna take a body which is a type of string or a null i think it's even new or undefined and that will return send message body so that's what we what we what we expect to get from it so we for sure we're going to get from from it or an error that's the idea and for that this is basically going to be called from there from handle function if the route key is sent message and let's just maybe let's just do something like body const body equals pass send message body i just want to show you how this will be called and we pass event body so that's the idea so any any message body in a web socket message is gonna be on the on the event body so that's how we how we get that from there from the front end nice and let's implement the logic for parsing send message body yeah this is going to be straightforward because what we what we can require from the front end is a json format so that's that's what i'm going to require a json format and let's just do something like a send message body let's define a const var send message body and let's use json parse on the body or an empty object that is the first step let's change the type of it to send message body unless i can and now i can't really use the generic parameter for parse so yeah that's how i have to do that and we can just return send message body but that's not everything i'm gonna add some validation because you know there is no validation on the typescript side of things and um written type does not include undefined for some reason but yeah let's let's handle it with the if statement first so if there is no send message body or there is no oh hang on that's gonna be we want to check if it's string right so if string what was the method and there is no method i think i have to do type of so if type of send message body message is string is not string basically it's not string and the same applies for the other property send message body recipient nickname so if any of these is not as it's supposed to be yeah that should be different than string yeah now it's better and if any of those is is wrong is incorrect this is where we want to throw some sort of an error and maybe let's create our own error for that so let's do something like um yeah class handler maybe handler error is is the best yeah i need a specific type of error because i would like to give the feedback to to the client to the front end if there is something wrong with the message itself so that's why i'm gonna use a specific type of error and i'm going to um pass the message maybe incorrect send message body type maybe something like that just to make it simple and if this error is thrown what we want to do because that's called here i'm gonna wrap this whole thing in try catch statements so we'll have like a one common error handling for this type of these are really like a bad request errors if you think about that but you want to send back the the feedback to the to the front end so yeah that's the unreachable code detected is it true okay oh that is because of default i know that should be fine anyway um let's just let's see maybe i just messed something up yeah now it's correct and let's check if the e is instance off in stands off and that's going to be handler error and if this is the case i just want to post post to connection that's the name of the oppose to connection and i'm just going to use that connection id [Music] so the sender that's going to be the sender in case of send message it's going to be the sender connection id so we're just sending back the message of the error i think that's what i want to do and that's it um then let's just do a return maybe uh do i need to oh yeah i need to return that's gonna be response okay in this case because we just want to post something back and in case it's something else let's just throw that error so then it's just gonna yeah that's just gonna probably disconnect from that from that websocket server i guess okay so yeah so in case uh anything is incorrect with that message we're just going to handle it nicely we're just gonna tell the front end hey this is something wrong with the send message body type or format maybe that's what i what i say nice okay so we got we got this body and that body is going to be required in in handle send message so let's require a connection id maybe let's call it sender connection id and then a body which is a get a message sorry that's not good messages send a message send message body nice handle set message and that's really what we want to do just return and send message then the first one sender connection id so that's the connection id and the body is this body but we don't really need to use that const const bar anymore we can just do something like that right away we just pass the past send message body so we make sure that this is a correct format this is really aligned with the type we say the body is good 100 message so yeah now what we want to do is to perform i think it's going to be two operations really maybe three because we need to get some extra data from the from the database from the tables first but yeah yeah the general idea for sending the message is one to create a message in the messages table that's for the message history and two is to send that message to the recipient connection id straight away that should be real time so these two operations is something we want to do let's start from creating the message history entry so that's gonna be uh what we what we need for that is um yeah maybe i just do something like that const i i don't think the output is needed though so yeah it's just going to be await.client and we want to do put and then what we're going to have is a table name and that table name is the same table as the one defined that so that's messages then apart from table we will have an item and that item has a several properties we've got message id created art and nickname to nickname let's start from message id message id this should be something random this should be something random we just adding a new message um we won't get it's not like the case with the connection id where we getting connection id as some random hash here we have to generate that random hash so let's use some sort of library to do that to have a really unique value so um yeah i just opened the terminal so terminal new terminal and let's install it so i'm just going to do npm install and that is going to be uuid that's the library i'm usually using for that that should automatic automatically add an entry to packages.json okay installed let's just confirm it is there yes there it is so we've got that i can close the terminal now and let's just start typing v4 v4 can we get it now okay i need to manually import it so let's just do ah is it because i don't have the types this is because i don't have the types of course i need to open a terminal again i have actually too many terminals yeah that's the latest one and i need to install another another package which is the types for uuid this one requires a separate package with types for typescript okay so let's have a look at the packages json now and as we can see yes we've got types for uuid and that should allow for the intellisense to use v4 when i start typing v4 i get v4 straight away i press enter this is a function that just gives us a random hash in uuid v4 format and as you can see that's been auto imported by my visual studio code so yeah so that's the message id now the second one which is created art and that's just the timestamp really so i can i can do new date and merely get milliseconds i think that's what that's what i want to get yeah get midi seconds then nickname to nickname is the one that we are interested in and there is there is one more which is the message itself if you think about it nickname to nickname so for this one we want to have these two nicknames the sender nickname and the recipient the nickname how can we get the sender nickname we've got a sender connection id so i could do something like that cost output equals away the dock client get that's how we can get it and for that we need to pass a table name that is a client table name and then the key based on connection id we just have we just need to pass sender connection id and at the end i always need to turn this into promise if i want to use async await a syntax cool so that's how i i'm gonna get output and then on the output from the output i can maybe let's call it sender client sender client equals output dot item and this is going to be our client which i think i have a type defined already so i can do i can just do something like that as client so that's the sender client and on the sender client we can essentially we can essentially i'm going to explain in a second why i use array but here we can access a nickname property on that client then the recipient nickname is on the body we've got recipient nickname so these both these both i've put them into an array and then i'm going to sort them and join by hash because that's the format we expect in nickname to nickname as i explained before why i'm doing this like that is to make sure we never have b and a so we don't have this two set of messages in message history where we have a messages between a and b and b and a with this one i make sure there is always a and b so if there are two users involved the all of the conversations all of the messages between them they always gonna be under the same nickname to nickname so this is what that code and that design of a table makes sure um it it is gonna be um yeah so um yeah uh maybe one yeah maybe let me explain it better so um on an example again so if we have a john and bob john and bob this code this code over here makes sure this is sorted by name by the nickname so bob is going to go first then john and they will be joined with the hash as a string and then if we have a situation like the other way around let's say the the sender client is now bob and the recipient client nickname is john then again this is going to be sorted but because it's already in the right sort in the right order the bob is fast because b right alphabetical order j is after b so that's how it's going to be it's always going to be the same right regardless of who is the the sender and who is the the recipient of course we also want to retain that information who was the sender i i'm gonna add it it is important so we can properly show it on the on the front end so don't worry about this if you just if you just if you just thought about it so yeah after nickname to nickname let's add the message itself so that's on the body and then finally that is gonna be our recipient but i think it's better if we do it all the way around so we will keep a sender we add sender to messages instead and nickname i think it could also be recipient if we really wanted to but i'm just going to go with sender because it feels slightly better for me to use the sender so now we have all of the information retained in the in the messages in a single message document which is a message id created art nickname to nickname so these are these two nicknames the the conversation is happening between and the message itself and the sender so we know who sent that message without that we wouldn't be able to tell who sends a message to who so it would be a bit difficult to display that on the front and it wouldn't be possible right so that's the first step to create a document in messages table now the second step is to send that message to that recipient client if that client is connected so because we've got recipient nickname only we need to get a client id for that we need to check if it even exists so this one is a query operation on a doc client so what we can do by the way what we can do is it is actually important because i don't need to write it again i've got that code already in the handle connect if you remember that is to make sure we don't have a duplication with nicknames so yeah this is this is where this logic could be different if you wanted to allow for a user with the same nickname to use multiple browsers or multiple devices because then that would give you like you would have to remove that code basically and then from the handle connect and that would give you at least an array of clients and then you know basically you need to send a message to all of these clients what i'm gonna do is to just keep it like this but for the sake of not having a duplication in my code i'm going to extract it to separate function that i'm gonna call i think what we really need from it is the connection id isn't it in both cases it is in both cases to send a message you just need a connection id so i will go there and i'm going to create a function i'm going to call it get connection id by nickname that's going to be async and that's gonna just require a nickname and it will give us a string or undefined if that connection id doesn't exist because for example that client disconnected from the chat and then let's just copy this code up to there and let's paste it paste it here here we're going to use a just a nickname of a da we don't have the query params and then what we want to do is if the output count or maybe other way around so if this is you know what okay that's fine so if this is the case we just want to return client dot connect connection id that's what we require otherwise we just written undefined and that's really the point of this function is it um some complaints okay yeah of course it has to be promise of string or undefined because it's a asynchronous function so that should handle it now i can go back there to to handle connect and let's just remove all of these and replace it with really what we really care about is the connection id so you know what this is not even this is not needed this is not needed either we just want to swap this bit of code with a weight get connection id by nickname and we want to pass the query params nickname is it right that might be undefined okay i see poster connection requires a string not string or undefined this is actually important this is why i like using typescript let's fast define uh something like that another const var i call it existing connection id because that's the one that is potentially existing already for that nickname and if it does if it does exist and existing connection id is um exit you know what let me just fix something here yeah that's gonna be better now so if existing connection id does exist we got some string value from it then we wanna do a post to connection to that connection a pink and then if that was successful we want to return with forbidden because you know if that was successful that means there is already a client with that nickname connected to the chat otherwise that's just going to proceed right remember post connection post to connection returns true right okay let's let's go back to to our handle send message because this is really where we need to get that connection id so that is a recipient connection id equals await get connection id by nickname and that's body recipe and nickname that's how we're gonna get it now and if that recipient connection id exists if it does exist that means we want to post to that message so that is what we want to do post to connection where the connection id is recipient connection id the data is going to be some sort of message that i have to stringify and let's maybe call it just a message where the value is an object where we pass the sender and that's sender let's make that oh yeah let's just do send a client nickname let's use nicknames for that and then the second one is just going to be the message itself so that is body a message so that's how that message is going to be sent and then finally we have to return response okay so this is all all good for the api gateway proxy result yeah let's quickly go through this again so i can explain step by step so handle send message it all starts from the pass sent a message body we make sure that the body passed with the json path is of send message body format that means it contains message that is a string and the recipient nickname a property also a string otherwise if if any of these conditions are not met then we throw an error but this this is not really an error that just you know disconnects that client we just send information back a feedback to the to this client with the message itself so we're just saying like yes something is wrong then for the handle send message function as we made sure the message the body is gonna be in correct format what happens in there basically we get the sender a client from the clients table so we have a connection id i mean connection id is something we have anyway but we also get the nickname of that client sender client the recipient nickname is something we already have on the body and here we prepare nickname to nickname which which is a specific property we've got on the message document that is needed to um to recognize you know this is the the message sent between these two users by the way i can do something like that and then we actually creating a new message in messages table with random hash as a message id created out as an actual time in in milliseconds that's just a timestamp as a number then nickname to nickname that's been prepared the message from the body okay from this body from send message body that's what's coming from the front end and then the sender which is the sender client nickname right after that we're getting recipient connection id since there was an information that was missing we only have a recipient nickname so this gives us a recipient connection id but only if that client with that nickname is still connected to the to the chat if if he or she is still connected to the chart what we do is to send a message to that connection with the value of a sender and a body a message the message from the body so this essentially sending it in the real time to that recipient and that's really it for sending the message let's do get messages that's the last custom event type that needs to be handled and for that type of event the steps are gonna be a bit similar especially on the on the parsing side of things the validation of the event body so um yeah let's just hand the get messages and let's add a function to parse it so pass get messages body where we get body string or null and we return we return get messages body as a result yeah that is a type i don't have defined yet so let's go back to the top of this handledos handlers.ts file and let's define that type what it's going to have so get messages body what we need is we need a target nickname isn't it so like if i'm thomas and i want to get messages between me and joe i need to send that nickname joe nickname so that is the first property and then i'm gonna add two extra properties for pagination but i'm not sure if i'm gonna handle the pagination on the on the front-end side it might be a bit complicated but since the query action on the document client dynamodb document client supports pagination in a really nice way let's make it let's make the the websocket server pagination ready so let's add limit which is going to be a number and there is this specific property we can use with the query yeah maybe let's call it start key and that is gonna have a type of key but that is from dynamodb i think it's this type of key or undefined in case for the very first uh very first one right the one that you know when you just do it from the from the uh end of the of the table in our case especially it's from the beginning or the end but we're gonna we're gonna go from the end so yeah so this extra too let's validate it so that is very similar to the previous one let's do get messages body now and that equals json parse again body or an empty json object and that will be get messages body if it doesn't exist or we're going to be dealing with with dealing with string for the nickname itself for the target nickname so let's do type of body um is it right we should okay we've got a target nickname do we that's get messages body so that's what i did wrong target nickname is different than string or this one might be undefined start key so we only have a limit so yeah for the limit we need to check if something is number so that's going to be type of cut messages body that is on the limit is different than number yeah let's just fix the formatting and yeah if this happens we just wanna throw a handler error incorrect get a messages format that's gonna be the error cool um is there anything i missed because that seems to be oh yeah of course it is i need to return that in case it's successful good yeah let's create the handle get messages now going to the bottom handle get messages that's our sync function and that is going to require what is going to require i think it's very similar to send messages so yeah we we need to pass handle get messages let's just do it like that so connection id and parse get messages body event dot body yeah now let's fix the the argument types yep so we will have a connection id as a string and then body which is get messages body and that returns promise api gateway proxy result cool yeah handle get messages so for this one we we need to do yeah let's start from the query so this is really about getting a list of messages based on a nickname to nickname so for this one we need a table name first and that table name has been already used by me so this is a great candidate to extract it so that will be messages table name not like that like that and then let's go back to there it's gonna be messages table name let's use messages table name index name yeah for the index name we've got it defined in serverless.yaml and that is nickname to nickname index and you know by the way i am pretty sure this could be passed to the handlers.ts as an environment variable i think the same case is for table names so that is an idea of improvement i'm not gonna do it in this video but i'm just saying um you could do it very similar way to what i've done with wss api gateway endpoint you see we what what could be done is just um you can just add a new environment and then use these sort of functions on on cloud formation to uh let's hang on hang on that gets a rn though so yeah so probably something else yeah i'm not gonna try to figure it out uh but yeah something for you to try to improve if you would like to experiment um yeah let's continue on this one so um let's have a look at different query operation that i already got for the clients table so that's gonna make it slightly easier i'm just gonna copy the condition expression attributes names and attributes values because this is required and i'm gonna change that because what we're using for for the messages is nickname to nickname nickname like that like that remember these are just placeholders so they will be so so they will be replaced um this is a an attribute name placeholder so i defined it here again and this will be replaced with nickname to nickname and then the value placeholder is going to be replaced yeah this is important because we want to replace it with a nickname to nickname and this is where we need this we need two nicknames we've got one on the body which is the target nickname but we don't have the the sended nickname right that the that client that is asking for there for the messages we don't have a nickname of that client so what i need to do is to use that connection id to get that client something i've already done if you remember it's in there increa in the send message isn't it it's exactly that so yeah another candidate for extraction get client that's gonna be the function i'm gonna use and we just do it by connection id so let's get client connection id and i'm just gonna copy this code over there and i think i can just do that yeah but there is no sender connection id there is just connection id now so that's how we do get client so first of all let's go back to send message where i needed a client a sender client instead of using that now i can do await get client and just pass sender connection id which is gonna handle getting the client in hand to send message now that's that's gonna be simpler and i need to do exactly same thing in here so there's a wait get client connection id perfect okay so i've got clients client the the one that is asking right sender and the second client i will have on the body so what i can do is to prepare nickname to nickname again that's gonna be duplication so i'm gonna go back there this can be extracted to a separate function as well i don't need to repeat this this bit of code so get a nickname to nickname that's what we need and that is going to take an array of nicknames there is a string array and it's going to return string which is essentially doing that just sorting them and joining them yeah this is this is by the way prepared for a multiple user conversation if you if you look at this there might be more nicknames three or four or ten i'm i'm basically not gonna implement that yet but it is again ready for that using the the ri approach in get nickname to nickname let's change that code this code so that is gonna be good nickname to nickname now just like that it's gonna do exactly the same thing it did before with that different code but now this is extracted to this one place where i can maybe just call it straight away there and just pass these two nicknames so that's gonna be client nickname and the second nickname is the one that we've got on the body which is a target nickname the order here it doesn't really matter because they're gonna be sorted right yeah and then these two additional properties that i have on the body so there is a limit and the limit is already on the body so that's what we want to pass the number of documents we gonna get and the the second one is exclusive start key and this is what we get on start key right finally scan index forward to false so it's going to start from the end of the index so from the latest message and it's going to go back in time so that is gonna be the order cool so output is going to give us messages on the one second yes i have forgotten about a weight and promise so on the output then we gonna get items but only if they exist so if items are there and if the count is greater than zero uh which is which might not be the okay let's do item length and length is greater than zero then we just gonna assign items to the messages otherwise that's just gonna be an empty array this is to handle it in a nice way basically if these items are not there there are no messages for this pair of users we're just gonna you know written an empty array so we still gonna return a message to that front-end client okay and finally send is it send message post message to connection right post to connection and post to connection that's gonna be the connection id from from there the one that we received in this handle handle function data that's another type of message isn't it let's call it messages messages make sense value is just gonna be messages perfect and finally last step is to return response okay to the api gateway like with api gateway proxy result and that is get messages function yeah there is one additional step that i could do to slightly refactor it i see this duplication so we got the client really what we require in the get messages is client and the same thing applies to a send message if you look at this right so it's not really sender connection id i think which is not really used anywhere else and it's not really used anywhere else i think it's not used anywhere else ah it is okay it is used there okay so maybe you know what i am not going to refactor it let's just keep it as it is great yeah i think this concludes implementation of the back end hopefully this code works i'm gonna give it a quick test in a second let's just go through get messages again so we all understand and can catch up on on what's going on so get messages if this is the route key then what happens first is the messages the body of that message of that event is passed to make sure we've got a target nickname there is a string and limit that is a number if one of these don't exist if one of these doesn't exist if there is something completely messed up with that message we throw an error that is sent back to that client the client that produced and sent that message to the websocket server if everything is good that function handle get messages is going to be called and in that function what really happens is the query is the query action is executed on the dynamodb table of messages on an nickname to nickname index and what we're looking for is a is just all the messages of just one pair of users so what we need is two nicknames and that's what we get here right we've got get nickname to nickname with a client nickname this is the actual client right this is the client that's asking for that list of messages and then the target nickname is something that that client was supposed to send in a message body that at this point is already validated so it does exist and then like we expect from this query action to give us a list of of messages between these two users of course it does require some additional parameters we've got the limit so that is how many messages are going to be returned and there is a exclusive start key and this is uh really a pagination functionality so that opens it up for the front end to use pagination if we want to just for example at the very beginning just show 10 or 50 last messages between these two users for one particular user and then you know fetch more as the user scrolls up this is how it basically looks like in in the facebook messenger right yeah scan index forward make sure it starts from there from the latest message good um here some additional checks on the items themselves basically if they don't exist or they there is no like the empty array we're just gonna set the messages to empty array and that's how we're going to send it back to that connection to the client who was asking for that list of messages right so yeah regardless whether it's empty or not these messages are gonna be returned back to that client and yeah this is really it this is really what that function does this is where it ends yep there is no more functions to implement so let's give it a quick test before i test it obviously i have to deploy the code so i'm gonna go back to terminal and let's just um use a serverless deploy command to deploy the code to aws okay deployed so yeah let's test it with websocket now so i'm gonna go through these two command line windows and first of all i'm gonna connect with the nickname thomas so as you can see this is like i tested it before for connecting disconnecting and getting the clients i'm just using this url from here and then for the nickname here i'm gonna use which is actually tommash and there i'm gonna use joe so yeah so that works as it's supposed to joe connected and thomas got information about joke connecting to the chat and now let's try to maybe let's try from let's start from incorrect message so i'm gonna send something like send message but no additional properties okay that's what we got incorrect send message body a format something to improve on i've just realized because that just gives us the message itself so i forgot to add to wrap this into something like that type should be error and message should be e a message that's how we want to send the error type instead of this so that is one one thing to to fix okay let's continue so let's add one property maybe let's let's do send message but this time let's add maybe the recipient i think the recipient is the first to be to be verified oh that's a message so yeah let's send a message as a number that's just one additional check again we got incorrect sent a message body format obviously this could be improved we could tell the client what error is this like yeah the message is in correct format right it should be string but yeah i leave it up to you if you want to improve that and yeah finally let's just send an actual message and let's test it if that message is going to be sent to joe so let's send the message hello and the recipient nickname is just going to be joe so that is the right that should be the right format yes that worked right we can see joe received that message straight away so there was real time yeah now let's try joe let's let's send a message from joe and um that's going to be thomas right so we're sending the message from joe back to thomas now and there we go that works okay one more try let's try to send a message maybe the same message or maybe i i slightly modify it yeah it's a bit tricky um to it's a bit tricky to modify it here with webstore cut so i'm just going to modify it here and just copy paste it so now what i'm going to do is to disconnect joe but still send a message from thomas to joe cool okay looks like everything went well and now i'm gonna connect joe bark and i'm gonna use a different action which is get messages and that action requires if you remember that requires target nickname this is a required field and that's gonna be thomas we want to have all the messages between these two guys and limit and let's just say 50 doesn't really matter yeah we don't need to send the the start key and then when i press enter i should get yeah i get the at least of all of the messages between thomas and joe right we've got sender joe that's the latest one right created at um looks like there is there is something wrong with the with the time though because as we can see the the high there is the first one and this is not really a time stamp let me have a look at this so when we send the message because that's on sending the message i think this is yeah that shouldn't be milliseconds this is the mistake i have done this this should be get time i think gets time gives you the time value in milliseconds so this is a a unix timestamp okay all right okay so yeah i'm i'm this is definitely gonna fix it um let me just quickly deploy it and i can confirm it so yeah we cannot really do anything with these messages they already there i'm gonna remove them later but for now what i'm gonna do is to go to this terminal window and just run serverless deploy again it by the way shouldn't really disconnect these two clients they should stay connected uh but yeah i will see once the the code is deployed so let's just apply this this fix quickly the code deployed let's have a look yeah right that seems to be that seems to be working they still connected these two clients and let's maybe let's send another message maybe how are you and let's send it to joe okay yeah we received that straight away so that should be added to the history and let's try to um now maybe i disconnect thomas and i send that message from joe so he's gonna say i'm good and he's going to send that message it should be created in the history so now i'm going to connect back that that's correct i'm connected and now let's do action get messages and target a nickname is joe a limit and let's use the limit properly now so we added two messages so maybe yeah maybe let's start from two mess let's do two messages there are only two messages so we should get only okay and now they are in correct order as you can see so we've got a message i'm good this is the last message sent from joe to thomas when thomas was disconnected and how are you was from thomas so this is the right order now if i repeat that but add more messages like higher limit we should see all of the messages but yeah the previous match messages are not in the correct order because of the issue with the with the timestamp we've created art as you can see anyway i can go to dynamodb and remove them manually that seems to be working well we can get messages send messages between two clients so um yeah i'm gonna do some additional testing of the camera and now we will move to the front end part so i'm just gonna remove that and let's create a new project let's do it in a separate visual studio called a window in a separate folder so i'm gonna go to file a new window and then this is a bit similar but different commands are gonna be executed in the terminal so let's open a new terminal and now in the terminal again i'm gonna go to cd projects and then in the projects directory what we need to do is to run a following comment that can be found on this website which is tailwind css i'm gonna use tailwind just want to make it make it faster with all of the css so that is really the command with a small modification so i'm going to copy this go back to visual studio code paste it in and let's rename that to serverless chat ui exactly that's going to be the name of the front-end part and the template is going to be typescript so that is this is the difference to the original command from tailwind css and now once i execute this command this folder server as chat ui should be automatically created so let's just hit enter and there we go yeah that is created so let's open this project now with visual studio code so i'm gonna go to open and then from my projects directory i should be able to find a serverless chat ui that's the one i'm interested in so let's open it nice okay so there is a create react app project from the typescript template now i'm gonna go back to my browser and there are two additional steps or actually more additional steps it's like six of them so yeah let's go to step number two what we need to install is tailwind css so that needs uh opening another terminal because that's a new project opened and now i've just i'm just pasting um yeah maybe not this one yet so let's install tailwind css post css and autoprefixer or prefixer oh okay that was quick then we're gonna initialize tailwind css and what is the next right we need to go to we need to add the paths to all of your template files in your tailwind config.js file okay so that's been created automatically i see and what we want to do is to modify the contents content section so let's just do it with that code nice yeah and finally that's not really finally it's still three more steps to go so we need to modify src index css and add these three lines there so that's the index css let's add them at the top of the file and and now i can start the build process and i can start using tailwind nice okay so that's essentially is going to be the last step for me let's open up the tsx and let's around npm run start and after few seconds i should see okay that took a while and now we can see the the default component from create react up okay that's the default one and uh and as a first step usually what i do with the uis is to try to find something open source already built by someone else on the internet and then either slightly change it or adapt to my solution so what i'm gonna do is to open up a new tab in my browser and i'm just gonna google for maybe tailwind css web chat and let's see what we get in the results maybe let's click on the first one and let's have a look at this tailwind component i think this one is good enough for our chat i mean yeah there is a sidebar missing with the list of the clients but i can add it myself so i'm going to do some tailwind css coding and yeah i think the rest can be reactive i'm pretty sure this is just html with tailwind css classes yep that's that's that's correct so i'm gonna have to adapt it into a react application yeah and let me just do it so um i'm just gonna copy all of these this whole div and then in my visual studio code in in the app components i'm going to remove the existing code and just paste everything from that tailwind components obviously this is going to fail because i need to sort of reactivate this code so change classes to class name and so on like things like that i'm going to show you in a second but let's go back to there and let's fix the styles i mean let's copy the styles as well so these styles what we want to do is to maybe let's add them to index those css that's going to be the fastest and there is some element gathered by id right okay that's just scrolling to the top so i i'm going to handle it myself so i don't need to copy this code cool okay so now i can close that or maybe we keep it but let's go to app component and let's fix all of the class attributes first they should be called class name so i'm just going to quickly do it okay all of them have class name it looks like now it looks like yeah emg seems to be not closed so that's another thing that is required by by react so i need to go and check all of the emg uh let me think if i can do it quickly uh probably not okay let's just do it manually then yeah that seems to be a final one so yeah let's see what else is wrong so what we've got is this user your user profile okay let's just remove that this is probably going to be completely different message and there is something wrong in here that's the input that is not closed okay let's just close it like that nice so after saving it as you can see my automatic formatting that i've got on visual studio code that just you know formatted the code so now it all seems to be good you know let's have a look how it looks like in the browser nice so yeah that's exactly what we what we had in the components right um one more thing um there is there is this screen that i want to show at the very beginning because yeah this is this is the chat functionality but at the very beginning we want to just show a simple input box with um with the nickname right nickname label so the user can provide a nickname and then proceed something like a login screen but just with the nickname and for that i'm gonna i'm gonna also reuse a component from tailwind so let's look for login and let's pick one of these components maybe this one yeah this one looks nice so let's just copy all of the code from here and then what i want to do is to create a separate component for that let's call it as a welcome screen something like this and yeah let's call it welcome and that's just gonna return that yeah now we have to okay fix that code so again it's a class change mainly yeah that's the class change so let's do it good and inputs as well we don't need a four attribute this is probably not gonna be even required nice um yeah let's export it and i think what else i have to do is to import react just like that nice export default that's what i want to do good let's go back to app.tsx and in the app.tsx what we want to do first is let's define let's let's define an a state for the nickname because that's that's the first logic that we really want to implement in our app component so a nickname said nickname equals use state and this is just going to be an empty string and then if let's just do it it in the imperative way so if if there is if the nickname is empty let's just do something like that then we want to return something else instead and that will be the welcome welcome component that's it so that's what we're gonna display and let me show you in the browser now how this looks like so that's what we got right join our community but that's not how you want this component to the content of the component to look like that should be way simpler so really um let's just let's just replace join our community maybe we've joined the chat that's fine join the chat and yeah let me make it bigger uh join the chat and then we won't have password there will be just a single input and just a button so we don't need anything below the button this is what we need and then the button should be not sign-in but join let's do join placeholder is nickname and yeah basically i will have to set that state in here but yeah in a second let's just have a look how this nice um you know what maybe even better maybe even better let's do something like web chat and join that's it simplicity right nickname and join good now we're going back to app.tsx um i'm just thinking let me make my browser window visible all the time okay i think it's better now we can see the browser window all the time so yeah and here by the way i think i can close this tab and this tab so we only have our react app cool um let's handle this logic to set the nickname right what what i want to do is to set that nickname from the welcome component and when the nickname is set we want to display the the chat itself so let's handle that yeah what we gonna need to do is to pass a certain nickname function to welcome but we don't want to pass nickname we want to have a local state at the welcome uh in the welcome component so let's just do that let's um create something like a nickname value here and set nickname value as a state obviously so that's going to be our local state this is needed to first be able to provide the nickname and then confirm it submit it with the join button otherwise if i just passed said nickname and nickname to welcome component as soon as we we typed um something in the in the nickname input you know just one letter there that would show us this screen and that's not something you want to do want to have like a submit action so only so this this state is going to be local but set nickname is going to be something that we pass to the welcome and that will be called once the button is clicked let me just implement that so yeah so that's going to be just the standard approach on an input field with react component so that's yeah that's going to take e on change takes e and sets a nickname a value with e target value and then the value is a nickname value right it's just a standard use of state and then on the button what i want to add is on a click and then on the on click there's just going to be this function that calls set nickname and that is going to be something that we require let's define some type that's just going to be a standard setter so takes string and returns void and that's exactly what we're gonna do we're gonna call set nickname with our nickname value okay um let's see if this is working oh yeah okay it has to be passed from there so that should be said nickname set nickname as a state setter nice let's try to type something in and click on join there we go right that works if i refresh i get back this screen good there is one case that we also want to handle if someone just clicks on join we won't see anything but let's just display some sort of an error message and in order to do that let's just add some spam um yeah before i add that span i need a state for an error so let's create it error message set error message use state and that's just going to be an empty string again and let's do something like that maybe so let's check if error message is different than empty string and if it is we want to add something like a span otherwise there's gonna be just nothing let's just do something like that and then for the span we want something like a class name and error itself so that will be error message here we wanna have a border different on the input oh maybe i i get i get back to it in a second let me just kinda let me finish that the span class name for the error message itself so we want to have for sure a color of the text should be red let's do on maybe not this kind of red there should be this one and let's implement that logic so we can see how the error looks like because at the moment i don't even set the error message if something wrong happens so that would be in the on click so what we want to do is to check if the nickname value is empty and if the if it is empty we just set some sort of error message otherwise we set nickname to nickname value and that error message is going to be something like a nickname cannot be empty so that's that's how it's gonna look like there's a simple check on a nickname value when someone clicks on the button and the error message is set to this value let's see yeah nickname cannot be empty so that's what we see if there is some issue one more thing i can try to do is to change the size of the of the font so it will look like this and let's add some oh yeah i think what else i can do yeah that's how i make it smaller so yeah font medium is is just the uh the weight of the of the font right so this is bolded now and then the size is a text xs as as you can see font size is 12 pixels for font medium we've got font weight of 500 nice i think this is enough so if if there is no nickname that's what we're gonna display if there is a nickname then we just show the chat window great okay i think that's the welcome functionality i mean in terms of how it looks like and how it works in terms of state in the welcome component itself one more thing i'm going to add to app.tsx is adding this to a local storage if you remember at the very beginning i said that the once you set the nickname the functionality is supposed to remember the nickname and won't make it possible to change it unless you go to the local storage of the browser and you remove it manually from there so to prevent you know the nickname impersonation i mean it would still be possible but just to to to make use of local storage and show how to remember something i'm gonna do it so for the nickname itself as an initial value what we wanna pass is an attempt of getting this getting a nickname from a local storage and i'm gonna do something like that so when the uploads for the first time the initial value for the nickname is is going to be nickname if it does exist if it returns a string if it if it doesn't then it's going to be an empty string and i'm going to use a use effect to store the nickname when it changes so this is where i'm gonna do a set item a nickname and the value itself is just a nickname okay maybe i add the window just to be on the safe side to the local storage here as well good okay let's quickly test that functionality so we have a web chart i'm going to provide a nickname a bob and we can see the chart if i refresh yeah i see the chat opened i don't see the welcome screen anymore and if i go to inspect then application and the local storage you can see there is a nickname in the local storage if i remove it manually just right click and delete i close it and refresh that welcome screen is displayed so so yeah that basically remembers me once i join it remembers my nickname so that functionality is done nice um next functionality let's implement the the web sockets now because that's the the core the core functionality of this chart application is really using the websocket client and communicating with our websocket server so what we want to do is that when the nickname is set so that's here we're sure the nickname is set we want to establish a connection with our websocket server and to use web sockets in react um it is a bit tricky because like that two options essentially i've seen a library where you can use the web sockets as hooks but it is a bit complicated the other option is to use them as as a mutable object in refs in the reactor f's and that's that's the the way i'm gonna take so that's what i'm going to do and in order to do that let's take the simplest approach for now so i am going to try i'm actually not sure if this will work straight away it might not because we've got this if statement for the nickname and this might be an issue because what i really wanted to do is to define the ref um define the ref for the let's just do at this point maybe before the if i don't think it's possible to do user fs after using the if statement in react so what really i want to do is this so that will be um yeah not like that that would be websocket websocket ref is use ref and yeah in there i really at the at this point i don't i don't want to do yeah this is where a small issue is uh i will try to figure this out so we will have a websocket object or undefined i guess that's what you want to do we do that yes we can do that and this is our ref and then if there is a nickname i don't think it's possible to do conditional ref i will double check in a second but let's try to do that and if there is a nickname i'm gonna try to change the address and assign websocket client and that websocket client is going to take a url with the nickname i'm gonna i'm gonna find the url in a second so that will be nickname equals nickname and if that is possible which seems to be possible um yeah there is there is some issue though let me just check maybe it just tries to connect and it cannot connect let me find the right url to connect to websockets let's try to find the websocket okay that's that's the url so i can kill this terminal and just use that url for now like that right okay yeah that seems to be working then okay i thought it's not possible to modify the current on the websocket draft but yeah it looks like it's fine we don't get any any warnings yeah logo is defined but not used that's fine let's let's do a small test a small test though so i'm gonna go back to inspect because that's not what i'm sure i'm not this this is something i'm not sure about and let's go to application again and let's remove the nickname then refresh and we can see web chat form again let's see if we to have any errors i'm gonna refresh that okay the console has no errors that's a good sign let's provide some sort of nickname maybe john join okay we've got some issues with the with the attributes these are just warnings so i'm gonna ignore them for now but yeah this one is serious websocket connection to wss failed let's try to connect to it with websocket first so i'm gonna create a new console window and then i'm just gonna use websocket with that address and if i try to connect i received 403 forbidden let's change the nickname maybe john 2 and that works so yeah we've got some issue with with this particular way of connecting to web sockets i suspect this is probably multiple connections to to our to our websocket server because we we specifically restricted that i'm pretty sure it is that because whenever that component re-renders i suspect this is basically creating a new instance of web socket so let me disconnect from from web sockets uh maybe i kill this terminal window and a simple experiment so what i'm gonna do is to wrap this into a function that returns that instance so let's do something like that so that will just return it and that function will be immediately executed so that will be something like that and i'm gonna just put a debugger expression over there um one second i think i've got something wrong yeah that is this is probably missing um and we've got that thing music okay yeah okay and i can see this debugger breakpoint the debugger stopped on it and now if i press on yeah let's let's continue oh yeah that's that's the problem so that's the problem we've got multiple times that's what i suspected so yeah we cannot go with this approach i need to change that let me close this developer tools bar and and let's think about the different solution um yeah i'm thinking what i could do is to use some sort of lazy loading class for the websocket some some sort of the container that just keeps one instance and gives us this one instance only when a certain method on on this on this is called let me show you so i'm gonna create a new file let's call it web socket maybe connector and that's gonna be that's gonna be a class websocket connector and we will have only get connection method on this class where the url is required the input param and here we're going to keep connection as a private member and that connection is going to be our websocket instance but it will be sort of optional it will be undefined as a default and then in the get connection if that connection doesn't exist yet because you know initially it is going to be undefined we will create it by calling websocket new websocket basically but with um url and then just return this connection as a websocket because we've made sure this is a type of websocket there will always be websocket returned from a get connection method so yeah that will make sure there is only one instance of websocket and now going back to app tsx i will simply create that i think i can just do something like this so that will be const websocket connector equals new web socket connector and then on the very beginning i mean not very beginning but at the the point where the ref is created what we can do is just pass this websocket connector and that's really it so i don't need to set anything on the current because our current is going to be a websocket a connector itself on the on the ref maybe let's rename it to websocket connect or ref so we know this is a different type and now with the websocket connector ref at this point where we are sure the nickname is set we can just prepare that url as a simple variable and this is where we can use our websocket connection so i will do websocket connector rf current and get connection with that url and we can essentially use another variable over there that we also make like this will all make sure what we're getting is just the same instance every single time and now on the on the websocket ws is essentially is is going to be a websocket already connected so on the websocket itself we can define these functions like on close on message on open and then interact with our react application and you know set some sort of states depending on what we receive from our websocket server and you know what the first handler i think these are called handlers the first handler we are interested in is on open this is when the connection is established so if i do something like ws on open and i think this is how it's done so you just assign a function to it and that function will be executed when the on open happens when the socket is open and now you know one once it is open this is executed and there is there is one limitation if you remember let me just go back to the back end code let me show you this limitation that we're going to handle right now in on in on open handler so here i've got it handlers.ts and that limitation is on on the connect event type if you look at this one we've got notify clients but we only notify a clients that are not this client that is actually connecting because it didn't happen yet that client that tries to connect by ascending this connect event right this is when we create a new instance of websocket on the front end this client won't be notified about all the other clients so it needs to ask explicitly about the clients list and that's that is on we have get clients event type so now i'm gonna go back to on open and implement that logic to get the clients list this is where we want to call it so since we've got access to ws what i can just use is a send method and here i am supposed to send a string but that's a stringified json that's what we require and on that object we will have action if you remember get clients and that's it that's what we can ascend only then this websocket client is going to send it and the the server is going to send back a message with all of the clients so that's another handler that we have to specify which is on message and onmessage is slightly different to on open because there we really interested about the data that we receive from the from the websocket server so i'm going to take an argument e which is an event and then there is a property called data where we will be able to find that a string sent from that this is really going to be just a json stringify sent from the from the back end so there i can straight away do something like const message equals json parse and e data okay but yeah now because you know we're using typescript here this has to have a correct data type which is any um that's the the written type of parse if there is if we don't change it to anything so let's just change the type for now to to how it really looks like if you don't remember how it looks like this is the back end handle get clients posts to connection create clients message function is called with the clients and this is how it looks like so we have a type clients and we have a value that is an object with the client's property and the value of the property is just yeah it's just the array of a connection id and a nickname is it yeah that's clients so let's go back to the front end and let's maybe for now because yeah this is gonna handle all of them all of the types of messages so this will be type string for sure but the rest is gonna i mean yeah there's gonna be value as well but the type of value is gonna depend on what type of message is this so yeah maybe for now because we're just handling get clients let's do clients which is again um a client that i'm just gonna copy from from the back end so that will be client array yeah this has to be changed remember that so this is our this is our message and you know what maybe let's just console log this message value just to make sure this is work as it's supposed to do i have any errors there is some issue yeah i just i just had to restart it the npm run start for some reason probably something with the imports but yeah now seems to be seems to be good and it's not complaining about the about their own message but yet that that was really because it couldn't import a websocket connector for some reason so yeah i just i just restarted it and now it works let's have a look at the console if we get anything from from the from the server and this is what we get we've got the nickname john yeah that's the only one that is connected at the moment but if i copy this localhost 3000 url and i open a new incognito window and then in this new incognito window i open another chart provide nickname tom for example and join then what we get is a new message with two clients now we have tom and john and it was the same case really for for this one if we refresh then we have two clients and here the disconnect happened and then connect of that tom client again so as you can see this is working nicely it is connected to our to our websocket server to api gateway web sockets and we can exchange some messages but yeah let's now implement it in the front end so it's it's better right we don't really want to use console.log we want to see a list of the clients on the on the screen so let's do that let's just do that um yeah that is that beat that is missing unfortunately and uh we're gonna need to implement i'm gonna need to implement myself a sidebar that will be located somewhere on the left and then on the right we will have that conversation but yeah that conversation has to be different depending on what a client i click on so let's let's do that now yeah first of all let's move all of these to a separate component so this shouldn't be in the app i'm gonna just go and move this entire code to hang on move this in entire code to a different component let's call it conversation so that's going to be the conversation dot tsx and in the conversation it's just going to be yeah conversation equals this and that's what we're going to return obviously i have to import a react at the top to make all of this working and then let's just conversation i think i forgot to export it so now that it is exported i can import and display it straight away so yeah this is a separate component now another component that's the new one i was talking about is going to be a sidebar let's just call it sidebar and again i just want to import react from react and this is where i'm going to maybe for now let's just return a simple div sidebar so this will be this will be something else let's just do let's drop let's drop it into a div as well but essentially there are gonna be two components inside so we'll have a sidebar as well good some issues app cannot be used something is wrong i think that is because our that is because that should be like that so what we want to do is to add a class name attribute to this div and the value of classnet class name is going to be flex then the sidebar i'm going to wrap it into a div and this is also going to be wrapped in in diff i mean the conversation component yeah this component i'm sorry this div container will have a flex a none attribute what flex none is is basically it doesn't grow or shrink so we can set a static width to the to the sidebar and that's really what we want to have two static values for the width there's gonna be one for the responsive for the mobile view and the other one from for the desktop view i'm going to show you in a second and for this for the conversation we can just use a flex auto i'm not sure if this one is not default but let's just be explicit with this case right so yeah for now we still don't see anything because what i have to do to see some space on the left is to add some a width so let's maybe add 32 and let's see what happens yep there we go so we we we have some space as you can see this is a mobile view and this is too much space i think for that particular view for this for this chart i'm gonna be sort of aligned with how facebook messenger works and what facebook messenger shows us when we have a smaller screen size it's just the icons for our for our friends and that's what i wanna replicate as well so as for the default i'm gonna use a lower value maybe eight or maybe slightly more like 16 maybe it's a good idea yeah i think 16 seems to be a good idea and then for the medium size of the screen we're gonna do something like 40 maybe let me show you so if i increase the oh that's still not working or did i forget about something yeah i did there should be w and hyphen and now if i resize that window as you can see that changes right so it's more space then let's have a look let's have a look at the the sidebar itself yeah one more thing i just realized what we could do is to add some sort of border on the right can we oh yeah okay that's better so now we see that's the space where the where the icons where the images these round sort of images are going to be i'm thinking about one for myself so this is going to be the that's going to be the picture of the user and here picture of the other users basically i'm not going to implement any any mechanism to upload this feature this images that would be too much work for now so yeah i don't want to make this video um that long so instead i'm just gonna i've got some images prepared and i'm gonna use them instead so we'll have at least this this is gonna look nice at least for now i'm just gonna use the image of this guy so yeah let's let's just continue so we've got the border and now let's go to sidebar and what we want to display really that it's going to be the same the same sort of image so let's go to conversation let's find that image which is this one and if i just put it in the sidebar somewhere here then it's going to display it's going to be displayed like that and i'm gonna change something for it so rounded full is fine i want to have the same size for mobile view and the and the desktop view but maybe we just make it smaller maybe something like that and i want that to be in the center so let's just turn this this this div into another flex box display flex and i think if i just do something like justify center then we can see it is right in the center that's what we want to have and this is this is for the for the for the for the mobile view uh maybe some simple margin nice okay you know what maybe slightly higher um yeah okay now it's the problem is we want to have a margin top seven seems to be slightly but i just want this to be aligned with the other one with the one here cool okay great and on the bottom so let's drop this into another flexbox class name flex and this time this flexbox is to [Music] place this this to place two sections over there so that's just gonna be flex column and that's the first one the top but we also have the bottom this is going to be our list and this is really another flex that's a flex flex column and this is where i'm going to add all of the images yeah that's lots lots of nested flexes but i will explain that in a second why i'm i'm doing it like that um actually i can explain it now so in the other view the desktop view we want to have the nicknames next to the the image so i want to align all of this in a nice way so that's that's the reason i think i can just do items center okay this is now center and let's just add a margin bottom as well so there is a some space in between let's make this images smaller oh you know actually this can be all the way around so this will be smaller because that's just my image although again i have to move it a little bit maybe like that okay let's align this one with that line over there and this is really how it's gonna look like oh yeah these are too close to each other so let's just add some margin again maybe four three yeah three seems to be better so this is how it's going to look like this is how it's going to look like in the mobile view yeah and we can basically add more of them but this will depend on the number of the clients okay so let me just keep for now too for the sake of of demonstration how this is going to look like with the actual clients connected to the chat and let's let's modify that code to handle this view as well because on this view i want to have maybe here we'll have chats a title charts and and here next to every single image will have the nickname so let's just do that in order to do that uh maybe i can actually just move that window slightly so yeah so we can actively see the changes nice so uh here i want to have a span with charts so that's just charts okay oh it looks like i don't have to align anything in there so that's that makes it easier the only thing i want to do is to add some simple margin right maybe two good okay so that's charts maybe boulder i'm not sure i can't remember now what was the font weight so let me check on tailwind oh yeah that's just fond medium so let's use font medium for charts class name font medium better and then that's going to be a very similar case for for these ones okay you see this is this is slightly different now how it looks like i basically don't want it to look like that also this might be slightly smaller so that will be a size i think that's what we're looking for no that is text text sm that's slightly better but yeah let's make it a line i think the reason is they are yeah we use a flex column so if i add a flex there then this is better but i also need to wrap this into a diff i think that's what i have to do so let me just let me just see and no that's not what i need to do no that's not really going to work i think i can do something else with that there is probably a different option i can use yeah that's the leading so that works so this is the line height so if it's a line with class name with the width of the of the image and hey basically the head is automatic there so if we have let's just do 8 as well so this is this should be aligned with hate and then we can we can see this is this is fine um there has to be a small space in between so maybe let's add some simple margin maybe not that big can i do one and half i can do one and half beautiful okay and you know what let's just copy paste this entire thing so we will see how it looks like if we have multiple of them okay that should be charts basically this is the this is the nickname nickname one and nickname two maybe just n2 something like that yeah and what you can see this is not really this this doesn't really look nice i think in this sort of view the desktop view i would rather have them aligned to the left instead of on the at the center so let me go to i think what just needs to be done yeah so that's the item centered i'm gonna change that to items start if we have md and now they align to the left the same should apply to charts so here i use justify center so i need to use justify if i start that should just align it to the left but now we need some sort of margin at the left on the left so let's just add this this can be padding as well but yeah let's just use a margin left too okay that's better yeah i think this is good enough so that's just gonna show you know the full nicknames next to the next to the image what's gonna happen if we have more text okay that's how it's gonna look like i think it's not that bad especially especially for what i want to achieve really which is a simple chat okay good so that's the sidebar let me just test now if this is still working if i do that okay some issues now that i have obviously this is probably because i need some changes to to add the i need to add some changes on the visible invisible that's the problem so on the spun we should have something like invisible and md visible and also there should be a width of zero and automatic when there is uh the screen size is medium so that's what i want to do i think that's just what needs to be copied and paste it there yeah that basically needs to go here as well good yeah now this is nice isn't it i mean could be nicer i'm not sure if this is really at the center but i don't want to spend more time on on this yeah let's pass the the clients so we can display the clients now do we have any state for the clients we don't have any state for the clients so let's just create one clients set clients and that's use state client ri empty ally as an initial value for the state and then where i had a console.log which i don't have anymore i am just going to set that state that value of the client state to message value clients not like that like that so now i can pass these clients to sidebar as props that has to be exported types so it can be used in in the other component not like this that's the right approach and finally we can reactify this code and what do i mean by that is just run the map on clients now that should be inside this div container and this is really what we want to print out that should be client dot nickname okay okay yeah now i'm wondering why we don't see okay yes now we see it so this is me if i again copy this url open a new incognito window and join us bob we've got two of them now so one of them is is myself as us bob here the other one is is john or joe i think it's john let me check so yeah we've got john and bob and now only john nice yeah we cannot click on it so let's implement that behavior now so when we click on one of these so they should be combat on basically i'm just gonna wrap this whole div into button so it can be clicked now and let's implement the behavior of that button so it will have on click and this is where yeah i need to introduce some extra functionality so this is where we're changing the target nickname this is when when we're changing the target nickname like let's say i want to see the conversation with person a i just click on person a and that changes the target the nickname so what what what should happen from the front end we are supposed to send a message to the websocket server that now we want to display we want to receive we want to get the conversation between myself and that person a when i click on person b same thing should happen for person b so this is where i'm gonna send the the get messages event to the back end so let's just do that yep um this this i think that the best approach for that is to create another state for the target nickname so i'm gonna create a target nickname and set target nickname that is going to be just a string with the initial value of i guess let's let's make it nickname or maybe not maybe let's just use the empty string i was thinking like the very default one could be myself when i just log in for the first time but i will figure this out later so for now let's just um yeah as a default one let's pick empty string and target nickname set target nickname so set target nickname is what we want to require from the sidebar because sidebar is the place where you're gonna click on the button so let's require that additional prop set target nickname which is a function that just doesn't return anything because it only sets a value that function is going to be called in the on click i think that's the name okay yeah i need to add it there so set target nickname and that a nickname is a client nickname so for everything the client we just going to set a target nickname depending on which client has been clicked on then in the app dot yes we want to pass set target nickname so yeah now we have a nickname and target nickname so nickname is our nickname and target nickname is the nickname of the person we want to speak to good i'm just thinking this might be it in terms of the sidebar functionality i can see somehow sometimes we don't see these clients but i think this is just a hot reloading so it shouldn't be an issue but yes as i was saying sidebar functionality is probably done at this point it's probably done so we can go to we can go back to app.ts x and think about changing the think about getting the messages i think that is the best next step as we have a set target nickname this should have this should have an extra action that extra action would be to send a message to websocket server to get the messages between myself and the other user the target nickname so you know what let's create a function for it this could be target nickname maybe target nickname value target nickname value and then we will have a function set target nickname and this is where there are gonna be two steps so one step is setting the nickname the setting the target nickname value this is required anyway to display our conversation partner nickname over there but apart from that i'm gonna send a message to the back end and that message is supposed to be get messages body so we have a target nickname limit and start key we're not gonna use start key for now limit is required though so let's just use some default value for limit like 100 maybe and yeah the target nickname is something we we have at the point at that point in in the back end so so really that's just gonna be action get messages then target nickname target nickname is the nickname from here and we also have a limit so maybe 1000 slightly better i don't think i will do pagination in this video this is gonna end up on just yeah loading all of the messages it's probably gonna be too much work so that is now the set target nickname it has some extra functionality to this extra step where we sent that message maybe that would be the first step and then the state actually changes cool yeah now let's handle that message so once we send get messages to the websocket server the server is going to respond with a different message containing that messages so we have to recognize that message in on message this is the the one handler that handles all sorts of messages received from the server so let me first maybe i slightly change that that type let's make the value unknown and then based on the type message type equals the first one was get clients i think or just clients let me quickly confirm that's clients and by the way the one we are interested in is messages clients and messages so that's clients and then clients let's take a small shortcut so we're going to change the value now to clients client like that so you can set it straight away if the message type is clients if the message type is messages then the format's going to be slightly different but yeah this is again where i have to introduce another state four messages and these messages are gonna be passed to conversation this is where we're gonna display them so let's create another one with messages set messages use a state that's going to be an empty ri but let's have a look again at the back end to see what is the format of a single message because it's slightly more complicated that's the format of a single message so let's define that as a type on the front end so what we've got we've got this part which is a string this is a number this is a string string again and finally string surprisingly so we've got message type defined and that's gonna be the type of that state a message array messages set messages that's exactly what's going to happen when we receive those messages from the from the back end and again i'm just gonna do the same approach with the types i have something missing though so message value as this shouldn't be a column okay let me just quickly explain so this one because the type of value is unknown at this point in our code once we recognize the type of the message i can sort of cast it it's not the real casting but for the typescript it is i can cast it to the format of the value that is associated with that type so for the clients we just got the clients which is an array that's how value looks like and for messages again value becomes this and that's because i know how the format looks like since i defined it on the bucket okay to not spend any more time on this let's try to let's try to have a look if we receive that so maybe what i will do is to add a simple console log for the message itself and any type of message that we receive from the back end is going to be visible in the console i will remove that console lock at the at the very end okay maybe last step before i give it a quick test is going to be pass messages to conversation so messages messages and in the conversation messages message something like that yeah that has to be imported from the from the app okay let's give it a go so i'm gonna inspect go to console refresh i can see one client on the left when i click on that client yeah i received messages there is no messages at the moment between me and myself this is this is basically myself in this case which is john so yeah i haven't sent anything to myself let's change that so i was thinking about displaying all of the messages first but because we don't have any messages at this point it might be a good idea to implement send message logic and then after that i will sort out this view css basically of the classes for the elements there and i display messages based on what the messages passed to conversation contained so for now let's do send message that's going to be the last type of of message to to handle and with that i can do some experiments and you know we can see some results on the screen immediately after i do that so that's going to be something i'm going to take care of first let's have a look so conversation definitely conversation is going to need to take a function prop and that's going to be our send message so maybe that's what we will define first so messages is one prop another one is going to be send message which is going to be a function and that function for now let's just take a message itself and void as a return type let's make it simple then let's define that function send a message message string let's make it empty for now and let's pass it to conversation send message send message it's not gonna do anything yet then to conversation and then in the conversation this is where we have that button the send button uh looks like it's not visible yeah it's only visible at the certain weave of the screen but that's fine i can use the on key press as well and i think this is this is the other button isn't it yep that's sent so there should be on click on click and on click is going to call submit a function that i'm going to create why i'm gonna keep it in the function is because i'm going to also create on key press event i think that's how this is done if it's enter then we also want to submit otherwise nothing should happen and let's define that function submit okay so submit what submits gonna do is to send that message from here but for that message we need a local state empty string as a default and then on the input itself i need to implement i mean set the handler for unchanged and there's gonna be e target value that's message and once the submit is executed send message really going to be is going to be called but it's missing so let's just do send message let's add it there to the list of of props and let's call it with the message itself and let's set this local message to empty string okay so that should send that message now in both cases so when the when we click on the button which is only visible in the desktop mode and like on the desktop screen right and enter on the on mobile let's go back to app.tsx and let's implement the functionality of send message so yeah let's see if we need any extra parameters so we've got message and we've got target nickname value that's something that has to be said beforehand just clicked on the on the icon there is still still this extra step i'm gonna have to do because at the moment at the very beginning there is no target nickname target nickname value so we need to have some sort of like proper default value but yeah let's let's just do it in a second for sending the message that's very similar to setting the target nickname but a different action which is send message and what send message requires let's have a look send message requires just a recipient and nickname and the message itself so that makes it easier recipient nickname that's our target nickname value and the message itself is what we passed to that send message function so that should just send it to the bucket when it comes back from the back end which is going to happen only for the for that user we're sending the message to right so we won't get anything from the back end uh we could add some sort of information that the backend sends to confirm that the message has been sent successfully but again this is this is extra work um yeah encourage you to to experiment again i might do it myself but off the camera this is again extra work to be done now i just trust the back and server to do it for me i shouldn't though but yeah let's continue so that's message type equals message this is when we receive the message so this is all this is going to happen on the other computer right in the other window in in my case when i'm i'm going to be testing it and what should happen for this one is basically setting the messages with the actual value of messages like all of the messages that we already have there but we're going to merge that array with that new value we've just received and that new value that we just received has to contain all of these fields i'm not sure if this is all sent from the from the back end so we may have to fill the fill the gaps let's have a look so when the message is sent to someone okay i think that message has everything that we need yes that message have it has everything that we need so this makes it way simpler we can just do message dot value and that's going to be dot message but again i have to do the trick with us message is message and that should basically be just a message which is incorrect i'm just looking at it and ah hang on there's a messages that's our message okay sorry it was my mistake um so yeah this is the message that we're receiving yeah it is different we only have a sender and the message itself so let's fill fill the gaps so we only have message as a message and we have sender as a string so that has to be different let's just received message let's use the const var so that's what we're going to receive yeah let's send it and message on the value so that's our received message and what we need is the message itself that's something we do have so received message message that's fine then we have sender we also have that i think these these two are the only required for displaying all of this so that's sender and we also need a created art but created at that should be simple as well that's because we can use date get time and now we have two missing ones which is um you know what maybe i just modified the back end that's going to make it simpler can i do it though um so what i'm getting when i do handle a send message yeah we have access to all of the information from the backend we have message id created ad like all of this can be just sent as a message so i think yeah let's improve it slightly so i'm gonna go back to the code i had before because it's simply less work if i just modify it on the back end so that is going to be message indeed but i need to modify the back-end that's that's pretty straightforward what i have to do on the back-end side that's handlers.ts line 245 is to create a const var called message which is going to be our item that we put right we insert to the messages table and then when we post it that's what i can just do so that message that's been created in that table is going to be sent to the client to the front end let's deploy it okay deployed let's have a look at the front end now and let's give it a quick test so what i'm gonna do is to open inspect because at the moment this is the only place we can see the message received from the from the back end i'm going to refresh it i'm in the chat click on myself to change the target that's important if i don't do that it may it might fail and then i'm gonna type something like hello and press enter and yeah there we go we got a message and that message is hello right that's john to john so i send it to myself what happens if a new person new user joins the chat maybe tom so now we have two of them let's see who is who okay so let's send a message to john so i click on the john yeah by the way that should change to to john i will sort it out and let's send a message let's send hi and here we can see hi i'm pretty sure that's the high message which it is okay so sending the messages works let's implement the view to display them i think this is the last step which is here really the conversation component and we will have the chat ui sort of done right it's still going to be far from down from done if we want to have the same functionality as the facebook messenger has but for the simple chat the goal of this video we're going to have it done and then the only thing i'm gonna have to do is to deploy this this front end to s3 to aws s3 and we should see it working on the internet in the browser not just locally so yeah let's have a look at a conversation now so yes a conversation i think there is some props missing we we have messages we have send message so yeah send message we can we can make that action messages list good we don't know in the context of conversation who is our target that's something that has to be displayed there so that would be target nickname string target nickname that is required let's pass the target nickname here i think this is target nickname value but we can keep it as a target nickname in conversation and this is where uh yeah instead of having anderson van roan we want to display target nickname okay yeah at the moment there is no target nickname i need to click on one and then we can see john so that's how it works which is not perfect but i'm going to fix it and apart from that we also need to know yeah at this point i need to know like in the conversation we need to have an information who is the user of that of the chat so yeah that's just you know speaking to myself but we also need to know who is like me in this case so that will be just a nickname so not target nickname but a nickname so let's pass nickname as well and let's require that nickname let's string okay so we have nickname target nickname we might not need one of them but yeah i will see i think i need to do the comparison because for displaying these like myself the user on the left the other user on the sorry the user on the right the other user on the left we need to have that information in the component one more thing i just see these icons we don't really need them the search the the heart and the bell so let's find them i think it's this one these three buttons is it this one yes okay that's correct we don't need that and let's add some margin to the image itself that should be actually um left okay that's better yeah this is status icon there is something i'm going to remove because we also don't have that functionality implemented we don't know who is active and who is not active you know a real time we don't have that real time information it's another feature that could be implemented by the way and finally is to display all of these messages yeah with these messages it is a bit tricky because as you can see they sort of grouped in this design and this is how the facebook messenger works as well so if one person sends two messages there is one and under the next one so they like sort of grouped so we can have like a group with just one message by one user but we also can have a group of four messages by the same user okay so that will be first logic to implement is to group all of the messages based on the users so i guess the approach would be to go through all of them like iterate over every single one on the messages and have a different array of objects where we have a sender in that object we have a sender and then we have a messages of that sender okay let me just show you an example of it so that would be something like let's say grouped messages and that will be just an example so group messages and let's say we will have a sender and that's going to be the nickname of that sender and then messages and this is where we just going to have um yeah like hello hi just the content of the messages can be just one message can be multiple and then you know next one again is going to be center maybe john and so on and so forth so that's that that's the the idea of it and that's how i need to um group the messages i'm i have uh i have i have here right received in the prop called messages with tyscript is going to be pretty straightforward because we already have the type on the messages and i think what i could just do is to try a reduce function so this is where we have um this function that gets the the accumulator and then the current that would be the current message in the context of messages here we pass the initial value so that's going to be array of this grouped messages object and what's going to happen at every single iteration step is to is to check the previews the previous grouped message you know what i'm just going to explain it slightly better with an example so let's say we we've got these grouped messages i mean that's the goal it's a grouped messages so that's how it's going to look like at the very beginning it's an empty array and then uh for example let's have a a messages table the messages sorry a messages array and then in the messages array there's going to be like a sender there's going to be message itself there is also like a message id and all sorts of other options but we don't really care about them we know they are ordered by created art that's the back end doing it for us so i think this is just these two pieces of information that we need uh maybe a third one which is the target nickname so we know the the target nickname is is not the p is the like if the target nickname is the sender that means we want to display it on the left otherwise you want to display it on the on the right but that's gonna be for the um here in the in the the html that we display is going to be making the decision but yeah let me just add another sender let's say it's again tom and the message is hello yeah for the sake of simplicity i'm not gonna use the double quotes and then another one but this time the center is john a message is like hi there okay and then what we wanna do is to from this array build array of with two objects the first object is just gonna be a sender tom with messages high and hello and then there's going to be john there's going to be another object with sender john an array of messages we've just hide there so that's the point of it and in order to be able to do that with reduce we're going to need to check the previous record on that array right i mean it's not going to be really a previous one because we just want to check the current one on the accumulator i think that's that's what we want to do so you want to compare if the the current the last item in the accumulator array this one has the same sender as the current which is the current message there is one extra condition though before we can access the last element of our accumulator what i have to do is to check if there is anything in there so accumulator has to have a length greater than zero otherwise there is no point to in checking we can we can essentially create uh create that new object in accumulator straight away so yes so that's that's that's what we want to check and then if it is greater than zero i can check the accumulator with the accumulator length minus one so that will be the the last element on accumulator and i want to check if the sender is the same as the current sender okay and if it is then i just want to add to that accumulator i just want to add two messages just push current message and that's it yeah the the the types are gonna fix themselves in a few seconds once i return so that's what's gonna happen um otherwise i just want to push a new item a new element to accumulator with a sender being current sender and the messages being just this one message so that's going to be an ri with just one message so that's how this is going to work and then i'm just going to return that accumulator um yeah i think i still need to sort out the types here and yet this is a mistake we want to add it to the last element and i think i need to define the type on the on the reduce let me just review it again so so yes so what's going to happen the first iteration this is empty so um this is not going to be executed not this part of the condition but this part of the condition because our rcc acc accumulator is empty we're just going to push a new object there with the sender and current sender that's the current sender so what's going to happen is this right we will have a sender which is tom and messages which is just high okay that's the first step then the second iteration this is this is gonna be the current that we get so what we're checking is if acc length is greater than zero now it is because that's been just you know the first value has been pushed there and because it is we're checking if the sender of the previews of the last essentially the last element in this new acc array the sender equals the sender of the current element and because the current element sender is tom and the previous element in acc is also tom what's going to happen is to the last element which is this one the acc to the messages property which is an array we're going to push just the current message so that's just going to be the hello so that's what's going to happen after the second iteration and then the third step the third iteration is this one but in this case that condition is not met because senders are now different so we push a new element with a new sender and justice one a message yeah let me fix the types i think uh there is something wrong with that so yeah the the reduced type is um it is an array i think that's what we want to do where we have a sender which is a string and we've got messages which is a string array and that should fix that should fix it so this could be the declarative uh this could be done in declarative way so i could use the spread operator and every single time written a new array for the for the acc but you know this one is is more efficient because you know spreading is is is always like adding more operation like increasing the time of it so i'm going to keep it as it is like that hopefully this is going to work so we are grouped messages now we should have them grouped by yes send it in messages exactly that yes so that would be a after the third iteration we would have something like that like that and then we just hide there okay and that's just going to apply to all of these messages if i just refresh i can't really i can't really see it yet okay so yeah this part the top is sorted then this part let's let me just find these sections so um this is on the messages and we've got the chat message that looks like a chat message is a single maybe let's use the chrome developer tools for that so chat message is this okay so that's that whole thing is chat message perfect okay so the whole group is really a chat message so how we want to do it is let's steal one of the chat messages maybe let's remove the first one we don't need that okay the second one is not needed either but this is nice because that's the whole group with multiple messages so that's how single message from the from the set from the other user is going to look like so um let's use the grouped messages for that so that will be a group messages map so let's just do that group messages map and then we will have grouped message uh i'm not sure if this is a good name though grouped messages they really grouped but maybe group would be better name for a single item and this is where we're going to just display that so there's a single group and then what we've got is oh yeah now this seems to disappear that's probably because yeah okay that that looks like like we've got the message over there so yeah that's a group and now for that group what i want to do next is to display these messages like they are displayed over there so we have that i think this is a single message isn't it no that's a single message yeah that div is a single message so let's just use that let's do group dot messages map and that will be message and that is a single message isn't it so that's what we want to display for a single message we don't need all of those anymore so i think yeah that's not needed and this is gonna be the message content as the content so this is where you want to really display that message and finally there is an image there is an image for now i'm just going to keep that image that we've got from that from that component from the tailwith component as a default let's see what happens if i just click on one person okay we can see the hello that's the hello message i sent to myself a few minutes ago nice although there has to be some extra logic in the in the grouped messages now because yeah that's that's an example of a message sent from someone else so this is when we want to um i guess what we want to do is to display this block of code only if the group sender equals target nickname so this is what we want to display that's exactly what we want to display and then if it's if it's all the way around it's going to be this chat message that's gonna be this one which is a slightly different styles i i assume yeah that's correct so that's what we're gonna display instead and again what we have to do there is to use the group messages map for every single i think it's that at that level right so that is we've got chat message and then one two more level of nesting and then on the div without anything this is where the the actual message is so that's where i'm going to add that then close the the bracket like that and that should do it um is there something wrong about it there is something wrong about it maybe let's go back maybe let's go back and let's just copy this and paste again oh yeah okay i think i know what it is now it's correct okay then the image and that closes so we don't need any more chat messages we don't need those anymore now it's just that hello message over there um yeah it seems like it's not really correct it shouldn't be at the center uh i i'm gonna fix it in a second but let's add that message itself again yeah there is some duplication unfortunately if you look at this behavior but i think there is a significant difference in the in the styles so maybe here to not make the video even longer i'm not gonna refactor this so i'm just gonna keep it as it is i will try to fix these styles but let's see if i can send some messages between two users and let's see how this is going to work so yeah now we have two users and let's just uh change this one to tom and this one to john okay we can see high and that high is it should be from from myself so yeah if i do again okay yes so it looks like that works on this side of things like when the message is sent but we all we still need to have some sort of logic to add that message we've just sent as a user as a sender right uh yeah otherwise this this is what what's only only works if i refresh i'm pretty sure when i go to john that is correct this is how it's supposed to be ish um yeah another issue as you can see this is not the right order we should see the latest message on the on the bottom not the other way around so we may need to change the way this index is scanned uh on the on the back end or maybe you know what let me just quickly fix it on the front end with this simple trick so when we set the messages that that is correct it goes to the end so that's fine but when we send these messages let's just call reverse if i call reverse then they should be in the in the different order so now if i send a message yeah that goes at the end yeah let's fix uh displaying the message when it is sent so that should that should just be send message a function what has to happen is we need to add a new message at the end of messages and this is where i have to fill the gaps with the yeah we've got the message itself we've got a sender which is just the nickname we can have created art that's also simple just let's get the current timestamp then message id this is not really important what that message id is going to be i could use v4 on this site as well but for now maybe let's let's just go with the random value i think that's just math random it doesn't really matter at this point and the nickname to nickname i think that has to be a string though and then nickname to nickname that should be pretty straightforward because what we need to do i don't think i use nickname to nickname but um i can essentially just get um my nickname target nickname value sort them and join them by hash so that's just the functionality from the back end but replicated and yeah if the and if this happens i'm pretty sure that it's just going to work as it's supposed to work so if i just type 2 here we can see the message on the screen straight away and that message has been sent to the other person yep as we can see this is all nice now few more things to sort out and that is a default target so when we refresh we don't have any any any chat window any target nickname uh set so let's just use the the last one and then when we open oh yeah that's still not visible but if i produce some more messages oh okay there is something uh there's some bug i've just found so yeah apparently um these messages get displayed anyway regardless whether um we've got the right we've got the right window that the right uh at target nickname so that will be another thing um from this few fixes list as you can see this is this is how the working with software looks like let me just fix that so if i go to um a message here that's the message so we only want to set messages when the message value and yeah this is where it gets tricky so maybe what i'm gonna do is just um you get something like yeah do something like that so we will get uh get a message message value is gonna be this then message value a message but we only set it if the message value the message sender equals to our target nickname value okay so this is important otherwise we will get all the messages from everyone in a single um in a single conversation right so regardless of what conversation you have oh you will have opened um you will get all of the messages from everyone if you don't have that extra check that i've just added so yeah so there's one thing um to fix then um the i'm pretty sure there is a let me just produce some more messages i'm just gonna show you but i can see it right now when i type new message and press enter that should scroll to the bottom that's an easy fix if i go to conversation what i can do is at the very bottom of the messages let's let's just find the last message this is right here so just before the close i'm gonna just create a special div and i'm going to create a ref here at the top of the component left like this messages and ref and i'm just gonna add that ref to that div and then i'm gonna add a function scroll to the bottom and that function is going to call on that ref current into view and we can add some smooth behavior actually and then let's do a use effect where we just scroll to the bottom of the at the very beginning and then when we have a submit which is going to go right after that let's just also scroll to the bottom so yeah that should that should do that should fix it uh let's have a look yeah you see now this is that that the scroll has happened and if i just send something that what happens as well so that is assorted we have a smooth scroll to the bottom the default target nickname let's fix that now so i'm gonna go to app.tsx and this is really about setting a new local storage item let's call it last target nickname and that's just gonna be our target nickname value and then for the target nickname value we're just going to follow the same approach from the nickname obviously there has to be a different local storage item a name now so that should fetch that that value from from the local storage if it is there if it's not there so that's the first time when we just join in the chat we opening the chat for the first time we have to provide our nickname what we're gonna have to do is to set some sort of a default value for that target nickname and i'm just thinking the best place to do that would be here that's the set nickname a function that we pass to we pass it to the welcome component so i can just add an extra line of code that checks if our target nickname value is empty and if it is empty we're just going to set that target nickname value i think this is not really gonna yeah i think that yeah that should be fine so set target nickname value to nickname okay that should do it although let me test it so if i refresh right now yeah the john is a default one we don't see any messages but i don't think i send any messages um this might be not handled as uh this might not be handled yet i'm just thinking just realized let's let me remove the local storage first let's see if this is working on the if the if that nickname is displayed as a default one when we join the chat so i'm gonna go i'm gonna join us thomas join yeah that is correct um let's see if i send a message to myself oh yeah now we've got two messages basically which is sort of correct right when we send the message to ourselves yeah um yeah that's the problem we don't load the messages at the very beginning and this is true because only that happens only when let me just find it set a target nickname is called that is only called from the sidebar component this is when this is called right and um yeah what i could do is to move this up and then call it from from here but this is not going to work and i tell you why this is not going to work this is because we don't know if the websocket connection has been established it it probably it hasn't because changing that nickname is just going to initialize that connection we might have some race condition or this might fail anyway so to make sure that the messages get loaded for the first time when the chat is like it's not very first time this can be your second time but i'm just saying at the beginning when you open the website and this is where that is supposed to happen this is where we need to load the messages so yeah we know at this point that there is going to be a target nickname value set so if we know that we can just load the messages for that target nickname like we do it here okay and to not duplicate myself what i'm going to do is to create a function let's add it there right before um light before right before on open maybe maybe here ah we can't really do it here that has to be that has to be here let's just call it load messages load messages and this is where we just sent that get messages with a target a nickname um us as a nickname but this is the nickname passed to that function and then um in that place where we actually call it instead i'm just gonna i mean when we used to send that message to the back end just gonna call that function instead with that nickname and that and then on the uh on open on open this is when we want to load the messages but this time we have a target nickname we have a target nickname value because this only happens once at the very beginning we're just going to load it once so this is this is sort of like you know just get clients and get get me the the messages between if i'm just you know opening the chat for the very first time get me all the messages with myself there's not gonna be any messages but then when i open the shot for the second time just you know give me the messages between myself and and the user i spoken to last time so yeah let's refresh that now any of that loads if i change thomas to tom which is still here i think hey one two three and then i refresh i see hey one two three yep now let's fix this should be aligned to the top right that should start on the top and then go to the bottom it shouldn't start like at the center so let's fix that quickly and i think we're getting to we're slowly reaching the finish line so let's just have a look at all of these messages i think this is where that happens so we've got flex flex column and that probably is um items i think this item start ah that's not right we don't want that on this side of things which is weird because that's flex column so it should be aligned to the top really ah that's messages right yeah that's messages that's just a single chat message so that messages we go one level up oh okay yeah that should be on the messages yeah that should be on the messages i think we just want the height of all of these messages to be aligned with the screen one right yeah that's what we want to do with the view so now that should work okay um so i think this is pretty much it um let me quickly change these icons so it it it's going to look nicer okay we have some new images however i need to change something first so this image that's going to point to public and dogos and this is going to be um depending on yeah that's that's that's the oh yeah um let me just change that it should be this and that is why i needed a nickname so that will be nickname dot jpeg then the same is going to be applied to there but this time this is not going to be that nickname but that is the group sender exactly the same for this one and then in the sidebar we're going back to nickname i think it isn't uh is it a nickname oh we don't really have the nickname um let's pass the nickname there so sidebar let's add the nickname as well nickname and that's it yeah we don't see it now because i've got the wrong i've got the wrong nickname let's change that yeah i think this is pretty much it and yeah let's just remove that nickname from the local storage and now this is going to be a final test ah there's still something not right um let me let me fix it that should be public okay this is better now um let's get rid of these two clients these are old clients so let's see i'm just gonna close that window oh yeah okay we still got that one wrong that is on the sidebar let me just find it so here is this one and this is the one that should be a client nickname okay this is the client nickname okay that's better and let's test let's do the final test right now so i'm gonna open another incognito window maybe i move the code somewhere here yeah this is only needed to see what sort of nicknames i can use and let's try this out so we can have a dodge as you can see and let's add another incognito window ah it looks like here the session is shared right now between incognito windows unfortunately two of them is max but yeah let's let's try to have a conversation so hi that's to myself if i speak to the other user hi okay that's another feature right we could have some sort of notification but here once i click here i should see that hi and then i just respond hello yep how are you that all works maybe let me use another browser there we go okay now we've got a three three clients and let's try to um maybe let's try to send a message to chi hello [Music] yeah we don't get that message until we click um there uh it's not from here that should be from here so we've got hello i can see one improvement because what happens is we've got like this flicker because what happens we've got like this flicker of messages because there's slight delay between the server and the client so we can go to up and then in the app when we load the messages i think this is where it's happening um no actually this is not when when we load them here but this is this is right before we load the messages right so this is the place where we can set the messages to empty before we load them so that's gonna just you know um remove all of the existing messages before loading new ones um this might be a good idea of course better idea is to add some sort of spinner but yeah again this is just adding additional features to to this chat good um yeah let me just uh try this now so um where is my firefox window there it is um yeah let's just send some messages and that's how it works right right so the chat is working and final step is the deployment of the front end this should be quick i promise what we need to do is to run build okay the build done and now what we need to do first of all i'm going to run following command attach that is to create a file so i'm going to create that is going to be a file in a tmp folder let's call it a bucket policy that's what i actually need to do i need to create an s3 bucket but it needs a policy and i'm just going to open it with visual studio code not like that then just a short bit of configuration that is not the right double quote this is basically the policy for the bucket so we say that the serverless chat ui bucket that i'm going to create um has a public read access so that's the required policy and now i'm going to run the following command so the first one is to create the bucket let me show you so it is this one creed bucket and the name of the bucket server is chat ui oh there is an issue with region i think i need to specify the region as us east one and there we go okay so the bucket is created i'm pretty sure if i do aws s3 uh ls i will be able to find it which is just said of a less okay yeah there's many of them but there is a serverless chat ui right so that has been created the next step is this command that is to set the bucket policy for serverless chart ui and that's the bucket policy file this one json file okay that seems to that seems to be working by the way if you see a screen like i've just i've just had before you just need to press q to exit it then two more commands so and now it's the same sync part so um this is where i have my build in the build folder so what i want to do is to synchronize a build folder with s3 serverless chart ui bucket that should take okay that was quick actually i thought it's gonna take longer and finally i'm gonna turn this bucket into a static website so aws s3 website where i had to specify the bucket name again the index document and error document i'm gonna keep them both as index.html so let's see yeah that seems to be done and now that a website should be accessible under this url which is which is the bucket name which is serverless chat ui dot s3 website dot region which is us east one this is the region i created the bucket in and then amazon aws.com when i press enter i should see the chat window so let's connect maybe a shiva and there we go we've got shiba here we also got samoa this is because uh we've got my local uh my local instance connected to the same server so the server is shared now um so yeah basically what i can do now is to send a message to to to let me just show you oh yeah that is not um that is not on anymore but i think the browser just kept that yeah the browser just kept the connection so i have to go to yeah open a new window and just go to that website again uh-huh and that should be um let's just do maybe as a hamster okay so yeah now we've got sheba and hampstead and let's send a message nice yeah as you as you can see this is working it's online and it's gonna be online for some time so you can play around if you want and this is it for today if you are interested in more content about building projects with serverless framework check out this video about creating and modifying aws s3 files with aws lambda thanks for watching and see you in the next video bye