<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hayatudeen Abdulrahman</title>
    <description>The latest articles on DEV Community by Hayatudeen Abdulrahman (@hayatscodes).</description>
    <link>https://dev.to/hayatscodes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F847062%2Fd393f8d8-ec45-4de7-8923-ebc60a744f3e.jpg</url>
      <title>DEV Community: Hayatudeen Abdulrahman</title>
      <link>https://dev.to/hayatscodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hayatscodes"/>
    <language>en</language>
    <item>
      <title>API Testing 101: A Beginner's Guide to Testing Nodejs APIs with Jest and Supertest</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Fri, 28 Jul 2023 17:28:50 +0000</pubDate>
      <link>https://dev.to/hayatscodes/api-testing-101-a-beginners-guide-to-testing-nodejs-apis-with-jest-and-supertest-2e4</link>
      <guid>https://dev.to/hayatscodes/api-testing-101-a-beginners-guide-to-testing-nodejs-apis-with-jest-and-supertest-2e4</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
Software testing

&lt;ul&gt;
&lt;li&gt;What is API testing?&lt;/li&gt;
&lt;li&gt;Importance of API testing&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Types of Testing

&lt;ul&gt;
&lt;li&gt;Unit testing&lt;/li&gt;
&lt;li&gt;Integration testing&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Testing frameworks and libraries&lt;/li&gt;
&lt;li&gt;Express server setup&lt;/li&gt;
&lt;li&gt;Testing the book API with Jest and Supertest&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In today’s fast-paced software development world, testing is becoming an essential part of the &lt;a href="https://www.tutorialspoint.com/sdlc/sdlc_overview.htm"&gt;software development life cycle&lt;/a&gt;, and with the increasing rise of web and mobile applications, bug-free, reliable, and efficient APIs are crucial to meeting various business needs.&lt;/p&gt;

&lt;p&gt;This guide will provide a starting point for Node.js developers who want to understand the fundamentals of testing and writing API tests for backend applications using Jest and Supertest.&lt;/p&gt;

&lt;p&gt;We will cover topics like software testing, types of testing, testing frameworks, and a step-by-step walk-through of a sample test code for a basic create, read, update, and delete (CRUD) operations API.&lt;/p&gt;

&lt;p&gt;To follow the guide completely, you’re required to know the fundamentals of building an API with Node.js as well as any Node.js framework (e.g., Express.js, Nest.js, etc.). However, you can follow the first few sections before the code section with little-to-no knowledge of backend development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software testing
&lt;/h2&gt;

&lt;p&gt;Firstly, let us know what software testing is before proceeding to understand API testing. According to a post on &lt;a href="https://www.ibm.com/topics/software-testing#:~:text=Software%20testing%20is%20the%20process,development%20costs%20and%20improving%20performance."&gt;IBM&lt;/a&gt;, software testing is the process of evaluating and verifying that a software product or application does what it is supposed to do. It involves providing edge cases to test that the application works as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is API testing?
&lt;/h3&gt;

&lt;p&gt;It’s simply a form of software testing that evaluates and verifies that an API functions as expected. It is done to test the API’s security, performance, and reliability. It generally involves simulating a request to the API endpoints to test that the responses match the expected results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importance of API testing
&lt;/h3&gt;

&lt;p&gt;According to a survey conducted by &lt;a href="https://static0.smartbear.co/smartbearbrand/media/pdf/smartbear_state_of_api_2020.pdf"&gt;SmartBear&lt;/a&gt; in 2020, 90% of organizations said they either currently have or plan to have a formal API testing process in place in the near future. This highlights the importance of API testing and the shift towards it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tykx8zfkca4zot1p5rz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tykx8zfkca4zot1p5rz.png" alt="A 2020 survey by smartBear illustrating the importance of API testing in business organizations" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A 2020 survey by smartBear illustrating the importance of API testing in business organizations.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The following are more benefits of API testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It ensures that the API returns correct responses every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It helps to identify security vulnerabilities to prevent the API from being compromised by attackers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It allows for detailed and accurate documentation of the API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It can help identify performance issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It helps to deliver a high-quality API with fewer bugs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Types of Testing
&lt;/h2&gt;

&lt;p&gt;There are many types of testing, but we will be looking at the two most common types of testing for backend developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit testing
&lt;/h3&gt;

&lt;p&gt;Unit testing is a type of testing that focuses on testing individual components of a codebase. In API development, unit testing is used to test individual routes, middlewares, or functions without considering their interaction with other components of the system like databases, caching systems, internal services, or other external services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration testing
&lt;/h3&gt;

&lt;p&gt;Integration testing is a type of testing that tests logically grouped modules together. It also focuses on testing the interaction between different components or services of an application. In API development, integration testing is used to test the interaction between the API’s routes, databases, middlewares, or functions with each other as well as other internal or external services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing frameworks and libraries
&lt;/h2&gt;

&lt;p&gt;Testing frameworks and libraries are software applications or packages that are used to perform testing in software development. There are several testing tools and libraries for different purposes and programming languages. However, the common testing frameworks and libraries for Node.js applications and APIs are listed below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jest&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supertest&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mocha&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Superagent&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s take a look at what Jest and Supertest do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jest&lt;/strong&gt;: &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; is a testing framework or tool that is used to write tests for any JavaScript codebase, including Node.js projects. It’s easy to use, well-documented, has little-to-no configuration, and can be customized to meet one’s needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supertest:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/supertest"&gt;Supertest&lt;/a&gt; is a testing library that is used to test Node.js APIs. It is best used along with a testing framework like Jest or Mocha.&lt;/p&gt;

&lt;p&gt;In the next sections, we’ll be looking at how to use Jest and Supertest to test a Node.js (Express) API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Express server setup
&lt;/h2&gt;

&lt;p&gt;In this section, we’ll be developing a book API that allows for creating, retrieving, updating, and deleting books from a database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NB:&lt;/strong&gt; The code in this section was created only to demonstrate the basic concepts of testing an API and is, by no means, an example of how a modern web API or test should be made or structured.&lt;/p&gt;

&lt;p&gt;Before we proceed, make sure you have &lt;a href="https://nodejs.org/en"&gt;Node.js&lt;/a&gt; installed on your machine.&lt;/p&gt;

&lt;p&gt;Follow the steps below to set up an Express server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Firstly, create a new directory for the book API and open it in your preferred editor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to the directory in your terminal and run &lt;code&gt;npm init -y&lt;/code&gt; to initialize a new Node.js project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In your terminal, run &lt;code&gt;npm install express mongoose&lt;/code&gt; to install the express and mongoose packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now, create a new file named &lt;code&gt;server.js&lt;/code&gt; and add the following code to it:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongoose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a schema for your data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Create a model based on the schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bookSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create an Express app&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server started on port 3001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new book&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;genre&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;genre&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error creating book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Get all books&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error retrieving books&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Get a single book by ID&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error retrieving book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Update a book by ID&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;genre&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByIdAndUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;genre&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;new&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error updating book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Delete a book by ID&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByIdAndRemove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error deleting book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start the server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server started on port 3001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongodb+srv://technical-guide:uQxSkFkzgx29r3E8@test-cluster.ne5tsho.mongodb.net/book-api?retryWrites=true&amp;amp;w=majority&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;useNewUrlParser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;useUnifiedTopology&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file, return to your terminal, and run &lt;code&gt;node server.js&lt;/code&gt;. You should have something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10o0mtfezo7rid69q8i2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10o0mtfezo7rid69q8i2.png" alt='Terminal interface showing "Connected to MongoDB"' width="783" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ve successfully created a basic CRUD API with Express.js and MongoDB.&lt;/p&gt;

&lt;p&gt;Since this guide is mainly focused on testing, I will briefly explain what the above API does without being too detailed.&lt;/p&gt;

&lt;p&gt;The book API allows users to interact with a collection of books. It establishes a connection to a MongoDB database and defines a schema for the book data. Users can create a new book by sending a POST request with the book's details, retrieve all books with a GET request, fetch a specific book by its ID using a GET request with the corresponding book ID, update a book's information by sending a PUT request with the updated details and the book's ID, and delete a book by its ID using a DELETE request. And the server runs on port 3000.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the book API with Jest and Supertest
&lt;/h2&gt;

&lt;p&gt;To start testing the book API with Jest and Supertest, we would need to first download the two packages by navigating to the project’s directory and then running &lt;code&gt;npm install --save-dev jest&lt;/code&gt; and &lt;code&gt;npm install --save-dev supertest&lt;/code&gt;. The &lt;code&gt;--save-dev&lt;/code&gt; flag is used to install the packages as development dependencies.&lt;/p&gt;

&lt;p&gt;Next, create a file named &lt;code&gt;server.test.js&lt;/code&gt;. This is where our test code will live. We’re saving the file with a file extension of &lt;code&gt;test.js&lt;/code&gt; because Jest recognizes files with this extension as test files.&lt;/p&gt;

&lt;p&gt;Now, copy the code below into the &lt;code&gt;server.test.js&lt;/code&gt; file (the explanation comes briefly).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;supertest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongoose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book API Endpoints&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should create a new book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Genre&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Genre&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;bookId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should get all books&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should get a single book by ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/books/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Genre&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should update a book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/books/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Genre&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Genre&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should delete a book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/books/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Updated Genre&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go through the test code.&lt;/p&gt;

&lt;p&gt;We import the necessary modules, including request, a Supertest method that allows you to interact with your server as if it were running and receiving real HTTP requests. We’re also importing &lt;code&gt;app&lt;/code&gt; from our &lt;code&gt;server.js&lt;/code&gt; file and &lt;code&gt;mongoose&lt;/code&gt; for database operations.&lt;/p&gt;

&lt;p&gt;The Jest &lt;code&gt;describe&lt;/code&gt; function is used to group related test cases. In this case, all the test cases are related to the Book API endpoints.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;afterAll&lt;/code&gt; hook provided by Jest is used to close the server and disconnect from the MongoDB database after all the test cases in the test suite are executed. This ensures that we clean up resources after testing; otherwise, Jest wouldn’t exit after the test completes.&lt;/p&gt;

&lt;p&gt;Each test case is represented by an &lt;code&gt;it&lt;/code&gt; block, which contains the test name and the test code.&lt;/p&gt;

&lt;p&gt;The test cases use Supertest to make HTTP requests to the API endpoints and check the responses against the expected results using the expect function.&lt;/p&gt;

&lt;p&gt;In the first test case, we create a new book by sending a POST request to the &lt;code&gt;/books&lt;/code&gt; endpoint and checking if the response status code is &lt;code&gt;200 (OK)&lt;/code&gt; and if the response body contains the correct book information.&lt;/p&gt;

&lt;p&gt;We save the &lt;code&gt;bookId&lt;/code&gt; from the response for later use in other test cases.&lt;/p&gt;

&lt;p&gt;In the second test case, we fetch the book that we created in the previous test case. We do this by sending a GET request to the endpoint &lt;code&gt;/books/:id&lt;/code&gt;, where &lt;code&gt;:id&lt;/code&gt; is the &lt;code&gt;bookId&lt;/code&gt; of the book we created in the first test case.&lt;/p&gt;

&lt;p&gt;In the third test case, we update the book's information. We do this by sending a PUT request to the &lt;code&gt;/books/:id&lt;/code&gt; endpoint with the updated book data. Again, we use the &lt;code&gt;bookId&lt;/code&gt; variable we saved in the first test case to target the specific book we created earlier.&lt;/p&gt;

&lt;p&gt;In the fourth test case, we delete the book that we created in the first test case. We do this by sending a DELETE request to the &lt;code&gt;/books/:id&lt;/code&gt; endpoint, using the &lt;code&gt;bookId&lt;/code&gt; variable to target the specific book.&lt;/p&gt;

&lt;p&gt;Now, we have everything set up to run the tests for our Book API. To do that, we need to add a test script to the &lt;code&gt;package.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest --testTimeout 20000"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the "scripts" section of the package.json file, we add a "test" script that runs Jest with a test timeout of 20,000 milliseconds (20 seconds). The test timeout is essential to allow enough time for the API tests to complete because they involve interactions with the database.&lt;/p&gt;

