One place for hosting & domains

      BuiltIn

      How To Use Built-In and Custom Directives in Vue.js


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      As a front-end framework, Vue.js can be thought of as a mixture of React and Angular. Vue borrows the prop-driven approach of React, but also uses directives, which were made popular by Angular. In this context, directives are reusable chunks of code or logic that developers can use within an HTML template. These can allow you to manipulate your HTML in many different ways, such as conditionally rendering an element, connecting events to an element, or creating dynamic attributes that depend on your Vue code.

      In this tutorial, you will try out some of the most common built-in Vue directives, such as v-if, v-show, v-on, v-bind, v-model, and v-html. For these first sections, the tutorial will lead you through examples that you can follow in an online Vue playground like the Vue Single File Component Playground. In addition to this, you will create a new Vue project to try out custom directives that you can add to HTML elements for additional functionality.

      Prerequisites

      Using the v-if, v-else, and v-else-if Directives

      Vue.js comes pre-packaged with a number of directives that developers commonly use when working within the framework. Directives such as v-if, v-show, v-on, v-model, v-bind, v-html, and more are all provided for you out-of-the-box. All of these provided directives start with v-.

      The v-if, v-else, and v-else-if directives allow you to create conditionally rendered HTML elements, and work similarly to JavaScript’s if, else if, else conditions. Instead of returning HTML or code like you would in JavaScript, Vue will conditionally render that block of code.

      To apply a Vue directive, you will add it to an HTML element in the template section of your Vue single-file component. For example, if you wanted to add a v-if directive to a paragraph element, you would use the following syntax:

      <p v-if="https://www.digitalocean.com/community/tutorials/condition"></p>
      

      In this case, the highlighted condition is a placeholder for the boolean expression that determines if the paragraph element is rendered or not.

      To illustrate how the v-if directives work in a real situation, create the following code for a user welcome page:

      <template>
        <p v-if="user.firstName && user.lastName">
          Welcome, {{ user.firstName }} {{ user.lastName }}!
        </p>
        <p v-else-if="user.username">
          Welcome, {{ user.username }}!
        </p>
        <div v-else>
          <button>Login</button>
          <button>Create Account</button>
        </div>
      </template>
      
      <script setup>
          const user = {
          firstName: 'Sammy',
          lastName: 'Shark',
          username: 'sammyDO'
        }
      </script>
      

      In this code, there are a number of HTML elements, including two <p> tags and a <div> tag with two buttons. However, when this code runs in the browser, only one of these chunks of code will render, depending on the condition that evaluates to true. HTML with false conditions will not render in your DOM (Document Object Model).

      As it is now, since both firstName and lastName have values in the <script> tag, the expression user.firstName && user.lastName will evaluate to true, and the browser will render the paragraph. This will look like the following image in your browser:

      Web page with the words

      If either firstName or lastName is not defined, the condition will evaluate to false, and Vue will move to the v-else-if directive. This directive is attached to the user.username expression, so if this evaluates to true it will render the attached paragraph element. To try this, delete the firstName in your <script> tag:

      ...
      <script setup>
          const user = {
          lastName: 'Shark',
          username: 'sammyDO'
        }
      </script>
      

      Since the condition user.firstName && user.lastName now evaluates to false but user.username evaluates to true, the username will render instead. This is shown in the following image:

      Web page with the words

      Lastly, if all the conidtions evaluate to false, control will pass to the v-else directive, which here renders two buttons to log in or create an account. To try this out, delete the username data in the script element:

      ...
      <script setup>
          const user = {
          lastName: 'Shark',
        }
      </script>
      

      Now you will find default buttons rendered in the browser:

      Web page with

      It is important to note that you must have an element with a v-if directive directly before a v-else-if or v-else directive. Otherwise it will not work.

      As an example, the following code snippet is considered invalid and you will get an error in your browser’s console:

      <template>
        <p v-if="user.firstName && user.lastName">
          Welcome, {{ user.firstName }} {{ user.lastName }}!
        </p>
        <p v-else-if="user.username">
          Welcome, {{ user.username }}!
        </p>
        <h1>Some Title</h1>
        <div v-else>
          <button>Login</button>
          <button>Create Account</button>
        </div>
      </template>
      
      <script setup>
          const user = {
          firstName: 'Sammy',
          lastName: 'Shark',
          username: 'sammyDO'
        }
      </script>
      

      Since the <h1> element after the paragraph with the v-else-if directive has no directive, this code will fail to render and will yield the following syntax error:

      Output

      SyntaxError: v-else/v-else-if has no adjacent v-if.

      With this summary of v-if covered, you can now try out another way to conditionally display HTML elements: the v-show directive.

      Using the v-show Directive

      Beyond the v-if-related directives, there is another directive that you can use to display HTML elements based on a condition. That directive is v-show. It’s similar to v-if, with the exceptions that there are no else-if and else counterparts and the elements are conditionally displayed rather than conditionally rendered.

      Take a look at the following code that addresses a similar situation as in the last section:

      <template>
        <div v-show="!userIsLoggedIn">
          <button>Login</button>
          <button>Create Account</button>
        </div>
        <div v-show="userIsLoggedIn">
          <p>Welcome!</p>
        </div>
      </template>
      
      <script setup>
          const userIsLoggedIn = true
      </script>
      

      In this code, you’ve attached v-show to two <div>s, along with conditional expressions. v-show will display the attached HTML if the condition in the quotation marks is met. In this example, userIsLoggedIn evaluates to true, so the browser will display the <div> with the Welcome! paragraph:

      Web page with the words

      Now change the userIsLoggedIn value to false:

      ...
      <script setup>
          const userIsLoggedIn = false
      </script>
      

      You will again find the login buttons:

      Web page with

      It is worth emphasizing the difference between v-if and v-show. v-if conditionally renders the HTML; if the conditional expression evaluates to false, the DOM will not include it at all. v-show on the other hand will always render the HTML in the DOM. But the element will not appear in the browser because it will be hidden with the display: none CSS style.

      With the conditional v-if and v-show directives covered, you can now move on to the v-on directive to tie events to HTML elements.

      Using the v-on Directive

      The v-on directive executes functions on a specific event. This can be a custom event or a standard JavaScript event, such as click, hover, or mouseenter. When using the v-on directive, you must provide the event type after a colon (:) and the function to be executed. The component’s function name will reside in-between the quotation marks.

      As an example of this, examine the following highlighted code:

      <template>
        <button v-on:click="handleClick">Click Me</button>
      </template>
      
      <script setup>
          function handleClick() {
          alert('You clicked the button!')
        }
      </script>
      

      In this example, the component function handleClick will execute when the user clicks on the <button> element that the directive is attached to. If you run this code and click on the button, you will receive an alert that reads You clicked the button!:

      Browser popup box that reads

      Events with the v-on directive can also have event modifiers chained to the event itself. These event modifiers can change how the event is executed, and can save time when writing functionality that would normally take multiple lines of JavaScript. Some modifiers provided by Vue include:

      • once: Limits the event to fire only once.
      • self: Event will only trigger if the event target is the same element that holds the directive.
      • prevent: Stops the event from happening.
      • stop: Stops the event from propagating.

      Next, you will run through an example of the once event modifier to try out the syntax. Add the following highlighted code to the previous snippet:

      <template>
        <button v-on:click.once="handleClick">Click Me</button>
      </template>
      ...
      

      With the .once modifier, the function handleClick will only execute one time. Try clicking the Click Me button, and the alert will pop up. Clear the alert, then click it again. The event will not fire because it has already fired once.

      The v-on directive also has a shorthand syntax. To use the shorthand syntax, replace v-on: with @.

      <template>
        <button @click.once="handleClick">Click Me</button>
      </template>
      ...
      

      This will lead to the same behavior as v-on:click.once.

      Now that events have been addressed with v-on, you can move on to binding data to template elements with v-bind and v-model.

      Using the v-bind and v-model Directives

      Vue.js is a Model-View-ViewModel (MVVM) framework, which means separate data (the model) updates the HTML (view), and the view in turn updates the model. This separation means that view, or what renders in the browser, can operate independently of the data processing.

      To enact the MVVM approach, Vue provides ways for data, props, and computed properties to pass into a Vue component. This is done with two-way data binding, which allows for the view and model to interact with each other.

      In the following code, you have a <p> element with dynamic content that changes depending on the data values:

      <template>
        <p>I am from {{ city }}.</p>
      </template>
      
      <script setup>
          const city = 'Cincinnati'
      </script>
      

      In this snippet, {{ city }} is a data reference to the city constant in the <script> element after it. As it is now, {{ city }} will evaluate to the string "Cincinnati", and a paragraph element with the words I am from Cincinnati will render in your browser. If you were to change Cincinnati to Seattle, the {{ city }} in the template would update accordingly.

      For this dynamic content, you don’t need to use a directive: The template has access to the city constant automatically. However, if you wanted to make an HTML attribute dynamic by referencing the <script> data, you would have to explicitly bind the data with v-bind.

      To illustrate this, you will enclose the {{ city }} placeholder in a link (<a>) tag that will dynamically lead to the Wikipedia page for the city assigned to the city constant. First, wrap the {{ city }} in an <a> tag so the user can click on it and get more information on the city itself. After that, create a computed property called wikipediaLink and have it return the appropriate URL. Add the highlighted code:

      <template>
        <p>I am from <a href="">{{ city }}</a>.</p>
      </template>
      
      <script setup>
        import { computed } from 'vue'
      
          const city = 'Cincinnati"https://www.digitalocean.com/community/tutorials/const wikipediaLink = computed(() =>`https://en.wikipedia.org/wiki/${city}`)
      </script>
      

      In the <script> element, you imported the computed function from the Vue library, then used it to assign a URL to wikipediaLink. The URL string uses a template literal to change depending on the value of city.

      Now that you have your Wikipedia link, go ahead and add the computed property name to the href attribute of the anchor tag you added earlier:

      <template>
        <p>I am from <a href="https://www.digitalocean.com/community/tutorials/wikipediaLink">{{ city }}</a>.</p>
      </template>
      ...
      

      If you were to view this in a browser at this point, you would recieve a console error. This is because right now Vue.js thinks wikipediaLink is a string literal, which is not a valid URL.

      To tell Vue.js to use the returned value of the wikipediaLink computed property, you need to use the v-bind directive. This will bind the reference in the template to the computed property, as shown in the following:

      <template>
        <p>I am from <a v-bind:href="https://www.digitalocean.com/community/tutorials/wikipediaLink">{{ city }}</a>.</p>
      </template>
      ...
      

      Now, the href value will be the URL for the Cincinnati Wikipedia page. This URL will update if the city property changes to another city, such as Seattle.

      Similar to v-on, v-bind also has a shorthand that you can opt to use. To use the shorthand, replace v-bind: with :, as shown in the following:

      <template>
        <p>I am from <a :href="https://www.digitalocean.com/community/tutorials/wikipediaLink">{{ city }}</a>.</p>
      </template>
      

      There is another directive in Vue.js that is used to bind a value to a data property. However, this directive only works for input tags:

      <template>
        <input v-model="city" type="text" />
      </template>
      
      <script setup>
        const city = 'Cincinnati'
      </script>
      

      When using the v-model directive, you are binding the input fields value attribute to the data property in the quotation marks. It is recommended to use the v-model directive over v-bind for form <input /> tags.

      This Vue component will render a text input box with the default text of Cincinnati, as shown in the following image:

      Text input box with

      This section covered different ways to bind HTML attributes to variable or computed data. Next, you will use the v-html directive to inject HTML into your template.

      Using the v-html Directive

      The last pre-packaged directive that this tutorial will go over is v-html. This directive converts HTML inside of a string literal into raw HTML for the browser to read, which will allow you more flexibility in how you create and apply your HTML.

      In this hypothetical component, you have a <div> that has a v-html directive with a data property as its value:

      <template>
        <div v-html="someHtmlCode" />
      </template>
      
      <script setup>
        const someHtmlCode = `<p>Some <span>HTML</span> in this string</p>`
      </script>
      

      The v-html directive is telling Vue.js to inject the string of the someHtmlCode data reference into the <div /> in the template. When compiled by Vue, you will find the following in your DOM:

      <div>
        <p>Some <span>HTML</span> in this string</p>
      </div>
      

      The HTML will also render in your browser:

      A browser window with

      This directive is useful if you need to render HTML that comes from a REST API service or large HTML from an external JavaScript file.

      At this point in the tutorial, you’ve tried out the directives that are provided to you by Vue.js. However, there may be times when you will need a directive that Vue doesn’t provide. In cases such as these, you can create a custom directive to handle specific logic in your HTML template.

      Creating a Custom Directive

      In the Vue.js framework, you have the ability to create custom directives to suit the individual needs for your project. In this section, for example, you will create a v-theme directive that applies a specific style to an element of your template.

      Up to this point, you did not have to follow along with a stand-alone Vue project. However, in this section, you will create a fresh project generated from the Vue CLI.

      Open your terminal and generate a new project with the following command. The name of this project will be custom-directive, which will also be the name of the root directory:

      • vue create custom-directive

      For this tutorial, select Vue 3x for Choose Vue Version. Once the app has been created, open your generated project in your text editor of choice. Then open the main.js file in the src directory.

      First, you will get this file set up. Store createApp(App) into a const so you can reference it later:

      custom-directive/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue"https://www.digitalocean.com/community/tutorials/const app = createApp(App)
      
      app.mount('#app')
      

      Now with that createApp function in its own const, you can extend this app by adding a directive:

      custom-directive/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue'
      
      const app = createApp(App)
      
      app.directive("theme", {})
      
      app.mount('#app')
      

      In this code, you are leveraging the directive() function on the Vue instance. This function accepts two arguments: the directive name (theme in this case) and an object with lifecycle hooks.

      You will set this code up so that it executes when the component is mounted. This mounted hook accepts two arguments: el, the HTML element, and binding, the value passed into the directive. With this information, you can construct the following:

      custom-directive/src/main.js

      const app = createApp(App)
      
      app.directive("theme", {
        mounted(el, binding) {
      
        }
      })
      
      app.mount('#app')
      

      For this theme directive, you will pass in a string to determine the element’s styling. This string will either be primary, secondary, or tertiary, each of which corresponds to a color in a hypothetical colorscheme.

      Now that the directive code is in place, you can add the logic. To access the value inside of the quotaion marks of the directive, you can use binding.value. To change the color of the element’s text, you will use JavaScript to access the el properties:

      custom-directive/src/main.js

      const app = createApp(App)
      
      app.directive("theme", {
        mounted(el, binding) {
          if (binding.value === 'primary') {
            el.style.color="red"
          } else if (binding.value === 'secondary') {
            el.style.color="green"
          } else if (binding.value === 'tertiary') {
            el.style.color="blue"
          } else {
            el.style.color="black"
          }
        }
      })
      
      app.mount('#app')
      

      The highlighted section of this snippet is a series of if/else statements. If the value passed into the directive is primary, the text color will be red, secondary will be green, tertiary will be blue, and no value will revert to the default black.

      Close and save your main.js file.

      At this point, you have created a custom directive that you can use in your Vue.js application. Vue will automatically prefix the directive name with v-, so the template can access your directive as v-theme.

      Open your App.vue file in your text editor of choice. In the template, add a <p> tag with some text in it:

      custom-directive/src/App.vue

      <template>
        <p>This text will change color based on the directive value!</p>
      </template>
      

      Save this file, then start the application with npm run serve. This will run your project on the :8080 port on your localhost:

      To view your generated project, open your browser of choice and visit localhost:8080 in the URL bar. You will find your paragraph element rendered in black:

      The words

      Next, change the color by adding your directive to the HTML:

      custom-directive/src/App.vue

      <template>
        <p v-theme="`primary`">This text will change color based on the directive value!</p>
      </template>
      

      Save this file and open it in your web browser. The value inside the quotation marks is a raw value and automatically bound to Vue, so inside the quotation marks you will need to wrap primary using back ticks to convert it to a string.

      Save your file and the text on your rendered site will turn red:

      The words

      The directive reads the value in the binding object and executes the code accordingly. Since the value is primary, JavaScript will change the color of the text to red.

      In this section, you created a custom directive, registered it, and executed its logic. You also added your new custom directive and assigned it to an HTML element in the template.

      Conclusion

      In this tutorial, you ran through exercises to test out what directives are and how to use them. Specifically, you used the most common built-in directives, including v-if, v-on, v-show, and v-html. In addition to this, you registered and created your own custom directive. This directive, v-theme, can now be used on any HTML element to execute a JavaScript function.

      While you went over a number of directives, there are many more made available to you by Vue.js. For more information regarding directives, it is recommended to review the official Vue.js Documentation. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      Leveraging MongoDB’s Built-in Authentication and Authorization Methods



      Part of the Series:
      MongoDB Security: Best Practices to Keep Your Data Safe

      MongoDB, also known as Mongo, is a document database used in many modern web applications. As with any database management system, it’s critical that those responsible for managing a Mongo database adhere to the recommended security best practices, both to prevent data from being lost in the event of a disaster and to keep it out of the hands of malicious actors.

      This series of conceptual articles provides a high-level overview of MongoDB’s built-in security features while also highlighting some general database security best practices.

      Authorization and authentication are two concepts that are critical for understanding database security. These two concepts are similar, but it’s important to understand what they are and what makes them different. Authentication is the process of confirming whether a user or client is actually who they claim to be. Authorization, on the other hand, involves setting rules for a given user or group of users to define what actions they can perform and which resources they can access.

      Authentication

      MongoDB features several mechanisms that allow you to authenticate users, with the default mechanism being its Salted Challenge Response Authentication Mechanism (SCRAM). SCRAM involves MongoDB reading and verifying credentials presented by a user against a combination of their username, password, and authentication database, all of which are known by the given MongoDB instance. If any of the user’s credentials don’t match what the Mongo database expects, the database won’t authenticate the user and they won’t gain access until they present the correct username, password, and authentication database.

      You can also use a text file to act as a shared password for a group of connected MongoDB instances, such as a replica set or shard cluster. This method, known as keyfile authentication, is considered to be a bare-minimum form of security and is best suited for testing or development environments, as advised by the MongoDB documentation.

      For production environments that implement sharding or replication, the MongoDB documentation recommends using another authentication mechanism: x.509 authentication. This involves distributing valid x.509 certificates — either self-signed or obtained from a third-party certificate authority — to the intended cluster members or clients. These are different from keyfiles, though, in that each machine gets its own dedicated x.509 certificate. This means that one machine’s certificate will only be useful for authenticating that machine. A client that presents a stolen x.509 certificate to the database server will not be able to authenticate.

      x.509 authentication leverages a concept known as mutual authentication. This means when a client or cluster member authenticates themself to the server, the server is likewise authenticating itself to the client or cluster member. If the client or cluster member attempts to connect to a database server with an invalid x.509 certificate, it will be prevented from doing so since the mutual authentication will fail.

      Authorization

      MongoDB manages authorization through a computer security concept known as role-based access control. Whenever you create a MongoDB user, you have the option to provide them with one or more roles. A role defines what privileges a user has, including what actions they can perform on a given database, collection, set of collections, or cluster. When you assign a role to a user, that user receives all the privileges of that role.

      MongoDB comes with a number of built-in roles that provide commonly-needed privileges. A few of these are available for every database, but most are only available for the admin database, as they’re intended to provide powerful administrative privileges. For example, you can assign a user the readWrite role on any database, meaning that you can read and modify the data held in any database on your system as long as you’ve granted a user the readWrite role over it. However, the readWriteAnyDatabase role — which allows the user to read and modify data on any database except for local and config — is only available in the admin database, as it provides broader system privileges.

      In addition to its built-in roles, Mongo also allows you to define custom roles, giving you even more control over what resources users can access on your system. Like users, roles are added in a specific database. Other than roles created in the admin database, which can include privileges to any database in the system, a user-defined role’s privileges only apply to the database in which the role was created. With that said, a role can include one or more existing roles in its definition, and a role can inherit privileges from other roles in the same database.

      With such fine-grained control over user privileges, you can set up dedicated users to perform certain functions, like a cluster administrator to manage replica sets and sharded clusters or a user administrator to create and manage users and custom roles. This type of user management strategy can also help harden your system’s security, as it reduces the number of users with broad privileges.



      Source link

      How To Debug Node.js with the Built-In Debugger and Chrome DevTools


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      In Node.js development, tracing a coding error back to its source can save a lot of time over the course of a project. But as a program grows in complexity, it becomes harder and harder to do this efficiently. To solve this problem, developers use tools like a debugger, a program that allows developers to inspect their program as it runs. By replaying the code line-by-line and observing how it changes the program’s state, debuggers can provide insight into how a program is running, making it easier to find bugs.

      A common practice programmers use to track bugs in their code is to print statements as the program runs. In Node.js, that involves adding extra console.log() or console.debug() statements in their modules. While this technique can be used quickly, it is also manual, making it less scalable and more prone to errors. Using this method, it is possible to mistakenly log sensitive information to the console, which could provide malicious agents with private information about customers or your application. On the other hand, debuggers provide a systematic way to observe what’s happening in a program, without exposing your program to security threats.

      The key features of debuggers are watching objects and adding breakpoints. By watching objects, a debugger can help track the changes of a variable as the programmer steps through a program. Breakpoints are markers that a programmer can place in their code to stop the code from continuing beyond points that the developer is investigating.

      In this article, you will use a debugger to debug some sample Node.js applications. You will first debug code using the built-in Node.js debugger tool, setting up watchers and breakpoints so you can find the root cause of a bug. You will then use Google Chrome DevTools as a Graphical User Interface (GUI) alternative to the command line Node.js debugger.

      Prerequisites

      Step 1 — Using Watchers with the Node.js Debugger

      Debuggers are primarily useful for two features: their ability to watch variables and observe how they change when a program is run and their ability to stop and start code execution at different locations called breakpoints. In this step, we will run through how to watch variables to identify errors in code.

      Watching variables as we step through code gives us insight into how the values of variables change as the program runs. Let’s practice watching variables to help us find and fix logical errors in our code with an example.

      We begin by setting up our coding environment. In your terminal, create a new folder called debugging:

      Now enter that folder:

      Open a new file called badLoop.js. We will use nano as it’s available in the terminal:

      Our code will iterate over an array and add numbers into a total sum, which in our example will be used to add up the number of daily orders over the course of a week at a store. The program will return the sum of all the numbers in the array. In the editor, enter the following code:

      debugging/badLoop.js

      let orders = [341, 454, 198, 264, 307];
      
      let totalOrders = 0;
      
      for (let i = 0; i <= orders.length; i++) {
        totalOrders += orders[i];
      }
      
      console.log(totalOrders);
      

      We start by creating the orders array, which stores five numbers. We then initialize totalOrders to 0, as it will store the total of the five numbers. In the for loop, we iteratively add each value in orders to totalOrders. Finally, we print the total amount of orders at the end of the program.

      Save and exit from the editor. Now run this program with node:

      The terminal will show this output:

      Output

      NaN

      NaN in JavaScript means Not a Number. Given that all the input are valid numbers, this is unexpected behavior. To find the error, let’s use the Node.js debugger to see what happens to the two variables that are changed in the for loop: totalOrders and i.

      When we want to use the built-in Node.js debugger on a program, we include inspect before the file name. In your terminal, run the node command with this debugger option as follows:

      When you start the debugger, you will find output like this:

      Output

      < Debugger listening on ws://127.0.0.1:9229/e1ebba25-04b8-410b-811e-8a0c0902717a < For help, see: https://nodejs.org/en/docs/inspector < Debugger attached. Break on start in badLoop.js:1 > 1 let orders = [341, 454, 198, 264, 307]; 2 3 let totalOrders = 0;

      The first line shows us the URL of our debug server. That’s used when we want to debug with external clients, like a web browser as we’ll see later on. Note that this server listens on port :9229 of the localhost (127.0.0.1) by default. For security reasons, it is recommended to avoid exposing this port to the public.

      After the debugger is attached, the debugger outputs Break on start in badLoop.js:1.

      Breakpoints are places in our code where we’d like execution to stop. By default, Node.js’s debugger stops execution at the beginning of the file.

      The debugger then shows us a snippet of code, followed by a special debug prompt:

      Output

      ... > 1 let orders = [341, 454, 198, 264, 307]; 2 3 let totalOrders = 0; debug>

      The > next to 1 indicates which line we’ve reached in our execution, and the prompt is where we will type in our commends to the debugger. When this output appears, the debugger is ready to accept commands.

      When using a debugger, we step through code by telling the debugger to go to the next line that the program will execute. Node.js allows the following commands to use a debugger:

      • c or cont: Continue execution to the next breakpoint or to the end of the program.
      • n or next: Move to the next line of code.
      • s or step: Step into a function. By default, we only step through code in the block or scope we’re debugging. By stepping into a function, we can inspect the code of the function our code calls and observe how it reacts to our data.
      • o: Step out of a function. After stepping into a function, the debugger goes back to the main file when the function returns. We can use this command to go back to the original function we were debugging before the function has finished execution.
      • pause: Pause the running code.

      We’ll be stepping through this code line-by-line. Press n to go to the next line:

      Our debugger will now be stuck on the third line of code:

      Output

      break in badLoop.js:3 1 let orders = [341, 454, 198, 264, 307]; 2 > 3 let totalOrders = 0; 4 5 for (let i = 0; i <= orders.length; i++) {

      Empty lines are skipped for convenience. If we press n once more in the debug console, our debugger will be situated on the fifth line of code:

      Output

      break in badLoop.js:5 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      We are now beginning our loop. If the terminal supports color, the 0 in let i = 0 will be highlighted. The debugger highlights the part of the code the program is about to execute, and in a for loop, the counter initialization is executed first. From here, we can watch to see why totalOrders is returning NaN instead of a number. In this loop, two variables are changed every iteration—totalOrders and i. Let’s set up watchers for both of those variables.

      We’ll first add a watcher for the totalOrders variable. In the interactive shell, enter this:

      To watch a variable, we use the built-in watch() function with a string argument that contains the variable name. As we press ENTER on the watch() function, the prompt will move to the next line without providing feedback, but the watch word will be visible when we move the debugger to the next line.

      Now let’s add a watcher for the variable i:

      Now we can see our watchers in action. Press n to go to the next step. The debug console will show this:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = 0 1: i = 0 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      The debugger now displays the values of totalOrders and i before showing the line of code, as shown in the output. These values are updated every time a line of code changes them.

      At this point, the debugger is highlighting length in orders.length. This means the program is about to check the condition before it executes the code within its block. After the code is executed, the final expression i++ will be executed. You can read more about for loops and their execution in our How To Construct For Loops in JavaScript guide.

      Enter n in the console to enter the for loop’s body:

      Output

      break in badLoop.js:6 Watchers: 0: totalOrders = 0 1: i = 0 4 5 for (let i = 0; i <= orders.length; i++) { > 6 totalOrders += orders[i]; 7 } 8

      This step updates the totalOrders variable. Therefore, after this step is complete our variable and watcher will be updated.

      Press n to confirm. You will see this:

      Output

      Watchers: 0: totalOrders = 341 1: i = 0 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      As highlighted, totalOrders now has the value of the first order: 341.

      Our debugger is just about to process the final condition of the loop. Enter n so we execute this line and update i:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = 341 1: i = 1 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      After initialization, we had to step through the code four times to see the variables updated. Stepping through the code like this can be tedious; this problem will be addressed with breakpoints in Step 2. But for now, by setting up our watchers, we are ready to observe their values and find our problem.

      Step through the program by entering n twelve more times, observing the output. Your console will display this:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = 1564 1: i = 5 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      Recall that our orders array has five items, and i is now at position 5. But since i is used as the index of an array, there is no value at orders[5]; the last value of the orders array is at index 4. This means that orders[5] will have a value of undefined.

      Type n in the console and you’ll observe that the code in the loop is executed:

      Output

      break in badLoop.js:6 Watchers: 0: totalOrders = 1564 1: i = 5 4 5 for (let i = 0; i <= orders.length; i++) { > 6 totalOrders += orders[i]; 7 } 8

      Typing n once more shows the value of totalOrders after that iteration:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = NaN 1: i = 5 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      Through debugging and watching totalOrders and i, we can see that our loop is iterating six times instead of five. When i is 5, orders[5] is added to totalOrders. Since orders[5] is undefined, adding this to a number will yield NaN. The problem with our code therefore lies within our for loop’s condition. Instead of checking if i is less than or equal to the length of the orders array, we should only check that it’s less than the length.

      Let’s exit our debugger, make the changes and run the code again. In the debug prompt, type the exit command and press ENTER:

      Now that you’ve exited the debugger, open badLoop.js in your text editor:

      Change the for loop’s condition:

      debugger/badLoop.js

      ...
      for (let i = 0; i < orders.length; i++) {
      ...
      

      Save and exit nano. Now let’s execute our script like this:

      When it’s complete, the correct result will be printed:

      Output

      1564

      In this section, we used the debugger’s watch command to find a bug in our code, fixed it, and watched it work as expected.

      Now that we have some experience with the basic use of the debugger to watch variables, let’s look at how we can use breakpoints so that we can debug without stepping through all the lines of code from the start of the program.

      Step 2 — Using Breakpoints With the Node.js Debugger

      It’s common for Node.js projects to consist of many interconnected modules. Debugging each module line-by-line would be time consuming, especially as an app scales in complexity. To solve this problem, breakpoints allow us to jump to a line of code where we’d like to pause execution and inspect the program.

      When debugging in Node.js, we add a breakpoint by adding the debugger keyword directly to our code. We can then go from one breakpoint to the next by pressing c in the debugger console instead of n. At each breakpoint, we can set up watchers for expressions of interest.

      Let’s see this with an example. In this step, we’ll set up a program that reads a list of sentences and determines the most common word used throughout all the text. Our sample code will return the first word with the highest number of occurrences.

      For this exercise, we will create three files. The first file, sentences.txt, will contain the raw data that our program will process. We’ll add the beginning text from Encyclopaedia Britannica’s article on the Whale Shark as sample data, with the punctuation removed.

      Open the file in your text editor:

      Next, enter the following code:

      debugger/sentences.txt

      Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fish
      Whale sharks are found in marine environments worldwide but mainly in tropical oceans
      They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharks
      The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feet
      Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in length
      The body coloration is distinctive
      Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the body
      

      Save and exit the file.

      Now let’s add our code to textHelper.js. This module will contain some handy functions we’ll use to process the text file, making it easier to determine the most popular word. Open textHelper.js in your text editor:

      We’ll create three functions to process the data in sentences.txt. The first will be to read the file. Type the following into textHelper.js:

      debugger/textHelper.js

      const fs = require('fs');
      
      const readFile = () => {
        let data = fs.readFileSync('sentences.txt');
        let sentences = data.toString();
        return sentences;
      };
      

      First, we import the fs Node.js library so we can read files. We then create the readFile() function that uses readFileSync() to load the data from sentences.txt as a Buffer object and the toString() method to return it as a string.

      The next function we’ll add processes a string of text and flattens it to an array with its words. Add the following code into the editor:

      textHelper.js

      ...
      
      const getWords = (text) => {
        let allSentences = text.split('n');
        let flatSentence = allSentences.join(' ');
        let words = flatSentence.split(' ');
        words = words.map((word) => word.trim().toLowerCase());
        return words;
      };
      

      In this code, we are using the methods split(), join(), and map() to manipulate the string into an array of individual words. The function also lowercases each word to make counting easier.

      The last function needed returns the counts of different words in a string array. Add the last function like this:

      debugger/textHelper.js

      ...
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          if (word in map) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
        });
      
        return map;
      };
      

      Here we create a JavaScript object called map that has the words as its keys and their counts as the values. We loop through the array, adding one to a count of each word when it’s the current element of the loop. Let’s complete this module by exporting these functions, making them available to other modules:

      debugger/textHelper.js

      ...
      
      module.exports = { readFile, getWords, countWords };
      

      Save and exit.

      Our third and final file we’ll use for this exercise will use the textHelper.js module to find the most popular word in our text. Open index.js with your text editor:

      We begin our code by importing the textHelpers.js module:

      debugger/index.js

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

      Continue by creating a new array containing stop words:

      debugger/index.js

      ...
      
      const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];
      

      Stop words are commonly used words in a language that we filter out before processing a text. We can use this to find more meaningful data than the result that the most popular word in English text is the or a.

      Continue by using the textHelper.js module functions to get a JavaScript object with words and their counts:

      debugger/index.js

      ...
      
      let sentences = textHelper.readFile();
      let words = textHelper.getWords(sentences);
      let wordCounts = textHelper.countWords(words);
      

      We can then complete this module by determining the words with the highest frequency. To do this, we’ll loop through each key of the object with the word counts and compare its count to the previously stored maximum. If the word’s count is higher, it becomes the new maximum.

      Add the following lines of code to compute the most popular word:

      debugger/index.js

      ...
      
      let max = -Infinity;
      let mostPopular="";
      
      Object.entries(wordCounts).forEach(([word, count]) => {
        if (stopwords.indexOf(word) === -1) {
          if (count > max) {
            max = count;
            mostPopular = word;
          }
        }
      });
      
      console.log(`The most popular word in the text is "${mostPopular}" with ${max} occurrences`);
      

      In this code, we are using Object.entries() to transform the key-value pairs in the wordCounts object into individual arrays, all of which are nested within a larger array. We then use the forEach() method and some conditional statements to test the count of each word and store the highest number.

      Save and exit the file.

      Let’s now run this file to see it in action. In your terminal enter this command:

      You will see the following output:

      Output

      The most popular word in the text is "whale" with 1 occurrences

      From reading the text, we can see that the answer is incorrect. A quick search in sentences.txt would highlight that the word whale appears more than once.

      We have quite a few functions that can cause this error: We may not be reading the entire file, or we may not be processing the text into the array and JavaScript object correctly. Our algorithm for finding the maximum word could also be incorrect. The best way to figure out what’s wrong is to use the debugger.

      Even without a large codebase, we don’t want to spend time stepping through each line of code to observe when things change. Instead, we can use breakpoints to go to those key moments before the function returns and observe the output.

      Let’s add breakpoints in each function in the textHelper.js module. To do so, we need to add the keyword debugger into our code.

      Open the textHelper.js file in the text editor. We’ll be using nano once again:

      First, we’ll add the breakpoint to the readFile() function like this:

      debugger/textHelper.js

      ...
      
      const readFile = () => {
        let data = fs.readFileSync('sentences.txt');
        let sentences = data.toString();
        debugger;
        return sentences;
      };
      
      ...
      

      Next, we’ll add another breakpoint to the getWords() function:

      debugger/textHelper.js

      ...
      
      const getWords = (text) => {
        let allSentences = text.split('n');
        let flatSentence = allSentences.join(' ');
        let words = flatSentence.split(' ');
        words = words.map((word) => word.trim().toLowerCase());
        debugger;
        return words;
      };
      
      ...
      

      Finally, we’ll add a breakpoint to the countWords() function:

      debugger/textHelper.js

      ...
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          if (word in map) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
        });
      
        debugger;
        return map;
      };
      
      ...
      

      Save and exit textHelper.js.

      Let’s begin the debugging process. Although the breakpoints are in textHelpers.js, we are debugging the main point of entry of our application: index.js. Start a debugging session by entering the following command in your shell:

      After entering the command, we’ll be greeted with this output:

      Output

      < Debugger listening on ws://127.0.0.1:9229/b2d3ce0e-3a64-4836-bdbf-84b6083d6d30 < For help, see: https://nodejs.org/en/docs/inspector < Debugger attached. Break on start in index.js:1 > 1 const textHelper = require('./textHelper'); 2 3 const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];

      This time, enter c into the interactive debugger. As a reminder, c is short for continue. This jumps the debugger to the next breakpoint in the code. After pressing c and typing ENTER, you will see this in your console:

      Output

      break in textHelper.js:6 4 let data = fs.readFileSync('sentences.txt'); 5 let sentences = data.toString(); > 6 debugger; 7 return sentences; 8 };

      We’ve now saved some debugging time by going directly to our breakpoint.

      In this function, we want to be sure that all the text in the file is being returned. Add a watcher for the sentences variable so we can see what’s being returned:

      Press n to move to the next line of code so we can observe what’s in sentences. You will see the following output:

      Output

      break in textHelper.js:7 Watchers: 0: sentences="Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fishn" + 'Whale sharks are found in marine environments worldwide but mainly in tropical oceansn' + 'They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharksn' + 'The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feetn' + 'Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in lengthn' + 'The body coloration is distinctiven' + 'Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the bodyn' 5 let sentences = data.toString(); 6 debugger; > 7 return sentences; 8 }; 9

      It seems that we aren’t having any problems reading the file; the problem must lie elsewhere in our code. Let’s move to the next breakpoint by pressing c once again. When you do, you’ll see this output:

      Output

      break in textHelper.js:15 Watchers: 0: sentences = ReferenceError: sentences is not defined at eval (eval at getWords (your_file_path/debugger/textHelper.js:15:3), <anonymous>:1:1) at Object.getWords (your_file_path/debugger/textHelper.js:15:3) at Object.<anonymous> (your_file_path/debugger/index.js:7:24) at Module._compile (internal/modules/cjs/loader.js:1125:14) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10) at Module.load (internal/modules/cjs/loader.js:983:32) at Function.Module._load (internal/modules/cjs/loader.js:891:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12) at internal/main/run_main_module.js:17:47 13 let words = flatSentence.split(' '); 14 words = words.map((word) => word.trim().toLowerCase()); >15 debugger; 16 return words; 17 };

      We get this error message because we set up a watcher for the sentences variable, but that variable does not exist in our current function scope. A watcher lasts for the entire debugging session, so as long as we keep watching sentences where it’s not defined, we’ll continue to see this error.

      We can stop watching variables with the unwatch() command. Let’s unwatch sentences so we no longer have to see this error message every time the debugger prints its output. In the interactive prompt, enter this command:

      The debugger does not output anything when you unwatch a variable.

      Back in the getWords() function, we want to be sure that we are returning a list of words that are taken from the text we loaded earlier. Let’s watch the value of the words variable:

      Then enter n to go to the next line of the debugger, so we can see what’s being stored in words. The debugger will show the following:

      Output

      break in textHelper.js:16 Watchers: 0: words = [ 'whale', 'shark', 'rhincodon', 'typus', 'gigantic', 'but', 'harmless', ... 'metres', '39', 'feet', 'in', 'length', '', 'the', 'body', 'coloration', ... ] 14 words = words.map((word) => word.trim().toLowerCase()); 15 debugger; >16 return words; 17 }; 18

      The debugger does not print out the entire array as it’s quite long and would make the output harder to read. However, the output meets our expectations of what should be stored: the text from sentences split into lowercase strings. It seems that getWords() is functioning correctly.

      Let’s move on to observe the countWords() function. First, unwatch the words array so we don’t cause any debugger errors when we are at the next breakpoint. In the command prompt, enter this:

      Next, enter c in the prompt. At our last breakpoint, we will see this in the shell:

      Output

      break in textHelper.js:29 27 }); 28 >29 debugger; 30 return map; 31 };

      In this function, we want to be sure that the map variable correctly contains the count of each word from our sentences. First, let’s tell the debugger to watch the map variable:

      Press n to move to the next line. The debugger will then display this:

      Output

      break in textHelper.js:30 Watchers: 0: map = { 12: NaN, 14: NaN, 15: NaN, 18: NaN, 39: NaN, 59: NaN, whale: 1, shark: 1, rhincodon: 1, typus: NaN, gigantic: NaN, ... } 28 29 debugger; >30 return map; 31 }; 32

      That does not look correct. It seems as though the method for counting words is producing erroneous results. We don’t know why those values are being entered, so our next step is to debug what’s happening in the loop used on the words array. To do this, we need to make some changes to where we place our breakpoint.

      First, exit the debug console:

      Open textHelper.js in your text editor so we can edit the breakpoints:

      First, knowing that readFile() and getWords() are working, we will remove their breakpoints. We then want to remove the breakpoint in countWords() from the end of the function, and add two new breakpoints to the beginning and end of the forEach() block.

      Edit textHelper.js so it looks like this:

      debugger/textHelper.js

      ...
      
      const readFile = () => {
        let data = fs.readFileSync('sentences.txt');
        let sentences = data.toString();
        return sentences;
      };
      
      const getWords = (text) => {
        let allSentences = text.split('n');
        let flatSentence = allSentences.join(' ');
        let words = flatSentence.split(' ');
        words = words.map((word) => word.trim().toLowerCase());
        return words;
      };
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          debugger;
          if (word in map) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
          debugger;
        });
      
        return map;
      };
      
      ...
      

      Save and exit nano with CTRL+X.

      Let’s start the debugger again with this command:

      To get insight into what’s happening, we want to debug a few things in the loop. First, let’s set up a watcher for word, the argument used in the forEach() loop containing the string that the loop is currently looking at. In the debug prompt, enter this:

      So far, we have only watched variables. But watches are not limited to variables. We can watch any valid JavaScript expression that’s used in our code.

      In practical terms, we can add a watcher for the condition word in map, which determines how we count numbers. In the debug prompt, create this watcher:

      Let’s also add a watcher for the value that’s being modified in the map variable:

      Watchers can even be expressions that aren’t used in our code but could be evaluated with the code we have. Let’s see how this works by adding a watcher for the length of the word variable:

      Now that we’ve set up all our watchers, let’s enter c into the debugger prompt so we can see how the first element in the loop of countWords() is evaluated. The debugger will print this output:

      Output

      break in textHelper.js:20 Watchers: 0: word = 'whale' 1: word in map = false 2: map[word] = undefined 3: word.length = 5 18 let map = {}; 19 words.forEach((word) => { >20 debugger; 21 if (word in map) { 22 map[word] = 1;

      The first word in the loop is whale. At this point, the map object has no key with whale as its empty. Following from that, when looking up whale in map, we get undefined. Lastly, the length of whale is 5. That does not help us debug the problem, but it does validate that we can watch any expression that could be evaluated with the code while debugging.

      Press c once more to see what’s changed by the end of the loop. The debugger will show this:

      Output

      break in textHelper.js:26 Watchers: 0: word = 'whale' 1: word in map = true 2: map[word] = NaN 3: word.length = 5 24 map[word] += 1; 25 } >26 debugger; 27 }); 28

      At the end of the loop, word in map is now true as the map variable contains a whale key. The value of map for the whale key is NaN, which highlights our problem. The if statement in countWords() is meant to set a word’s count to one if it’s new, and add one if it existed already.

      The culprit is the if statement’s condition. We should set map[word] to 1 if the word is not found in map. Right now, we are adding one if word is found. At the beginning of the loop, map["whale"] is undefined. In JavaScript, undefined + 1 evaluates to NaN—not a number.

      The fix for this would be to change the condition of the if statement from (word in map) to (!(word in map)), using the ! operator to test if word is not in map. Let’s make that change in the countWords() function to see what happens.

      First, exit the debugger:

      Now open the textHelper.js file with your text editor:

      Modify the countWords() function as follows:

      debugger/textHelper.js

      ...
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          if (!(word in map)) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
        });
      
        return map;
      };
      
      ...
      

      Save and close the editor.

      Now let’s run this file without a debugger. In the terminal, enter this:

      The script will output the following sentence:

      Output

      The most popular word in the text is "whale" with 3 occurrences

      This output seems a lot more likely than what we received before. With the debugger, we figured out which function caused the problem and which functions did not.

      We’ve debugged two different Node.js programs with the built-in CLI debugger. We are now able to set up breakpoints with the debugger keyword and create various watchers to observe changes in internal state. But sometimes, code can be more effectively debugged from a GUI application.

      In the next section, we’ll use the debugger in Google Chrome’s DevTools. We’ll start the debugger in Node.js, navigate to a dedicated debugging page in Google Chrome, and set up breakpoints and watchers using the GUI.

      Chrome DevTools is a popular choice for debugging Node.js in a web browser. As Node.js uses the same V8 JavaScript engine that Chrome uses, the debugging experience is more integrated than with other debuggers.

      For this exercise, we’ll create a new Node.js application that runs an HTTP server and returns a JSON response. We’ll then use the debugger to set up breakpoints and gain deeper insight into what response is being generated for the request.

      Let’s create a new file called server.js that will store our server code. Open the file in the text editor:

      This application will return a JSON with a Hello World greeting. It will have an array of messages in different languages. When a request is received, it will randomly pick a greeting and return it in a JSON body.

      This application will run on our localhost server on port :8000. If you’d like to learn more about creating HTTP servers with Node.js, read our guide on How To Create a Web Server in Node.js with the HTTP Module.

      Type the following code into the text editor:

      debugger/server.js

      const http = require("http");
      
      const host="localhost";
      const port = 8000;
      
      const greetings = ["Hello world", "Hola mundo", "Bonjour le monde", "Hallo Welt", "Salve mundi"];
      
      const getGreeting = function () {
        let greeting = greetings[Math.floor(Math.random() * greetings.length)];
        return greeting
      }
      

      We begin by importing the http module, which is needed to create an HTTP server. We then set up the host and port variables that we will use later to run the server. The greetings array contains all the possible greetings our server can return. The getGreeting() function randomly selects a greeting and returns it.

      Let’s add the request listener that processes HTTP requests and add code to run our server. Continue editing the Node.js module by typing the following:

      debugger/server.js

      ...
      
      const requestListener = function (req, res) {
        let message = getGreeting();
        res.setHeader("Content-Type", "application/json");
        res.writeHead(200);
        res.end(`{"message": "${message}"}`);
      };
      
      const server = http.createServer(requestListener);
      server.listen(port, host, () => {
        console.log(`Server is running on http://${host}:${port}`);
      });
      

      Our server is now ready for use, so let’s set up the Chrome debugger.

      We can start the Chrome debugger with the following command:

      Note: Keep in mind the difference between the CLI debugger and the Chrome debugger commands. When using the CLI you use inspect. When using Chrome you use --inspect.

      After starting the debugger, you’ll find the following output:

      Output

      Debugger listening on ws://127.0.0.1:9229/996cfbaf-78ca-4ebd-9fd5-893888efe8b3 For help, see: https://nodejs.org/en/docs/inspector Server is running on http://localhost:8000

      Now open Google Chrome or Chromium and enter chrome://inspect in the address bar. Microsoft Edge also uses the V8 JavaScript engine, and can thus use the same debugger. If you are using Microsoft Edge, navigate to edge://inspect.

      After navigating to the URL, you will see the following page:

      Screenshot of Google Chome's "inspect" page

      Under the Devices header, click the Open dedicated DevTools for Node button. A new window will pop up:

      Screenshot of debug window

      We’re now able to debug our Node.js code with Chrome. Navigate to the Sources tab if not already there. On the left-hand side, expand the file tree and select server.js:

      Screenshot of debugger window's Sources tab

      Let’s add a breakpoint to our code. We want to stop when the server has selected a greeting and is about to return it. Click on the line number 10 in the debug console. A red dot will appear next to the number and the right-hand panel will indicate a new breakpoint was added:

      Screenshot of adding a breakpoint in the Chrome debugger

      Now let’s add a watch expression. On the right panel, click the arrow next to the Watch header to open the watch words list, then click +. Enter greeting and press ENTER so that we can observe its value when processing a request.

      Next, let’s debug our code. Open a new browser window and navigate to http://localhost:8000—the address the Node.js server is running on. When pressing ENTER, we will not immediately get a response. Instead, the debug window will pop up once again. If it does not immediately come into focus, navigate to the debug window to see this:

      Screenshot of the program's execution paused in Chrome

      The debugger pauses the server’s response where we set our breakpoint. The variables that we watch are updated in the right panel and also in the line of code that created it.

      Let’s complete the response’s execution by pressing the continue button at the right panel, right above Paused on breakpoint. When the response is complete, you will see a successful JSON response in the browser window used to speak with the Node.js server:

      {"message": "Hello world"}
      

      In this way, Chrome DevTools does not require changes to the code to add breakpoints. If you prefer to use graphical applications over the command line to debug, the Chrome DevTools are more suitable for you.

      Conclusion

      In this article, we debugged sample Node.js applications by setting up watchers to observe the state of our application, and then by adding breakpoints to allow us to pause execution at various points in our program’s execution. We accomplished this using both the built-in CLI debugger and Google Chrome’s DevTools.

      Many Node.js developers log to the console to debug their code. While this is useful, it’s not as flexible as being able to pause execution and watch various state changes. Because of this, using debugging tools is often more efficient, and will save time over the course of developing a project.

      To learn more about these debugging tools, you can read the Node.js documentation or the Chrome DevTools documentation. If you’d like to continue learning Node.js, you can return to the How To Code in Node.js series, or browse programming projects and setups on our Node topic page.



      Source link