&lt;p&gt;To run the tests, open your terminal and run the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Jest will execute the test cases in the server.test.js file, and you should see the test results in the terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F631swp0ku68mpd3c3sfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F631swp0ku68mpd3c3sfr.png" alt="Terminal interface showing the 5 passed test" width="720" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hurray. The test passed. Congratulations on writing your first test!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, this beginner's guide has provided you with a solid starting point for understanding API testing and how to test Node.js APIs using Jest and Supertest. To further enhance your skills, I encourage you to explore additional resources available online, including the official documentation for Jest and Supertest. Also, Practising with various testing scenarios will help you gain confidence and expertise in API testing, making you a more proficient developer in today's software development landscape.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>node</category>
      <category>testing</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Socket.IO Authentication System With JWT</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Thu, 08 Jun 2023 20:19:43 +0000</pubDate>
      <link>https://dev.to/hayatscodes/socketio-authentication-system-with-jwt-3ae8</link>
      <guid>https://dev.to/hayatscodes/socketio-authentication-system-with-jwt-3ae8</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;
The Authentication System

&lt;ul&gt;
&lt;li&gt;Database Model&lt;/li&gt;
&lt;li&gt;Express Authentication Handlers&lt;/li&gt;
&lt;li&gt;Socket Server Setup&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Client-Side Connection&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are few resources to explore when it comes to authenticating &lt;code&gt;socket.io&lt;/code&gt; connections. I recently finished a programming project that involved authenticating socket connections. During the project, I tried my best to search for resources that explained how to create an authentication system for &lt;code&gt;socket.io&lt;/code&gt; connections, but to no avail. They were either not useful for my project's use case or were unclear. With some effort, I was able to build a secure authentication system for the server so that every connecting client will be authenticated before a connection is established.&lt;/p&gt;

&lt;p&gt;In this article, I will be taking you through the step-by-step process of building a secure &lt;code&gt;socket.io&lt;/code&gt; authentication system with JSON web tokens (JWT). The knowledge can also be transferred to other authentication libraries, like &lt;code&gt;passport.js&lt;/code&gt; or other programming languages' authentication libraries, as I will be using NodeJs.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;I will be explaining every detail of the example codes to let everyone follow along regardless of the tech stacks they are familiar with.&lt;/p&gt;

&lt;p&gt;However, we will build the authentication system using a MongoDB &lt;code&gt;User&lt;/code&gt; collection, an &lt;code&gt;http&lt;/code&gt; server, an Express &lt;code&gt;app&lt;/code&gt;, and a &lt;code&gt;socket.io&lt;/code&gt; server instance. Additionally, I will provide a sample client-side code to demonstrate its use case.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;User&lt;/code&gt; collection is used to store and retrieve the users' credentials.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;http&lt;/code&gt; server is used to listen to HTTP and socket connection requests.&lt;/p&gt;

&lt;p&gt;The express &lt;code&gt;app&lt;/code&gt; is used to set up function handlers that handle the registration and log-in authentication endpoints. A response containing a JSON web token (JWT) is expected on successful authentication.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;socket.io&lt;/code&gt; server instance is responsible for managing socket connection events. A middleware function is utilized to validate the JWT sent by the client, ensuring that only authenticated users make socket connection requests.&lt;/p&gt;

&lt;p&gt;The client-side code is used to make HTTP requests to the authentication endpoints and stores the JWT response, which is then used to make web socket connection requests to the &lt;code&gt;socket.io&lt;/code&gt; server instance.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Authentication System
&lt;/h2&gt;

&lt;p&gt;In this section, we build the authentication system. From the database model to the &lt;code&gt;socket.io&lt;/code&gt; server instance authentication middleware setup.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Model
&lt;/h3&gt;

&lt;p&gt;Let's start by creating the &lt;code&gt;User&lt;/code&gt; model schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: String,
    email: String,
    password: String,
  });

const User = mongoose.model('User', userSchema);

module.exports = User;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We import the &lt;code&gt;mongoose&lt;/code&gt; module, which provides the functionality to define and interact with MongoDB schemas and models.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;userSchema&lt;/code&gt; is created using the &lt;code&gt;mongoose.Schema&lt;/code&gt; constructor, specifying the structure and data types of the user object. The schema includes fields for &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt;, each of type &lt;code&gt;String&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;userSchema&lt;/code&gt; is then used to create a &lt;code&gt;User&lt;/code&gt; model. This model allows for interaction with the 'User' collection in the connected MongoDB database.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;User&lt;/code&gt; model is exported to make it available for use in the authentication system.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Express Authentication Handlers
&lt;/h3&gt;

&lt;p&gt;Here, we'll be creating the handler functions for the registration and log-in endpoints using Express.&lt;/p&gt;

&lt;p&gt;First of all, let's import the necessary modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require("models/user.model");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, the &lt;code&gt;express&lt;/code&gt;, &lt;code&gt;bcrypt&lt;/code&gt;, and &lt;code&gt;jsonwebtoken&lt;/code&gt; module are all being assigned to their respective variables. Additionally, the &lt;code&gt;User&lt;/code&gt; database model is imported to enable the storage and verification of user information.&lt;/p&gt;

&lt;p&gt;Next, we create the Express &lt;code&gt;app&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const app = express();
app.use(express.json());

// Register API endpoint
app.post('/auth/register', registerUser);

// Login API endpoint
app.post('/auth/login', loginUser);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We begin by invoking the &lt;code&gt;express()&lt;/code&gt; function provided by the express module, which allows us to register endpoints along with their respective handler functions. Additionally, we attach the &lt;code&gt;express.json()&lt;/code&gt; middleware to parse JSON request payloads. Two &lt;code&gt;POST&lt;/code&gt; methods are subsequently added to the Express &lt;code&gt;app&lt;/code&gt;, enabling it to handle client requests at the &lt;code&gt;/auth/register&lt;/code&gt; and &lt;code&gt;/auth/login&lt;/code&gt; endpoints. These requests are handled by the &lt;code&gt;registerUser&lt;/code&gt; and &lt;code&gt;loginUser&lt;/code&gt; handler functions, respectively.&lt;/p&gt;

&lt;p&gt;Next, we proceed to develop the logic for the &lt;code&gt;registerUser&lt;/code&gt; handler function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// User Registration Handler Function
async function registerUser(req, res) {
  try {
    const { username, email, password } = req.body;

    // Check if the username or email already exists
    const existingUser = await User.findOne().or([{ username }, { email }]);
    if (existingUser) {
      return res.status(400).json({ message: 'Username or email already exists' });
    }

    // Hash the password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Create a new user
    const newUser = new User({
      username,
      email,
      password: hashedPassword,
    });

    // Save the user to the database
    await newUser.save();
    res.status(201).json({ message: 'Registration successful' });
  } catch (error) {
    console.error('Registration error', error);
    res.status(500).json({ message: 'Registration error' });
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We begin by destructuring the &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt; from the &lt;code&gt;req.body&lt;/code&gt; object, which contains the request body sent by the client. Next, we perform a check for the uniqueness of the &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;username&lt;/code&gt; by searching for them in the database. If either of them is found, a &lt;code&gt;400&lt;/code&gt; bad request response is returned. Otherwise, the function proceeds to hash the password using the &lt;code&gt;bcrypt.hash()&lt;/code&gt; function provided by the bcrypt module. The hashed password, along with the destructured &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; properties, are then saved to the database for persistence. In the absence of errors, a &lt;code&gt;201&lt;/code&gt; created response is returned. However, if any error occurs during the execution of the handler function, a &lt;code&gt;500&lt;/code&gt; internal server error response is returned.&lt;/p&gt;

&lt;p&gt;Next, we proceed to develop the logic for the &lt;code&gt;loginUser&lt;/code&gt; handler function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ...

// User Login Handler Function
async function loginUser(req, res) {
  try {
    const { username, password } = req.body;

    // Check if the username exists
    const user = await User.findOne({ username });
    if (!user) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    // Compare the password
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    // Generate a JWT
    const token = jwt.sign({ userId: user._id }, process.env.SECRET_KEY);

    res.json({ token, message: 'Login successful' });
  } catch (error) {
    console.error('Login error', error);
    res.status(500).json({ message: 'Login error' });
  }
}

// ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first verify if the destructured &lt;code&gt;username&lt;/code&gt; exists in the database. If it does not exist, a &lt;code&gt;400&lt;/code&gt; bad request response is returned. Also, if the destructured &lt;code&gt;password&lt;/code&gt; does not match the user-stored password in the database, a &lt;code&gt;400&lt;/code&gt; bad response is returned. However, if the passwords match, we proceed to generate a JWT token using the &lt;code&gt;jsonwebtoken&lt;/code&gt; module. This token is generated by setting the &lt;code&gt;userId&lt;/code&gt; key to the value of the &lt;code&gt;_id&lt;/code&gt; property of the User, which is automatically assigned by MongoDB to each saved user information. Assuming no errors occur, a response containing the generated token is returned.&lt;/p&gt;

&lt;p&gt;Next, we export the Express &lt;code&gt;app&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ...
module.exports = app;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete code for the authentication endpoints can be found below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require("models/user.model");

const app = express();
app.use(express.json());

// Register API endpoint
app.post('/auth/register', registerUser);

// Login API endpoint
app.post('/auth/login', loginUser);

// User Registration Handler Function
async function registerUser(req, res) {
  try {
    const { username, email, password } = req.body;

    // Check if the username or email already exists
    const existingUser = await User.findOne().or([{ username }, { email }]);
    if (existingUser) {
      return res.status(400).json({ message: 'Username or email already exists' });
    }

    // Hash the password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Create a new user
    const newUser = new User({
      username,
      email,
      password: hashedPassword,
    });

    // Save the user to the database
    await newUser.save();
    res.json({ message: 'Registration successful' });
  } catch (error) {
    console.error('Registration error', error);
    res.status(500).json({ message: 'Registration error' });
  }
}

// User Login Handler Function
async function loginUser(req, res) {
  try {
    const { username, password } = req.body;

    // Check if the username exists
    const user = await User.findOne({ username });
    if (!user) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    // Compare the password
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    // Generate a JWT
    const token = jwt.sign({ userId: user._id }, process.env.SECRET_KEY);

    res.json({ token, message: 'Login successful' });
  } catch (error) {
    console.error('Login error', error);
    res.status(500).json({ message: 'Login error' });
  }
}

module.exports = app;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our Express &lt;code&gt;app&lt;/code&gt; is now ready to be used.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Socket Server Setup
&lt;/h3&gt;

&lt;p&gt;Now, let's set up the &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;socket.io&lt;/code&gt; server instances for authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const http = require('http');
const ioServer = require('socket.io');
const app = require('./app');

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We begin by importing the &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;socket.io&lt;/code&gt; modules, as well as our Express &lt;code&gt;app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, we proceed to create instances of the &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;socket.io&lt;/code&gt; servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create server from express app
const server = http.createServer(app);

// Create the socket server instance
const io = ioServer(server);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the &lt;code&gt;createServer()&lt;/code&gt; function provided by the &lt;code&gt;http&lt;/code&gt; module to create an HTTP server instance. The Express &lt;code&gt;app&lt;/code&gt; is then passed to the function to handle requests to the authentication endpoints. The &lt;code&gt;http&lt;/code&gt; server is then used to create a &lt;code&gt;socket.io&lt;/code&gt; server instance.&lt;/p&gt;

&lt;p&gt;Next, we proceed to add the authentication middleware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;io.use(async (socket, next) =&amp;gt; {
        try {
            const token = socket.handshake.auth.token;

            // Verify and decode the JWT
            const decoded = jwt.verify(token, process.env.SECRET_KEY);

            // Get the user information from the database
            const user = await User.findById(decoded.userId);
            if (!user) {
                throw new Error('User not found');
            }

            // Attach the user object to the socket
            socket.user = user;
            next();
        } catch (error) {
            console.error('Authentication error', error);
            next(new Error('Authentication error'));
        }
    });

    io.on('connection', (socket) =&amp;gt; {
        // Handle Events after authentication
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, when a client connects to the server, the middleware function &lt;code&gt;io.use()&lt;/code&gt;, provided by the &lt;code&gt;socket.io&lt;/code&gt; library, is invoked. Inside the function, we first retrieve the JWT token from the &lt;code&gt;socket.handshake.auth.token&lt;/code&gt; property sent by the client during the handshake (connection). It then verifies and decodes the token using a secret key stored in an environment variable.&lt;/p&gt;

&lt;p&gt;If the token is valid, the middleware retrieves the user information from the database based on the user ID extracted from the token. If the user is found, the user object is attached to the socket for future reference.&lt;/p&gt;

&lt;p&gt;If any errors occur during the authentication process, such as an invalid token or user not found, an error is thrown, and the middleware calls the &lt;code&gt;next()&lt;/code&gt; function with an error argument.&lt;/p&gt;

&lt;p&gt;After successful authentication, the &lt;code&gt;io.on('connection')&lt;/code&gt; event handler is triggered, allowing for further event handling and communication with the authenticated user.&lt;/p&gt;

&lt;p&gt;The authentication system is now complete. But to listen to connections, the code below can be added or customized to your liking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server.listen(PORT, () =&amp;gt; {
    console.log(` Server started running at ${PORT}`);
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;PORT&lt;/code&gt; is the preferred port to listen to the connections. Also, don't forget to connect to your MongoDB collection before starting the server.&lt;/p&gt;

&lt;p&gt;The complete code for the server setup can be found below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const http = require('http');
const ioServer = require('socket.io');
const app = require('./app');
// Create server from express app
const server = http.createServer(app);

// Create the socket server instance
const io = ioServer(server);

io.use(async (socket, next) =&amp;gt; {
        try {
            const token = socket.handshake.auth.token;

            // Verify and decode the JWT
            const decoded = jwt.verify(token, process.env.SECRET_KEY);

            // Get the user information from the database
            const user = await User.findById(decoded.userId);
            if (!user) {
                throw new Error('User not found');
            }

            // Attach the user object to the socket
            socket.user = user;
            next();
        } catch (error) {
            console.error('Authentication error', error);
            next(new Error('Authentication error'));
        }
    });

    io.on('connection', (socket) =&amp;gt; {
        // Handle Events after authentication
    }

    server.listen(PORT, () =&amp;gt; {
        console.log(` Server started running at ${PORT}`);
    });

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Client-Side Connection
&lt;/h2&gt;

&lt;p&gt;To demonstrate how the client-side can be set up to use the authentication system, we'll be using Axios to make requests to the authentication endpoints and use the retrieved token to connect to the &lt;code&gt;socket.io&lt;/code&gt; server instance.&lt;/p&gt;

&lt;p&gt;Let's start by registering a user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const axios = require('axios');
const io = require('socket.io-client');

const username = 'HayatsCodes';
const email = 'hayatscodes@gmail.com';
const password = 123456;
let token;

try {
    const response = await axios.post(`http://localhost:${PORT}/auth/register`, {
      username,
      email,
      password,
    });
    console.log(response.data.message); // Registration successful
  } catch (error) {
      console.error(error);
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firstly, the &lt;code&gt;axios&lt;/code&gt; and &lt;code&gt;socket.io-client&lt;/code&gt; libraries are imported.&lt;/p&gt;

&lt;p&gt;Then, we are registering a user by making a &lt;code&gt;POST&lt;/code&gt; request to the &lt;code&gt;/auth/register&lt;/code&gt; endpoint of the server. The server URL is constructed using the &lt;code&gt;PORT&lt;/code&gt; variable, which should contain the port number.&lt;/p&gt;

&lt;p&gt;The user's &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt; are provided as the request payload. We use the &lt;code&gt;await&lt;/code&gt; keyword to make the request and store the response in the &lt;code&gt;response&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;If the registration is successful, the messsage from the response is logged to the console.&lt;/p&gt;

&lt;p&gt;If an error occurs during the registration process, the &lt;code&gt;catch&lt;/code&gt; block is executed, and the error is logged to the console using &lt;code&gt;console.error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's sign in the registered user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
      const response = await axios.post(`http://localhost:${PORT}/auth/login`, {
        username,
        password,
      });
      token = response.data.token;
      console.log(response.data.message); // login successful
    } catch (error) {
        console.error(error.response.data); 
    }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're signing in the registered user by making a POST request to the &lt;code&gt;/auth/login&lt;/code&gt; endpoint of the server while including the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; in the request payload.&lt;/p&gt;

&lt;p&gt;If the login is successful, the token is extracted from the &lt;code&gt;response.data.token&lt;/code&gt; property, and the &lt;code&gt;message&lt;/code&gt; is logged to the console.&lt;/p&gt;

&lt;p&gt;The token is then assigned to the &lt;code&gt;token&lt;/code&gt; variable for use in the &lt;code&gt;socket.io&lt;/code&gt; client connection.&lt;/p&gt;

&lt;p&gt;If an error occurs during the registration process, the &lt;code&gt;catch&lt;/code&gt; block is executed, and the error is logged to the console using &lt;code&gt;console.error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's connect to the &lt;code&gt;socket.io&lt;/code&gt; server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const client = io(`http://localhost:${PORT}`, {
      auth: {
        token
      }
});
// handle events
client.on('connect', () =&amp;gt; { console.log('connected!') });
// Additional event handling can follow

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code snippet, we're establishing a client-side connection to the &lt;code&gt;socket.io&lt;/code&gt; server using the &lt;code&gt;io&lt;/code&gt; function provided by the &lt;code&gt;socket.io-client&lt;/code&gt; library.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;io&lt;/code&gt; function is called with the socket server URL and an object as an argument, which includes the &lt;code&gt;auth&lt;/code&gt; property. This property specifies the authentication token that will be sent to the server during the handshake process. The value of the earlier saved &lt;code&gt;token&lt;/code&gt; variable from the login request is provided as the token value.&lt;/p&gt;

&lt;p&gt;Once the connection is established, the &lt;code&gt;client.on('connect')&lt;/code&gt; event handler is set up to listen for the 'connect' event. When the client successfully connects to the server, the callback function is executed, logging 'connected!' to the console.&lt;/p&gt;

&lt;p&gt;Additional event handling and communication with the server can be added within the appropriate event handlers.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a secure authentication system for &lt;code&gt;socket.io&lt;/code&gt; connections can be a challenging task due to the limited resources available. However, by leveraging JSON web tokens (JWT), it is possible to create a robust authentication system. In this article, we have covered the step-by-step process of building such a system, including setting up the database model, creating authentication endpoints with Express, implementing authentication middleware with &lt;code&gt;socket.io&lt;/code&gt;, and demonstrating client-side connections using &lt;code&gt;axios&lt;/code&gt; and the &lt;code&gt;socket.io-client&lt;/code&gt; library. By following the provided code examples and explanations, developers can build their own secure authentication system for &lt;code&gt;socket.io&lt;/code&gt; connections, allowing only authenticated users to establish connections and interact with the server.&lt;/p&gt;

</description>
      <category>socketio</category>
      <category>authentication</category>
      <category>node</category>
      <category>backend</category>
    </item>
    <item>
      <title>How I Built a Command-Line Chat Application: Project Reflections</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Wed, 07 Jun 2023 22:20:05 +0000</pubDate>
      <link>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-project-reflections-3ikj</link>
      <guid>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-project-reflections-3ikj</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Challenges&lt;/li&gt;
&lt;li&gt;Lessons Learned&lt;/li&gt;
&lt;li&gt;Contributions and Future Improvements&lt;/li&gt;
&lt;li&gt;
Project References

&lt;ul&gt;
&lt;li&gt;Demo Video&lt;/li&gt;
&lt;li&gt;NPM Registry Link&lt;/li&gt;
&lt;li&gt;GitHub Link&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we will delve into the challenges I encountered while developing the command-line chat application, along with the lessons I learned throughout the process. From the initial stages of structuring the application to tackling authentication issues and creating a user-friendly chat interface, I faced numerous obstacles that tested my problem-solving skills and programming knowledge.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I faced several challenges from the idea stage to the deployment of the app. Below I list the major challenges I faced building the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;App structure:&lt;/strong&gt; I started building the app with no structure in mind, as I wanted to build fast and organize it later. Eventually, I struggled to organize the app for modularity. But the experience of structuring other backend projects and some little research on the MVC design pattern came in handy. I still think the app isn't well structured, though.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; This was one of the hardest features I had to implement. With vague documentation and few resources to explore the &lt;code&gt;socket.io&lt;/code&gt; library authentication, I had to spend several hours making the authentication feature work as expected. I initially wanted to store the token on the user's machine but found it to be buggy. So I changed my mind and used &lt;code&gt;HTTP&lt;/code&gt; requests to store and retrieve the token from the server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The chat interface's home menu feature:&lt;/strong&gt; I wanted the user to be able to go back to the home menu from the chat interface. I thought of using the &lt;code&gt;inquirer.js&lt;/code&gt; prompts that were being used throughout the application. That was impossible because the &lt;code&gt;readline&lt;/code&gt; interface continues to be displayed until it is closed. So I figured out that I could respond to the user's input to go back to the home menu. After that, re-rendering the home menu was also difficult. However, its implementation made the chat message interface code messy, and it also disobeyed the &lt;code&gt;DRY&lt;/code&gt; principle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Railway Redis:&lt;/strong&gt; I've used Redis in development before but never in production. This project was my first time setting up Redis in production. Since I was deploying on Railway, it was supposed to be easier. But it took time for me to understand how to set up a Redis database, as their documentation wasn't really clear.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Publishing to NPM&lt;/strong&gt; : Publishing my first NPM package was challenging. With little time before the project submission deadline, I learned how to publish on NPM and named the app &lt;code&gt;terminal-chat-app&lt;/code&gt;. However, after installing the app from the NPM registry for manual testing, I realized I forgot to add the node shebang line to the app's node script. After correcting the issue, I encountered a version error while attempting to republish. Not knowing what to do, I deleted the package and got another error that I had to wait 24 hours to publish again. To meet the hackathon deadline, I temporarily changed the package name to &lt;code&gt;tarminal-chat-app&lt;/code&gt;. However, I couldn't change the package name again because it now has more than 130 weekly downloads on NPM.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It's usually unclear at the beginning:&lt;/strong&gt; I barely knew command-line and web socket programming before the project, and I was still able to make something out of those unfamiliar tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It takes time to build great stuff:&lt;/strong&gt; I planned to finish the app in a few days, but it took me 2 weeks of debugging, problem-solving, and building to finish the app. Although, if I had known all or most of the technologies used, I would have finished earlier. But I realized that it's not the normal CRUD API that I'm used to building, so it took more time than expected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The definition of a minimum viable product (MVP):&lt;/strong&gt; A lot of features came to mind while building the app, but I had to settle for the easiest feature to implement and think of building upon that later. So if the app was a business product, I'd say what I built was the MVP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Programming Fundamentals help a lot:&lt;/strong&gt; Knowing some programming fundamentals makes things easier. From recursively calling functions to separating code for modularity, it helped make the project a success.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build first, refactor later:&lt;/strong&gt; I started building the app without having any code best practices in mind. It made building faster as I focused on solving the problem at hand rather than writing clean code. I only started refactoring after the app was ready and in good condition.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributions and Future Improvements
&lt;/h2&gt;

&lt;p&gt;The project is open source, and everyone is free to contribute to it. Some of the improvements that can be made are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Restructuring the app files for more modularity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding a private chat messaging feature&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding a group messaging feature by asking for a code before joining&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better error handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tests for the client's code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real-time notifications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Restore 10 or more of the previous messages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and more...&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project References
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Demo Video
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://youtu.be/q7R8VUhbzQs"&gt;https://youtu.be/q7R8VUhbzQs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  NPM Registry Link
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/tarminal-chat-app"&gt;https://www.npmjs.com/package/tarminal-chat-app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  GitHub Link
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/HayatsCodes/terminal-chat-app"&gt;https://github.com/HayatsCodes/terminal-chat-app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It was fun and challenging to build yet another project. From learning new and unfamiliar technologies to implementing several features, it was an enlightening and enriching experience. I look forward to building more and more as I search for my first job.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Built a Command-Line Chat Application: The Client Code Explained</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Wed, 07 Jun 2023 22:17:41 +0000</pubDate>
      <link>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-the-client-code-explained-1p9b</link>
      <guid>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-the-client-code-explained-1p9b</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
Client Authentication

&lt;ul&gt;
&lt;li&gt;User Registration&lt;/li&gt;
&lt;li&gt;User Login&lt;/li&gt;
&lt;li&gt;User Token&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Home Menu

&lt;ul&gt;
&lt;li&gt;Creating Chat Rooms&lt;/li&gt;
&lt;li&gt;Joining Chat Rooms&lt;/li&gt;
&lt;li&gt;Exiting the App&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
User Interface

&lt;ul&gt;
&lt;li&gt;Authentication Display Options&lt;/li&gt;
&lt;li&gt;Home Display Options&lt;/li&gt;
&lt;li&gt;Interface Rendering&lt;/li&gt;
&lt;li&gt;Chat Message Interface&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
The Socket Client and Node Script

&lt;ul&gt;
&lt;li&gt;Socket Client Events&lt;/li&gt;
&lt;li&gt;The Node Script&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this section, We'll dive into the client-side code implementation. The chat application provides users with the ability to register, log in, create chat rooms, join existing chat rooms, and exchange messages with other users. We will dissect the client code, examining the authentication logic, user registration and login functionalities, as well as the menu options for creating and joining chat rooms. Additionally, we will explore the user interface rendering and the event handling using the socket.io-client library.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Client Authentication
&lt;/h2&gt;

&lt;p&gt;Starting from the client's &lt;code&gt;/src&lt;/code&gt; folder, the &lt;code&gt;/auth&lt;/code&gt; folder contains the client's authentication logic. It has three files: &lt;code&gt;registerUser.js&lt;/code&gt;, &lt;code&gt;loginUser.js&lt;/code&gt;, and &lt;code&gt;getToken.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  User Registration
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// registerUser.js
const { prompt } = require('inquirer');
const axios = require('axios');

const loginUser = require('./loginUser');

const registerUser = async () =&amp;gt; {
  const questions = [
    {
      type: 'input',
      name: 'username',
      message: 'Enter your username:',
    },
    {
      type: 'input',
      name: 'email',
      message: 'Enter your email:',
    },
    {
      type: 'password',
      name: 'password',
      message: 'Enter your password:',
    },
  ];

  try {
    const answers = await prompt(questions);
    const { username, email, password } = answers;

    const response = await axios.post('https://terminal-chat-app-production.up.railway.app/auth/register', {
      username,
      email,
      password,
    });

    console.info(response.data.message); // Registration successful
    console.info('-----------------------');
    const token = loginUser(username, password, email);
    return token;
  } catch (error) {
    if ( error.response.data.message === 'Username or email already exists') {
      console.info(error.response.data.message);
      registerUser();
    } else {
      console.error(error.response.data);
    }
  }
};

module.exports = registerUser;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;registerUser.js&lt;/code&gt; prompts users with a list of questions asking for their details. The answers are then sent in a post request to the server's registration endpoint.&lt;/p&gt;

&lt;p&gt;After successful registration, the user is logged in with the registered details (we'll take a look at that in the next heading). A token is then retrieved from the login operation, which is then returned from the function.&lt;/p&gt;

&lt;p&gt;Also, if a user enters an existing email or password, they are prompted again to enter their registration details.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  User Login
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// loginUser.js
const axios = require('axios');
const { prompt } = require('inquirer');

const loginUser = async (username, password, email = null) =&amp;gt; {
  if (email) {
    try {
      const response = await axios.post('https://terminal-chat-app-production.up.railway.app/auth/login', {
        username,
        password,
      });

      const token = response.data.token;

      console.log(response.data.message); // login successful
      return token;

    } catch (error) {
      console.error(error.response.data); // login error
    }
  } else {
    const questions = [
      {
        type: 'input',
        name: 'username',
        message: 'Enter your username:',
      },
      {
        type: 'password',
        name: 'password',
        message: 'Enter your password:',
      },
    ];

    try {
      const answers = await prompt(questions);
      const { username, password } = answers;

      const response = await axios.post('https://terminal-chat-app-production.up.railway.app/auth/login', {
        username,
        password,
      });

      const token = response.data.token;
      console.log(response.data.message); // login successful
      return token;
    } catch (error) {
      if (error.response.data.message === 'Invalid username or password') {
        console.info(error.response.data.message);
        loginUser(username, password);
      } else {
        console.error(error.response.data); 
      }
    }
  }
};

module.exports = loginUser;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;loginUser.js&lt;/code&gt; exports a &lt;code&gt;loginUser&lt;/code&gt; function that takes three parameters, with the &lt;code&gt;email&lt;/code&gt; parameter initially set to null.&lt;/p&gt;

&lt;p&gt;Since the user's email is available during registration, it is used to handle user login immediately after registration. The &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; parameters are then used to quickly make a request to the server's login endpoint.&lt;/p&gt;

&lt;p&gt;However, when the email remains null during normal login, the user is prompted to enter their &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;, which are then used to make a request to the server's login endpoint.&lt;/p&gt;

&lt;p&gt;The user is also prompted again to enter their login details when invalid credentials are entered.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  User Token
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// getToken.js
const axios = require('axios');

module.exports = async (username) =&amp;gt; {
    try {
        const response = await axios.get(`https://terminal-chat-app-production.up.railway.app/auth/tokens/${username}`);
        const token = response.data
        return token;
    } catch(error) {
        console.error(error.response.data); 
    }

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getToken.js&lt;/code&gt; simply gets the user token with their username from the server's running Redis database.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Home Menu
&lt;/h2&gt;

&lt;p&gt;Next, we move into the &lt;code&gt;/menu&lt;/code&gt; folder. It contains the logic for the home menu options. There are three folders in it: &lt;code&gt;createChatRoom.js&lt;/code&gt;, &lt;code&gt;joinChatRoom.js&lt;/code&gt;, and &lt;code&gt;exitApp.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating Chat Rooms
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// createChatRoom.js
const { prompt } = require('inquirer');
const joinChatRoom = require('./joinChatRoom');
const axios = require('axios');
const question = [
  {
    type: 'input',
    name: 'roomName',
    message: 'Enter Room Name'
  }
]

module.exports = async function createChatRoom(client) {
  try {
    const answer = await prompt(question);
    const roomName = answer.roomName;

    const response = await axios.post('https://terminal-chat-app-production.up.railway.app/api/chatrooms', {
      roomName
    });
    const chatRoom = response.data;
    console.log(`${chatRoom} chat room created`);
    joinChatRoom(client, chatRoom);
    return chatRoom;
  } catch (error) {
    if (error.response.data.message) {
      console.info(error.response.data.message);
      createChatRoom(client); // Recursive call to prompt again
    } else {
      console.error(error);
    }
  }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;createChatRoom.js&lt;/code&gt; exports a function that takes a single parameter, &lt;code&gt;client&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It starts by prompting the user to enter a room name. The room name is then sent in a &lt;code&gt;POST&lt;/code&gt; request to the server to store it in the database.&lt;/p&gt;

&lt;p&gt;The user then joins the room created by passing &lt;code&gt;client&lt;/code&gt;, which is a socket-client instance, and the room name to the &lt;code&gt;joinChatRoom&lt;/code&gt; function (which will be explained later). The created chat room is then returned from the function.&lt;/p&gt;

&lt;p&gt;If an error occurs, the user is prompted to create the chat room again.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Joining Chat Rooms
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// joinChatRoom.js
const { prompt } = require('inquirer');
const axios = require('axios');

module.exports = async function joinChatRoom(client, chatRoom = null) {
    if (chatRoom) {
        client.emit('join', chatRoom);
    } else {
        const response = await axios.get('https://terminal-chat-app-production.up.railway.app/api/chatrooms');
        const chatRooms = response.data;
        const chatRoomsOption = [
            {
                type: 'list',
                name: 'selectedRoom',
                message: 'Choose a Chat Room:',
                choices: chatRooms,
            },
        ]
        const { selectedRoom } = await prompt(chatRoomsOption);
        client.emit('join', selectedRoom);
        return selectedRoom;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;joinChatRoom.js&lt;/code&gt; exports a function that takes two parameters: &lt;code&gt;client&lt;/code&gt;, a &lt;code&gt;socket.io-client&lt;/code&gt; instance, and &lt;code&gt;chatRoom&lt;/code&gt;, which is set to null.&lt;/p&gt;

&lt;p&gt;It triggers the &lt;code&gt;join&lt;/code&gt; event immediately when the &lt;code&gt;chatRoom&lt;/code&gt; is not null. This happens when a user finishes creating a new chat room in &lt;code&gt;createChatRoom.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, if a user wants to join an existing room, the user is prompted to choose a room from the list of rooms retrieved from the server. The &lt;code&gt;join&lt;/code&gt; event is then triggered, with the selected chat room sent to the &lt;code&gt;socket.io&lt;/code&gt; server. The selected chat room is then returned from the function.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Exiting the App
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// exitApp.js
module.exports = () =&amp;gt; {
    console.info('Exited Terminal Chat App Successfully!');
    process.exit(0);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;exitApp.js&lt;/code&gt; exports an anonymous function that simply performs a process termination operation. This terminates the app, and a message is logged on to the user's screen.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  User Interface
&lt;/h2&gt;

&lt;p&gt;Into the &lt;code&gt;/views&lt;/code&gt; folder, there is the user interface logic. It consists of four files: &lt;code&gt;getAuthOption.js&lt;/code&gt;, &lt;code&gt;getMenuOption.js&lt;/code&gt;, &lt;code&gt;renderInterface.js&lt;/code&gt;, and &lt;code&gt;chatMessageInterface.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Authentication Display Options
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// getAuthOptions.js
const { prompt } = require('inquirer');

function getAuthOption() {
  const authOptions = [
    {
      type: 'list',
      name: 'selectedOption',
      message: 'Authentication',
      choices: [
        { name: 'Register', value: 'Register', message: 'Create an account' },
        { name: 'Login', value: 'Login', message: 'Login to your account' },
        { name: 'Exit', value: 'Exit', message: 'Exit the App' },
      ]
    },
  ];

  return prompt(authOptions)
    .then(answers =&amp;gt; answers.selectedOption);
}

module.exports = getAuthOption;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getAuthOption.js&lt;/code&gt; exports a function that prompts users to select an authentication option of either &lt;code&gt;Register&lt;/code&gt;, &lt;code&gt;Login&lt;/code&gt;, or &lt;code&gt;Exit&lt;/code&gt;. It then returns the selected option.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Home Display Options
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// getMenuOption.js
const { prompt } = require('inquirer');

function getMenuOption() {
  const menuOptions = [
    {
      type: 'list',
      name: 'selectedOption',
      message: 'Home',
      choices: [
        { name: 'Create-Chat-Room', value: 'Create-Chat-Room', message: 'Create a Chat Room' },
        { name: 'Join-Chat-Room', value: 'Join-Chat-Room', message: 'Join a Chat Room' },
        { name: 'Exit', value: 'Exit', message: 'Exit the App' },
      ]
    },
  ];

  return prompt(menuOptions)
    .then(answers =&amp;gt; answers.selectedOption);
}

module.exports = getMenuOption;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getMenuOption.js&lt;/code&gt; exports a function that prompts users to select a home menu option of either &lt;code&gt;Create-Chat-Room&lt;/code&gt;, &lt;code&gt;Join-Chat-Room&lt;/code&gt;, or &lt;code&gt;Exit&lt;/code&gt;. It then returns the selected option.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Interface Rendering
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// renderInterface.js
const registerUser = require('../auth/registerUser');
const loginUser = require('../auth/loginUser');
const createChatRoom = require('../menu/createChatRoom');
const joinChatRoom = require('../menu/joinChatRoom');
const exitApp = require('../menu/exitApp');

const render = {
    'Register': registerUser,
    'Login': loginUser,
    'Create-Chat-Room': createChatRoom,
    'Join-Chat-Room': joinChatRoom,
    'Exit': exitApp
};

module.exports = render;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;renderInterface.js&lt;/code&gt; exports a render object that stores the authentication and home menu options functions.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Chat Message Interface
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// chatMessageInterface.js
const readline = require('readline');
const io = require('socket.io-client');
const exitApp = require('../menu/exitApp');
const getMenuOption = require('./getMenuOption');
const render = require('./renderInterface');
const getToken = require('../auth/getToken');
const attachEvents = require('../../attachEvents');

function chatMessageInterface(client, chatRoom) {
  console.info('----------------------------------------------');
  console.info('Press -h to go Home.');
  console.info('Press -e to Exit.');
  console.info('----------------------------------------------');

  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });

  let clientUsername;

  // Event listener for the 'username' event
  client.on('username', (username) =&amp;gt; {
    clientUsername = username;
  });

  rl.on('line', async (input) =&amp;gt; {
    const message = input.trim();
    if (message === '-e') {
      rl.close(); // Close the readline interface
      exitApp();
    } else if (message === '-h') {
      // Check if the clientUsername is defined
      if (!clientUsername) {
        console.log('Waiting for username...');
        return;
      }

      const token = await getToken(clientUsername);
      client.disconnect();

      // create a new client connection
      const newClient = io('https://terminal-chat-app-production.up.railway.app', {
        auth: {
          token
        }
      });

      // Attach events to newClient
      attachEvents(newClient);

      // Display Home menu after successful authentication
      const homeOption = await getMenuOption();

      // Render menu interface according to what the user selects
      const chatRoom = await render[homeOption](newClient);

      // Start chat room messaging
      chatMessageInterface(newClient, chatRoom);
    }
    client.emit('chat message', chatRoom, message);
  });
}

module.exports = chatMessageInterface;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;chatMessageInterface.js&lt;/code&gt; exports a function that takes two parameters: &lt;code&gt;client&lt;/code&gt;, a &lt;code&gt;socket.io-client&lt;/code&gt; instance, and &lt;code&gt;chatRoom&lt;/code&gt;, the room where the chat is taking place.&lt;/p&gt;

&lt;p&gt;It starts by logging some navigation information onto the user's terminal. It then creates a readline interface that allows for user inputs to be read.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;client&lt;/code&gt; listens for the &lt;code&gt;username&lt;/code&gt; event and stores it in a &lt;code&gt;clientUsername&lt;/code&gt; variable for later use.&lt;/p&gt;

&lt;p&gt;The readline interface then listens for the &lt;code&gt;line&lt;/code&gt; event which is triggered when a user enters a command-line input. The input is stored in a &lt;code&gt;message&lt;/code&gt; variable, and this determines the next operation.&lt;/p&gt;

&lt;p&gt;Next, the &lt;code&gt;exitApp&lt;/code&gt; function is called when the &lt;code&gt;message&lt;/code&gt; is &lt;code&gt;-e&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the message is &lt;code&gt;-h&lt;/code&gt;, the client token is retrieved from the server before the client is disconnected from the &lt;code&gt;socket.io&lt;/code&gt; server.&lt;/p&gt;

&lt;p&gt;A new client connection is then created with the retrieved token to make sure the user is authenticated to create and join chat rooms. The other functions in the &lt;code&gt;else if&lt;/code&gt; block will be explained later in the heading " &lt;strong&gt;The&lt;/strong&gt;  &lt;strong&gt;Node Script&lt;/strong&gt;".&lt;/p&gt;

&lt;p&gt;Also, if the &lt;code&gt;message&lt;/code&gt; is neither &lt;code&gt;-e&lt;/code&gt; nor &lt;code&gt;-h&lt;/code&gt;, the &lt;code&gt;chat message&lt;/code&gt; event is triggered and is used to send messages to other users in the chat room.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Socket Client and Node Script
&lt;/h2&gt;

&lt;p&gt;At the root of the client folder, there contains &lt;code&gt;attachEvents.js&lt;/code&gt;, which attaches events to the &lt;code&gt;socket.io-client&lt;/code&gt; instance, and &lt;code&gt;commander.js&lt;/code&gt;, the client's interface node script.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Socket Client Events
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// attachEvents.js
module.exports = (client) =&amp;gt; {

    client.on('connect', () =&amp;gt; { });

    // Handles 'chat message' event when another user sends a message
    client.on('chat message', (message) =&amp;gt; {
      console.info(message);
    });

    client.on('joined', (info) =&amp;gt; {
      console.info(info);
    });

    client.on('user joined', (info) =&amp;gt; {
      console.info(info);
    });

    // // Handles 'user left' event when a user leaves a room
    client.on('user left', (info) =&amp;gt; {
      console.info(info);
    });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;attachEvents.js&lt;/code&gt; exports a function that takes the &lt;code&gt;socket.io-client&lt;/code&gt; instance as a parameter. It listens to various events and specifies what to do when they're triggered.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The Node Script
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env node // commander.js
const { Command } = require('commander');
const io = require('socket.io-client');

const getAuthOption = require('./src/views/getAuthOption');
const getMenuOption = require('./src/views/getMenuOption');
const chatMessageInterface = require('./src/views/chatMessageInterface');
const render = require('./src/views/renderInterface');
const attachEvents = require('./attachEvents');

const program = new Command();

program.version('1.0.0').description('Terminal Chat App');

// Start Terminal chat app
program
  .description('Starts the Terminal chat app')
  .command('start').action(async () =&amp;gt; {
    // Display Authentication menu
    const authOption = await getAuthOption();

    // Render authentication interface according to what the user selects
    const token = await render[authOption]();

    if (!token) {
      console.info('Authentication Error!');
      process.exit(1);
    }

    // connect to the socket server after authentication
    const client = io('https://terminal-chat-app-production.up.railway.app', {
      auth: {
        token
      }
    });

    // Attach events to client
    attachEvents(client);

    // Display Home menu after succesful authentication
    const homeOption = await getMenuOption();

    // Render menu interface according to what the user selects
    const chatRoom = await render[homeOption](client);

    // Start chat room messaging
    chatMessageInterface(client, chatRoom);
  }
  );

program.parse(process.argv);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above file is a node script that uses the &lt;code&gt;commander&lt;/code&gt; library for handling command-line arguments. The script only allows for the &lt;code&gt;start&lt;/code&gt; command-line argument. This argument starts the app.&lt;/p&gt;

&lt;p&gt;On starting the app, the authentication menu is displayed. The &lt;code&gt;render&lt;/code&gt; object is then used to render what the user selects from the menu, and the result of the operation is stored in a &lt;code&gt;token&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;If a token was not returned from the previous operation, which translates to an authentication failure, the app is terminated. Otherwise, the app keeps running.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;socket.io-client&lt;/code&gt; instance is then created, and it uses the returned token for authentication when connecting to the &lt;code&gt;socket.io&lt;/code&gt; server.&lt;/p&gt;

&lt;p&gt;The home menu is displayed after a successful connection with the server. Again, the &lt;code&gt;render&lt;/code&gt; object is then used to render what the user chooses from the home menu option. A &lt;code&gt;chatRoom&lt;/code&gt; is returned from the operation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;socket.io-client&lt;/code&gt; instance, along with the &lt;code&gt;chatRoom&lt;/code&gt;, is then used to start the chat message interface.&lt;/p&gt;

&lt;p&gt;The user can now start chatting with other users in the chat room in real-time.&lt;/p&gt;

&lt;p&gt;The client code was published on the &lt;a href="https://www.npmjs.com/package/tarminal-chat-app"&gt;NPM registry&lt;/a&gt;. This is for easy access and installation on the command line anywhere in the world. You can run &lt;code&gt;npm install tarminal-chat-app&lt;/code&gt; on your terminal to install the app.&lt;/p&gt;

&lt;p&gt;In the next section, I review the challenges, lessons learned, and potential future improvements for the project.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Built a Command-Line Chat Application: The Server Code Explained</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Wed, 07 Jun 2023 22:14:38 +0000</pubDate>
      <link>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-the-server-code-explained-495o</link>
      <guid>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-the-server-code-explained-495o</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
Database Schema

&lt;ul&gt;
&lt;li&gt;User and Chat Room Models&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
API Routes

&lt;ul&gt;
&lt;li&gt;Authentication Route Handlers&lt;/li&gt;
&lt;li&gt;Authentication Router&lt;/li&gt;
&lt;li&gt;Chat Room Route Handlers&lt;/li&gt;
&lt;li&gt;Chat Room Router&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Configuration and Utility

&lt;ul&gt;
&lt;li&gt;MongoDB Configuration&lt;/li&gt;
&lt;li&gt;Redis Setup&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
The Express App, Socket Server, and HTTP Server

&lt;ul&gt;
&lt;li&gt;The Express App&lt;/li&gt;
&lt;li&gt;The Socket Server Instance&lt;/li&gt;
&lt;li&gt;HTTP Server&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a command-line chat application involves several components working seamlessly together. In this article, we'll dive into the server-side code that powers the application. We'll explore the database schema, API routes for authentication, chat room routes, configuration, and utility files, as well as the Express app, socket server, and HTTP server setup. So, let's begin our journey through the server code and unravel the backend logic behind this command-line chat application.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Schema
&lt;/h2&gt;

&lt;p&gt;Starting from the server's &lt;code&gt;/src&lt;/code&gt; folder, specifically the &lt;code&gt;/models&lt;/code&gt; folder, let's take a look into the database schemas of both the user and the chat room.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  User and Chat Room Models
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// user.model.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: String,
    email: String,
    password: String,
  });

const User = mongoose.model('User', userSchema);

module.exports = User;


// chatRoom.model.js
const mongoose = require('mongoose');

const chatRoomSchema = new mongoose.Schema({
  roomName: {
    type: String,
    required: true,
  },
}, { timestamps: true }
);

const ChatRoom = mongoose.model('ChatRoom', chatRoomSchema);

module.exports = ChatRoom;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two schemas use &lt;code&gt;mongoose&lt;/code&gt; to create a valid MongoDB schema.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;user.model.js&lt;/code&gt; defines a &lt;code&gt;userSchema&lt;/code&gt; object with three fields: &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt;. This stores the user information in a MongoDB collection for authentication.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chatRoom.model.js&lt;/code&gt; defines a &lt;code&gt;chatRoomSchema&lt;/code&gt; object with only one field: &lt;code&gt;roomName&lt;/code&gt;. This allows for the addition and retrieval of chat rooms from the database.&lt;/p&gt;

&lt;p&gt;They're both exported for use in the &lt;code&gt;/routes&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  API Routes
&lt;/h2&gt;

&lt;p&gt;Moving into the &lt;code&gt;/routes&lt;/code&gt; folder, it has two folders: &lt;code&gt;/auth&lt;/code&gt;, which contains the authentication routes and handler functions; and &lt;code&gt;/chatRoom&lt;/code&gt;, which contains the chat room route and handler functions.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Authentication Route Handlers
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// auth.controller.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require("../../models/user.model");
const redisClient = require('../../utils/redisClient');

// User Registration
async function registerUser(req, res) {
  try {
    const { username, email, password } = req.body;

    // Check if the username or email already exists
    const existingUser = await User.findOne().or([{ username }, { email }]);
    if (existingUser) {
      return res.status(400).json({ message: 'Username or email already exists' });
    }

    // Hash the password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Create a new user
    const newUser = new User({
      username,
      email,
      password: hashedPassword,
    });

    // Save the user to the database
    await newUser.save();
    res.json({ message: 'Registration successful' });
  } catch (error) {
    console.error('Registration error', error);
    res.status(500).json({ message: 'Registration error' });
  }
}

async function loginUser(req, res) {
  try {
    const { username, password } = req.body;

    // Check if the username exists
    const user = await User.findOne({ username });
    if (!user) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    // Compare the password
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(400).json({ message: 'Invalid username or password' });
    }

    // Generate a JWT
    const token = jwt.sign({ userId: user._id }, process.env.SECRET_KEY);
    await redisClient.set(username, token);

    res.json({ token, message: 'Login successful' });
  } catch (error) {
    console.error('Login error', error);
    res.status(500).json({ message: 'Login error' });
  }
}

async function getToken(req, res) {
  try {
    const username = req.params.id;
    const token = await redisClient.get(username);
    if (token) {
      res.status(200).json(token);
    } else {
      res.status(400).json({ message: 'No token found' })
    }
  } catch (error) {
    res.status(500).json({ message: 'Couldn\'t get token' });
  }

}

module.exports = {
  registerUser,
  loginUser,
  getToken
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;auth.controller.js&lt;/code&gt; contains three handler functions: &lt;code&gt;registerUser&lt;/code&gt;, &lt;code&gt;loginUser&lt;/code&gt;, and &lt;code&gt;getToken&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;registerUser&lt;/code&gt; function handles user registration. It initially compares the passed user details in the &lt;code&gt;req.body&lt;/code&gt; object with all the user data stored in the database for uniqueness before proceeding to hash the user password for security. If there are no errors, the user details, including the hashed password, are saved to the database for persistence.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;loginUser&lt;/code&gt; function handles user login. It starts by checking for a valid user name being passed and then proceeds to compare the user password from the request with the user-stored hashed password in the database. With no errors, a new token is generated using the &lt;code&gt;jsonwebtoken&lt;/code&gt; library. The token is then stored in a Redis database with the user's username set as the key.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getToken&lt;/code&gt; function handles token transfers through HTTP requests. It simply gets a stored token with the user's &lt;code&gt;username&lt;/code&gt; from the Redis database.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Authentication Router
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// auth.route.js
const express = require('express');
const { registerUser, loginUser, getToken } = require('./auth.controller');

const authRouter = express.Router();

// Register API endpoint
authRouter.post('/register', registerUser);

// Login API endpoint
authRouter.post('/login', loginUser);

// Token API endpoint
authRouter.get('/tokens/:id', getToken);

module.exports = authRouter;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;auth.route.js&lt;/code&gt; defines an &lt;code&gt;authRouter&lt;/code&gt; variable, which is initialized as an Express router. The &lt;code&gt;POST&lt;/code&gt; and &lt;code&gt;GET&lt;/code&gt; HTTP methods are attached to the router to handle requests for the corresponding endpoints using the handler functions imported from &lt;code&gt;auth.controller.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Chat Room Route Handlers
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// chatRoom.controller.js
const ChatRoom = require("../../models/chatRoom.model");

async function createChatRoom(req, res) {
    try {
        const { roomName } = req.body;
        const isRoomExist = await ChatRoom.findOne({ roomName });
        if (isRoomExist) {
            res.status(409).json({ message: 'Chat room already exists' });
        } else {
            const chatRoom = new ChatRoom(req.body);
            const savedChatRoom = await chatRoom.save();
            res.status(201).json(savedChatRoom.roomName);
        }
    } catch (err) {
        res.status(500).json({message: 'Chat room creation failed'});
    }
}

async function joinChatRoom(req, res) {
    try {
        const chatRooms = await ChatRoom.find({}, 'roomName');
        const roomNames = chatRooms.map((chatRoom) =&amp;gt; chatRoom.roomName);
        res.status(200).json(roomNames);
    } catch(err) {
        res.status(500).json({message: 'couldn\'t join chat room'});
    }
}

module.exports = {
    createChatRoom,
    joinChatRoom,
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two handler functions can be seen in the above code: &lt;code&gt;createChatRoom&lt;/code&gt; and &lt;code&gt;joinChatRoom&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;createChatRoom&lt;/code&gt; handles chat room creation. It starts by checking if the room in the request body exists in the database and then proceeds to save the chat room to the database if there were no errors.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;joinChatRoom&lt;/code&gt; on the other hand handles user joining of chat room. It simply retrieves all the created chat rooms from the database.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Chat Room Router
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// chatRoom.route.js
const express = require('express');
const { createChatRoom, joinChatRoom } = require('./chatRoom.controller');

const chatRoomRouter = express.Router();

chatRoomRouter.post('/chatrooms', createChatRoom);
chatRoomRouter.get('/chatrooms', joinChatRoom);

module.exports = chatRoomRouter;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;chatRoom.route.js&lt;/code&gt; uses the handler functions in &lt;code&gt;chatRoom.controller.js&lt;/code&gt; to handle &lt;code&gt;POST&lt;/code&gt; and &lt;code&gt;GET&lt;/code&gt; requests for the &lt;code&gt;/chatrooms&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration and Utility
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/config&lt;/code&gt; and &lt;code&gt;/utils&lt;/code&gt; folders contain the MongoDB and Redis connection setups, respectively.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  MongoDB Configuration
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /mongo.js
const mongoose = require('mongoose');
require('dotenv').config();

const uri = process.env.MONGO_URI

async function mongoConnect() {
    await mongoose.connect(uri, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
    });
}

module.exports = mongoConnect;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mongo.js&lt;/code&gt;, in the &lt;code&gt;/config&lt;/code&gt; folder, exports a &lt;code&gt;mongoConnect&lt;/code&gt; function that uses the environmental variable &lt;code&gt;MONGO_URI&lt;/code&gt; to connect to the MongoDB database.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Redis Setup
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /utils/redisClient.js
const { createClient } = require('redis');
let redisClient;

if (process.env.NODE_ENV === 'production') {
    // Create a Redis client with the production redis url
    redisClient = createClient({
        url: `${process.env.REDIS_URL}`
    });

} else {
    // Create a Redis client with the default port
    redisClient = createClient();
}

redisClient.on('error', err =&amp;gt; console.log('Redis Client Error', err));

module.exports = redisClient;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;/utils&lt;/code&gt; folder, &lt;code&gt;redisClient.js&lt;/code&gt; checks whether the code is running in production. If it is, &lt;code&gt;redisClient&lt;/code&gt; is created with an object option that includes a &lt;code&gt;url&lt;/code&gt; property. The value of this property is set to &lt;code&gt;process.env.REDIS_URL&lt;/code&gt;, which is provided by Railway, a code deployment service. This allows for easy connection to a running Redis database. And if the code is not being run in production, &lt;code&gt;redisClient&lt;/code&gt; is configured to connect to a running Redis database on the default port &lt;code&gt;6379&lt;/code&gt; of the host's machine.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Express App, Socket Server, and HTTP Server
&lt;/h2&gt;

&lt;p&gt;At the root of the &lt;code&gt;/server&lt;/code&gt; folder, there is &lt;code&gt;app.js&lt;/code&gt;, the express app configuration; &lt;code&gt;socketManager.js&lt;/code&gt;, the server's socket connections manager; and &lt;code&gt;server.js&lt;/code&gt;, the HTTP server setup.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The Express App
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app.js
const express = require('express');
const authRouter = require('./src/routes/auth/auth.route');
const chatRoomRouter = require('./src/routes/chatRoom/chatRoom.route');

const app = express();
app.use(express.json());

app.use('/auth', authRouter);
app.use('/api', chatRoomRouter);

module.exports = app;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;app.js&lt;/code&gt; exports an express &lt;code&gt;app&lt;/code&gt; variable that uses both the &lt;code&gt;authRouter&lt;/code&gt; and &lt;code&gt;chatRoomRouter&lt;/code&gt; to handle requests for the &lt;code&gt;/auth&lt;/code&gt; and &lt;code&gt;/api&lt;/code&gt; endpoints.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The Socket Server Instance
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// socketManager.js
const jwt = require('jsonwebtoken');
const User = require('./src/models/user.model');

module.exports = (io) =&amp;gt; {
    // Authentication middleware
    io.use(async (socket, next) =&amp;gt; {
        try {
            const token = socket.handshake.auth.token;

            // Verify and decode the JWT
            const decoded = jwt.verify(token, process.env.SECRET_KEY);

            // Get the user information from the database
            const user = await User.findById(decoded.userId);
            if (!user) {
                throw new Error('User not found');
            }

            // Attach the username property of the user object to the socket
            socket.username = user.username;
            next();
        } catch (error) {
            console.error('Authentication error', error);
            next(new Error('Authentication error'));
        }
    });

    io.on('connection', (socket) =&amp;gt; {

        // Create a Map to track the room for each socket connection
        const socketRoomMap = new Map();

        // Handle 'join' event when a client joins the chat room
        socket.on('join', (room) =&amp;gt; {
            // Emits the username to the client
            socket.emit('username', socket.username);

            socket.join(room);
            // console.log(socket.rooms);
            socketRoomMap.set(socket.username, room); // Store the room information for the socket connection
            socket.emit('joined', `You joined ${room}`);
            socket.broadcast.to(room).emit('user joined', `${socket.username} joined ${room}`);
        });

        // Handle 'chat message' event when a client sends a message
        socket.on('chat message', (room, message) =&amp;gt; {
            socket.broadcast.to(room).emit('chat message', `${socket.username}: ${message}`);
        });

        // Handle 'disconnect' event when a client disconnects
        socket.on('disconnecting', () =&amp;gt; {
            const room = socketRoomMap.get(socket.username); // Retrieve the room information for the socket connection
            if (room) {
                socket.broadcast.to(room).emit('user left', `${socket.username} left the chat room`);
                socketRoomMap.delete(socket.username); // Remove the room information for the socket connection
            }
        });
    });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;socketManager.js&lt;/code&gt; exports an anonymous function that takes a single parameter, &lt;code&gt;io&lt;/code&gt;. The parameter is an instance of the &lt;code&gt;socket.io&lt;/code&gt; server and is used to manage socket connections.&lt;/p&gt;

&lt;p&gt;It starts by using an authentication middleware that verifies the token sent by the connecting socket client. It then assigns the user associated with the token to the &lt;code&gt;socket.username&lt;/code&gt; object property. This happens before any connection to the server to make sure only authenticated users can make requests to the &lt;code&gt;socket.io&lt;/code&gt; server instance.&lt;/p&gt;

&lt;p&gt;After successful authentication, the socket server instance (&lt;code&gt;io&lt;/code&gt;) listens for the &lt;code&gt;connection&lt;/code&gt; event. The event is triggered when a client successfully connects to the &lt;code&gt;socket.io&lt;/code&gt; server instance.&lt;/p&gt;

&lt;p&gt;A callback function that receives the connected socket client is then executed when the event is triggered. Inside the callback function, three event listeners are attached to the connected socket client: the &lt;code&gt;join&lt;/code&gt;, &lt;code&gt;chat message&lt;/code&gt;, and &lt;code&gt;disconnecting&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;When the &lt;code&gt;join&lt;/code&gt; event is triggered, the connected socket client first emits a &lt;code&gt;username&lt;/code&gt; event. This event takes the &lt;code&gt;socket.username&lt;/code&gt; object property in the &lt;code&gt;socket.io&lt;/code&gt; server instance and sends it to the client. It then proceeds to join the &lt;code&gt;room&lt;/code&gt; it received from the client. Also, it stores the &lt;code&gt;room&lt;/code&gt; in a map object, with the key being the user's username. This is to be used when the user disconnects. It also emits the &lt;code&gt;joined&lt;/code&gt; event to the connected client and the &lt;code&gt;user joined&lt;/code&gt; event to other users in the chat room.&lt;/p&gt;

&lt;p&gt;Next, when the &lt;code&gt;chat message&lt;/code&gt; event is triggered, the callback function in the event simply broadcasts the received &lt;code&gt;message&lt;/code&gt; from the client to all the users in the specified &lt;code&gt;room&lt;/code&gt; except for the broadcasting user itself.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;disconnecting&lt;/code&gt; event is triggered when a user is about to disconnect from the &lt;code&gt;socket.io&lt;/code&gt; server instance. The callback function defined in the event starts by retrieving the room saved in the map object with the disconnecting user's &lt;code&gt;socket.username&lt;/code&gt; property. A &lt;code&gt;user left&lt;/code&gt; event is then broadcasted to all the users in the retrieved room except the disconnecting user.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  HTTP Server
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const http = require('http');
const ioServer = require('socket.io');
const redisClient = require('./src/utils/redisClient');
const app = require('./app');
const mongoConnect = require('./src/config/mongo');
const socketManager = require('./socketManager');
require('dotenv').config();

// Create server from express app
const server = http.createServer(app);

// set up the socket server and allow all resource to access the server
const io = ioServer(server, { cors: { origin: "*" } });

// Manage socket connections
socketManager(io);

server.listen(3001, async () =&amp;gt; {
    // Connect to Mongo
    await mongoConnect();
    // connect to redis
    await redisClient.connect();
    console.log('Server started running...');
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;server.js&lt;/code&gt; is the entry point of the &lt;code&gt;/server&lt;/code&gt; folder. It starts by creating an HTTP server that uses an Express app to handle requests. It then proceeds to set up a &lt;code&gt;socket.io&lt;/code&gt; server instance with the HTTP server. The socket instance is then passed as an argument to the &lt;code&gt;socketManager&lt;/code&gt; function to manage socket connections. Finally, the HTTP server listens for requests on port &lt;code&gt;3001&lt;/code&gt; and waits for a successful connection to the MongoDB and Redis databases before starting the server.&lt;/p&gt;

&lt;p&gt;The server code was deployed to &lt;a href="https://railway.app?referralCode=8W0n7L"&gt;Railway&lt;/a&gt; for real-time communication between users around the world.&lt;/p&gt;

&lt;p&gt;In the next section, we explain the client setup and the main features of the chat app.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Built a Command-Line Chat Application: Project Details</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Wed, 07 Jun 2023 22:04:41 +0000</pubDate>
      <link>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-project-details-3cjd</link>
      <guid>https://dev.to/hayatscodes/how-i-built-a-command-line-chat-application-project-details-3cjd</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Project Overview&lt;/li&gt;
&lt;li&gt;Project Goals&lt;/li&gt;
&lt;li&gt;Project Features&lt;/li&gt;
&lt;li&gt;Project Structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a project as a backend developer is not often fancy like that of a frontend developer. So I decided on creating a full-stack app, but with the command line being the user interface and an HTTP and socket server being the back end. The project has been on my mind, but I only got to work on it for the GitHub + DEV Hackathon.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/tarminal-chat-app"&gt;Tarminal-chat-app&lt;/a&gt; is a command-line chat application that allows for real-time communication between users in a public chat room. The app aims to provide a seamless chat experience from the command line.&lt;/p&gt;

&lt;p&gt;The command-line interface was built with &lt;code&gt;commander.js&lt;/code&gt; and &lt;code&gt;inquirer.js&lt;/code&gt;. It uses &lt;code&gt;Axios&lt;/code&gt; for making HTTP requests to the server, while the &lt;code&gt;socket.io-client&lt;/code&gt; library is used for establishing real-time communication with the server.&lt;/p&gt;

&lt;p&gt;The server is an Express app that uses the &lt;code&gt;socket.io&lt;/code&gt; library to enable real-time communication with the client. It uses &lt;code&gt;mongoose&lt;/code&gt; for storing user data on MongoDB, &lt;code&gt;bcrypt&lt;/code&gt; for hashing user passwords, &lt;code&gt;jsonwebtoken&lt;/code&gt; for authentication, and &lt;code&gt;redis&lt;/code&gt; for token storage.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Goals
&lt;/h2&gt;

&lt;p&gt;The main goal of the project was to build a command-line application that lets users chat with each other across several different terminals.&lt;/p&gt;

&lt;p&gt;The features I had in mind were private chat, group chat, and public chat messaging.&lt;/p&gt;

&lt;p&gt;I did some research and got a general overview of what the app would look like.&lt;/p&gt;

&lt;p&gt;From my research, I intended to make the user interface an NPM package and the server deployed somewhere online so that users could communicate with each other from their respective terminals.&lt;/p&gt;

&lt;p&gt;Initially, I did not know how to meet the goals of the project. But as a software developer who knows some fundamentals, I believed it was something I could do.&lt;/p&gt;

&lt;p&gt;During development, I changed my mind about the features and only implemented the public chat messaging feature. This feature allowed any user to create a chat room, which would be added to a list of chat rooms that any user could join and start messaging each other.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Features
&lt;/h2&gt;

&lt;p&gt;The key features of the projects are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; Each user is required to provide an email, username, and password during registration. It allows them to log in with their username and password on subsequent requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Username:&lt;/strong&gt; This feature allows for the unique identification of users. It is being used to identify each user in the chat rooms after a successful login.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creating and joining public chat rooms:&lt;/strong&gt; &lt;code&gt;Create-Chat-Room&lt;/code&gt; is a home menu option that allows any user to create a chat room and be added to a list of chat rooms that other users can join. &lt;code&gt;Join-Chat-Room&lt;/code&gt; is also a home menu option but displays a list of chat rooms that can be joined. With either of the two options, the user is automatically brought to a chat message interface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chat message interface:&lt;/strong&gt; This feature allows the user to type text-based messages into the command line. It is enabled only after a user successfully creates or joins a public chat room.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time chat:&lt;/strong&gt; The &lt;code&gt;socket.io&lt;/code&gt; and &lt;code&gt;socket.io-client&lt;/code&gt; libraries enable this feature to take user-typed messages from the command line and transmit them to all users in the chat room in real time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Home and Exit option:&lt;/strong&gt; The 'Exit' option is available throughout the application. It can either be accessed by choosing the option in the authentication and home menu or by typing &lt;code&gt;-e&lt;/code&gt; in the chat message interface. The 'Home' option can only be accessed in the chat message interface by typing &lt;code&gt;-h&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;The Model-View-Controller (MVC) design pattern inspired the structure of the project. The project is separated into two main folders: &lt;code&gt;/server&lt;/code&gt; and &lt;code&gt;/client&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The server folder includes the &lt;code&gt;/src&lt;/code&gt; folder, where the database schemas, configurations, and REST API logic live; a &lt;code&gt;/test&lt;/code&gt; folder, which contains the test cases for the API; &lt;code&gt;app.js&lt;/code&gt;, where the Express app is configured; &lt;code&gt;socketManager.js&lt;/code&gt;, where socket event management takes place for the server; &lt;code&gt;.env&lt;/code&gt;, which contains environmental variables for the database and authentication; and &lt;code&gt;server.js&lt;/code&gt;, which is the entry point of the server.&lt;/p&gt;

&lt;p&gt;The client folder consists of the &lt;code&gt;/src&lt;/code&gt; folder, which contains the logic for the main features of the app like authentication, user interface, and chat message interface; &lt;code&gt;attachEvents.js&lt;/code&gt;, which contains socket events for the client; and &lt;code&gt;commander.js,&lt;/code&gt; which is the node script for the user interface.&lt;/p&gt;

&lt;p&gt;In the next section, I dive into the project's server codebase.&lt;/p&gt;

</description>
      <category>socketio</category>
      <category>javascript</category>
      <category>node</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Introducing Terminal-Chat-App: Chat, Create, and Join Public Chat Rooms from the Command Line</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Tue, 23 May 2023 23:56:09 +0000</pubDate>
      <link>https://dev.to/hayatscodes/introducing-terminal-chat-app-chat-create-and-join-public-chat-rooms-from-the-command-line-291</link>
      <guid>https://dev.to/hayatscodes/introducing-terminal-chat-app-chat-create-and-join-public-chat-rooms-from-the-command-line-291</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The Terminal Chat App is a command-line chat application that allows users to join public chat rooms and chat from the command-line interface. Users can also create new chat rooms and interact with other users in real-time&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission: Wacky Wildcards
&lt;/h3&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/tarminal-chat-app" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/tarminal-chat-app&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie2uwrb9l8oh3iasvalx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie2uwrb9l8oh3iasvalx.jpg" alt="Terminal Chat App Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Video demo
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://youtu.be/GfpvChlPq3g" rel="noopener noreferrer"&gt;https://youtu.be/GfpvChlPq3g&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;The Terminal Chat App is a command-line chat application that allows users to join public chat rooms and chat from the command-line interface. Users can also create new chat rooms and interact with other users in real-time&lt;/p&gt;

&lt;p&gt;Features:&lt;br&gt;
Command-line interface&lt;br&gt;
Join public chat rooms&lt;br&gt;
Create public chat rooms&lt;br&gt;
Chat in real-time&lt;br&gt;
User authentication&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/HayatsCodes/terminal-chat-app" rel="noopener noreferrer"&gt;https://github.com/HayatsCodes/terminal-chat-app&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://choosealicense.com/licenses/mit/" rel="noopener noreferrer"&gt;MIT&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background (What made you decide to build this particular app? What inspired you?)
&lt;/h2&gt;

&lt;p&gt;As a software engineer looking for my first job, I wanted to be challenged and get out of my comfort zone by building a novel real-life project. I actually had the idea before the hackathon but procrastinated. However, the hackathon refueled me to build the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)
&lt;/h3&gt;

&lt;p&gt;I'd never heard of GitHub codespaces before the hackathon, and I got to find out what I'd been missing all along. It was at least 3X faster than my normal local workspace because of the dedicated virtual machines of up to 32 cores (I used the 2-core machine, and it was still significantly faster than my machine). I loved the fact that I used the default Linux image and didn't need to configure the workspace to get stuff done. I was so excited about GitHub codespaces that I posted about it on LinkedIn and also wrote an article about it on DEV. It was fun building on GitHub codespaces and I'm glad to have discovered it through this hackathon. I only used GitHub actions to automate the server's API test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;p&gt;The server was deployed to railway.app while the user interface was published on the NPM registry.&lt;/p&gt;

</description>
      <category>githubhack23</category>
    </item>
    <item>
      <title>From Tutorial Hell to Project Heaven: 5 Lessons from My First Project</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Mon, 15 May 2023 13:29:31 +0000</pubDate>
      <link>https://dev.to/hayatscodes/from-tutorial-hell-to-project-heaven-5-lessons-from-my-first-project-3neg</link>
      <guid>https://dev.to/hayatscodes/from-tutorial-hell-to-project-heaven-5-lessons-from-my-first-project-3neg</guid>
      <description>&lt;p&gt;Tutorial hell is every beginner's worst nightmare. We've all been there: we watch tons of tutorial videos but find it difficult to build a project from scratch. It's not because one doesn't understand the concepts taught in the tutorial, but because one is afraid of not knowing enough to start a project.&lt;/p&gt;

&lt;p&gt;My first project was an opportunity for me to apply what I had learned in a comprehensive course and gain confidence in my skills.&lt;/p&gt;

&lt;p&gt;However, building a project from scratch can be intimidating. I encountered several challenges while building my first project and learned things I could never have learned if I had relied solely on the course. Months later, that project is still one of my best backend projects.&lt;/p&gt;

&lt;p&gt;When I started learning backend development, I took a 40+ hour course to start with. Halfway through, I had learned the most fundamental concepts to start building backend projects.&lt;br&gt;
However, I got bored of the course and wanted to start building projects on my own. I was afraid to get started because I thought I wasn't ready until I finished the course.&lt;/p&gt;

&lt;p&gt;Eventually, I took the leap to build my first API project. It was an API that gets over twenty thousand free fiction books available on Project Gutenberg's website.&lt;/p&gt;

&lt;p&gt;At first, the idea was to build a basic book store API that allowed users to perform CRUD operations. However, I wanted to work on something unique and closely related to the project I did in the course, namely, an API that's not been implemented before.&lt;/p&gt;

&lt;p&gt;After several days of trying things out myself, I had a well-working API. The project broke me out of tutorial hell and gave me the confidence to build several projects after that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These are the 5 lessons I learned building my first project:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fundamentals are enough to start building:&lt;/strong&gt; Regardless of what you're trying to learn, knowing the fundamentals is enough to start building a project yourself. You don't have to learn every technology to start building things. Just start. I'd just learned NodeJS, Express, and MongoDB before my first project. That's just a fraction of the backend fundamentals, and I was able to build a live and usable project with it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The earlier, the better:&lt;/strong&gt; The earlier you start building projects, the easier it becomes for you to break out of tutorial hell. My first project gave me confidence to build more projects. It was hard at first, but it's now very easy for me to have an idea and start implementing it right away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You understand more by doing:&lt;/strong&gt; Watching or reading tutorials gives you a false sense of achievement. Fair enough; you might understand what's been taught in the tutorial, but you won't fully understand it without trying things out yourself. For example, the CRUD concept was very easy when I followed the course, but I didn't fully grasp it until I built my first project. API RESTful patterns were also difficult to grasp before my first project. Tutorials get you familiar with the concept, while projects solidify the knowledge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You will most likely learn a new concept while building:&lt;/strong&gt; Database pagination was something I never knew before my first project. Since I was working with over 20,000 books data, it was not efficient to get all of them at once. So I did some research and got to learn about pagination, which is simply resolving database queries by page. It improved the performance of the API significantly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concept retention is directly proportional to building:&lt;/strong&gt; By building projects and implementing the concepts you've learned, you reinforce your understanding and solidify your knowledge. It's a hands-on approach that helps you internalize the concepts and retain them more effectively compared to passive learning methods like just watching or reading. Before backend development, I tried frontend development, and I would make sure to watch the whole course before implementing a project of my own. But I realized that I'd mostly forgotten the concepts taught in the course. The solution is to build on each concept or two or more related concepts. By building my first project, I retained most of the concepts I'd learned so far, which are still useful for me today.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In conclusion, my first project was a significant turning point in my learning journey. It helped me break free from tutorial hell and gave me the confidence to pursue more projects independently. Through the challenges I encountered while building my first project, I gained valuable insights and learned crucial lessons that have stayed with me.&lt;/p&gt;

&lt;p&gt;In the end, it empowered me to embrace a project-driven approach to learning. It taught me the significance of building projects, practicing consistently, and seeking solutions through hands-on experience. I continue to carry these lessons with me as I tackle new challenges, and they have been instrumental in my growth as a developer.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Getting Started with GitHub Codespaces on VS Code</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Sun, 14 May 2023 14:29:11 +0000</pubDate>
      <link>https://dev.to/hayatscodes/getting-started-with-github-codespaces-on-vs-code-3i32</link>
      <guid>https://dev.to/hayatscodes/getting-started-with-github-codespaces-on-vs-code-3i32</guid>
      <description>&lt;p&gt;&lt;strong&gt;GitHub codespaces are one of the best development environments for developers. From a faster development environment to having access to a remote Linux environment, it's a workspace for all. &lt;br&gt;
In this tutorial, I will be teaching you one of the many ways to use GitHub codespaces. Let's get started.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What are GitHub codespaces?
&lt;/h2&gt;

&lt;p&gt;Before we get started with the tutorial, let's get to know more about GitHub codespaces and their features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub codespaces&lt;/strong&gt; are development environments hosted in the cloud. It allows users to configure these environments according to their needs. Some of the configuration options include setting up the bash terminal, runtimes for popular languages like Python, PHP, and Node, git for version control, yarn for package installation, e.t.c.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub codespaces&lt;/strong&gt; run on dedicated virtual machines ranging from 2 cores to up to 32 cores. Users can connect to their codespaces from the browser or locally using IDEs like VS Code and JetBrains. Users get up to 60 hours of free usage every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up VS Code as the default editor for GitHub codespaces
&lt;/h2&gt;

&lt;p&gt;By default, when creating a new codespace, it opens in the VSCode web client. To open codespaces in your locally installed VS Code, you need to install the GitHub Codespaces extension for Visual Studio Code.&lt;/p&gt;

&lt;p&gt;You can install GitHub codespaces &lt;a href="https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces"&gt;here&lt;/a&gt; by clicking the install button on the page. Note that you must have Visual Studio Code installed locally for the install button to redirect you to VS Code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once you've installed the extension, you can now follow the following steps to set VS Code as your default editor for GitHub codespaces:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Visit &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt; and make sure you're signed in to your account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click your profile photo in the upper-right corner of any page.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb4e1xieejuzp7pzfaby.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb4e1xieejuzp7pzfaby.jpg" alt="Clicking GitHub profile photo" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then click Settings from the drop-down menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9q3qjuxemjht8w7n32j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9q3qjuxemjht8w7n32j.jpg" alt="Clicking settings from GitHub profile drop down menu" width="184" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the left sidebar, scroll down and find the "Code, planning, and automation" section. Then click Codespaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbtyl8lr7dlj5y5npt21.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbtyl8lr7dlj5y5npt21.jpg" alt="Clicking codespaces on the left sidebar" width="300" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scroll down and find the "Editor preference" section. Then select Visual Studio Code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cyszommvaaf7ptlvde6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cyszommvaaf7ptlvde6.jpg" alt="Selecting VS Code in the Editor preference section" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub codespaces will now open on the VS Code desktop application when you next create or open a codespace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring VS Code for GitHub Codespaces
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before we get started creating our own codespace in VS Code, you will need to sign into the GitHub codespace using your GitHub credentials:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open VS Code, and in the Activity Bar, click the "Remote Explorer" icon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmtl9fgvmx2mg3kjt5xi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmtl9fgvmx2mg3kjt5xi.jpg" alt="Clicking remote explorer in VS Code" width="517" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; At the top of the "Remote Explorer", you will see a dropdown menu. Select "GitHub Codespaces" from the dropdown.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fije0cy15hgcy995xx8vz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fije0cy15hgcy995xx8vz.jpg" alt="Selecting GitHub codespaces from the dropdown menu" width="447" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the "Sign in to GitHub" button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7vtx2t3dc3l8hhpbteg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7vtx2t3dc3l8hhpbteg.jpg" alt="Signing into GitHub for GitHub Codespace" width="495" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You'll be prompted to sign in if you are not currently signed in to GitHub. Follow other instructions until you're back on the remote explorer again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffe3faowla1or2wd1aamv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffe3faowla1or2wd1aamv.jpg" alt="Signed in to codespace with GitHub" width="507" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You're now signed in to GitHub codespaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a GitHub Codespace in VS Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Now that we've setup GitHub codespaces for VS Code, let's create a new codespace:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the "Remote Explorer" icon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmtl9fgvmx2mg3kjt5xi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmtl9fgvmx2mg3kjt5xi.jpg" alt="Clicking remote explorer in VS Code" width="517" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use your mouse to hover over the remote explorer panel. Then click the "+" icon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1q8bpyfebcckev926mf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1q8bpyfebcckev926mf.jpg" alt="Clicking the + icon to create a codespace" width="510" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A text box and a list of your repositories will be displayed. Type and select the repo name you want to create the codespace for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4078ybsmlsj4uimokyn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4078ybsmlsj4uimokyn.jpg" alt="Selecting repo for codespace" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will be prompted to select a branch and a machine type. Go ahead and select your preferred settings.&lt;/p&gt;

&lt;p&gt;You can now start to develop in the codespace in VS Code&lt;/p&gt;

&lt;h2&gt;
  
  
  Opening a GitHub Codespace in VS Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Click the "Remote Explorer" icon.&lt;/li&gt;
&lt;li&gt;In the "GitHub Codespaces" panel, hover over the codespace and select the plug icon to connect to the codespace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs2ahvfs6rj7u3o36kbk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs2ahvfs6rj7u3o36kbk.jpg" alt="Clicking the plug icon to connect to a codespace" width="577" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now enjoy developing in your codespace.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>githubhack23</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Technologies and Tools - GitHub + DEV Hackathon Progress Update</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Thu, 11 May 2023 21:26:48 +0000</pubDate>
      <link>https://dev.to/hayatscodes/technologies-and-tools-github-dev-hackathon-progress-update-4312</link>
      <guid>https://dev.to/hayatscodes/technologies-and-tools-github-dev-hackathon-progress-update-4312</guid>
      <description>&lt;p&gt;&lt;strong&gt;I did some research on the technologies and tools that will enable the project to be a success. So I will be listing the technologies I will be using for the projects in this article. I will be updating it as I build the app.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Language/Framework: NodeJS/Express
&lt;/h3&gt;

&lt;p&gt;I'm a backend developer experienced with NodeJS and Express, so the app's interface and server will be built with these frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command Line Libraries: Commander and Inquirer
&lt;/h3&gt;

&lt;p&gt;I will be using these frameworks to build the command-line interface that will interact with the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Socket Library: Socket.io
&lt;/h3&gt;

&lt;p&gt;I'm new to web sockets but found this to be the best library for real-time apps. This will facilitate real-time communication between the client and the server.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Idea: A Command-Line Chat Application - GitHub + DEV Hackathon Progress Update</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Thu, 11 May 2023 13:36:58 +0000</pubDate>
      <link>https://dev.to/hayatscodes/idea-a-command-line-chat-application-github-dev-hackathon-progress-update-1ng4</link>
      <guid>https://dev.to/hayatscodes/idea-a-command-line-chat-application-github-dev-hackathon-progress-update-1ng4</guid>
      <description>&lt;p&gt;Hello DEVs. I'm excited to participate in my first hackathon. I got the idea for a command-line chat application a few weeks ago and have been procrastinating on it. But the &lt;strong&gt;GitHub + DEV Hackathon&lt;/strong&gt; has reignited my interest in building the application.&lt;/p&gt;

&lt;p&gt;The goal of this project is to create a real-time chat application that allows users to create and join room chats from the command line. The project is mostly targeted at anyone who is not afraid of the command line (lol).&lt;/p&gt;

&lt;p&gt;The project falls under the &lt;strong&gt;Wacky Wildcards&lt;/strong&gt; category of the hackathon. And I'm looking forward to building a silly and fun application.&lt;/p&gt;

&lt;p&gt;I hope to learn new technologies and become a better developer while also winning some cool prizes by the end of the hackathon.&lt;/p&gt;

</description>
      <category>githubhack23</category>
      <category>node</category>
      <category>hackathon</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>The JavaScript Assignment, Equality, and Strict Equality operators</title>
      <dc:creator>Hayatudeen Abdulrahman</dc:creator>
      <pubDate>Sun, 28 Aug 2022 21:48:30 +0000</pubDate>
      <link>https://dev.to/hayatscodes/the-javascript-assignment-equality-and-strict-equality-operators-3i5f</link>
      <guid>https://dev.to/hayatscodes/the-javascript-assignment-equality-and-strict-equality-operators-3i5f</guid>
      <description>&lt;p&gt;The equal (=), double equal (==), and triple equal (===) signs, which are respectively known as the assignment, equality, and strict equality operators in JavaScript, often confuse junior JavaScript developers. Inappropriate use of these operators can make programs run abnormally, sometimes without the compiler throwing an error, making codes difficult to debug.&lt;/p&gt;

&lt;p&gt;In this article, I will explain what each of them does and how and when to use them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lets dive in!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Assignment Operator (=)
&lt;/h3&gt;

&lt;p&gt;The assignment operator is the same across all programming languages. As its name implies, in the simplest form, it is used to assign values to variables. The variable is on the left-hand side and the assigned value is on the right-hand side.&lt;/p&gt;

&lt;p&gt;It can also be combined with the arithmetic operators to perform simple operations before assignment. For more on this, click &lt;a href="https://www.educba.com/javascript-assignment-operators/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XMmpYClI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560968973/uT9743qE5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XMmpYClI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560968973/uT9743qE5.png" alt="" width="727" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image illustrates the usage of the assignment operator and its shorthand forms.&lt;/p&gt;

&lt;p&gt;It was coded as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Line 1 declared a variable called &lt;code&gt;newVariable&lt;/code&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 2 assigned the value 20 to &lt;code&gt;newVariable&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Line 36 syntax is &lt;code&gt;newVariable = newVariable &amp;lt;op&amp;gt; 10&lt;/code&gt;&lt;em&gt;. Where&lt;/em&gt; &lt;code&gt;&amp;lt;op&amp;gt;&lt;/code&gt; &lt;em&gt;is replaced with the appropriate operator, e.g., line 3 is the same as&lt;/em&gt; &lt;code&gt;newVariable = newVariable + 10&lt;/code&gt; &lt;strong&gt;&lt;em&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The assignment operator has &lt;a href="https://www.telerik.com/blogs/details/operator-precedence-javascript#:~:text=Operator%20Precedence%20Operator%20precedence%20in%20JavaScript%20determines%20the,the%20order%20to%20perform%20a%20given%20mathematical%20expression."&gt;right-to-left associativity&lt;/a&gt;, that is, operations are performed from right to left. Thus, line 3 of the code will add &lt;code&gt;newVariable&lt;/code&gt; to 10 before assigning the result back to &lt;code&gt;newVariable&lt;/code&gt; &lt;em&gt;(itself)&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Equality Operator (==)
&lt;/h3&gt;

&lt;p&gt;The equality operator, which has a double equal sign, is also similar across most programming languages.&lt;/p&gt;

&lt;p&gt;The operator is used to compare two or more expressions and always evaluates to a true or false (boolean) value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8R0PJXLs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560970946/kzUGt2pX_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8R0PJXLs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560970946/kzUGt2pX_.png" alt="" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I assigned 100 to variable &lt;code&gt;num1&lt;/code&gt; and 300 to variable &lt;code&gt;num2&lt;/code&gt;, then&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;num1&lt;/code&gt; and &lt;code&gt;num2&lt;/code&gt; variables are compared and at the same time stored in a result variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And a false value is logged to the console since the values of &lt;code&gt;num1&lt;/code&gt; and &lt;code&gt;num2&lt;/code&gt; are not equal.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before comparison, the operator also performs type conversion if necessary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gm0Mm_h0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560972459/qkj1lCvmh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gm0Mm_h0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560972459/qkj1lCvmh.png" alt="" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above code compares a variable string of 10 with a variable number of 10 using the equality operator, and a true value is obtained, though theyre of different types.&lt;/p&gt;

&lt;p&gt;There is also an inequality operator (!=) in JavaScript which does the exact opposite of the equality (==) operator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---XXF0AvT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560974211/A1zPO-7FY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---XXF0AvT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560974211/A1zPO-7FY.png" alt="" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Changing the equality operator in the above code to an inequality operator, a truthy value is obtained.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Strict Equality Operator (===)
&lt;/h3&gt;

&lt;p&gt;The strict equality operator, unlike the other two operators, is only available in two programming languages: PHP and JavaScript.&lt;/p&gt;

&lt;p&gt;The operator works like the equality operator, except that it does not make a type conversion before comparison.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MQwsigE6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560975482/8njhRymGq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MQwsigE6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560975482/8njhRymGq.png" alt="" width="766" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above diagram, a comparison using the strict equality operator, though having a value of 10 of different types, evaluates to false.&lt;/p&gt;

&lt;p&gt;Using the strict equality operator is considered as best practice to avoid code bugs.&lt;/p&gt;

&lt;p&gt;The strict inequality operator (!==) in JavaScript does the exact opposite of the strict equality (===) operator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aUuxueDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560976654/BsxougyCF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aUuxueDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1672560976654/BsxougyCF.png" alt="" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above code checks if string 50 is not equal to number 50, using the strict equality operator, and it evaluates to true.&lt;/p&gt;

&lt;p&gt;Check &lt;a href="https://stackoverflow.com/questions/359494/which-equals-operator-vs-should-be-used-in-javascript-comparisons"&gt;here&lt;/a&gt; for more on the difference between equality and strict equality operators.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;In this article, I explain the difference between the assignment, equality, and strict equality operators. You should now be able to distinguish between the three operators as well as their use cases.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
