NodeJS Run and Debug in VS Code

Posted by Roman Seidelsohn

Background

We, the frontend developers of Springer Nature’s Institutional Customer Experience (ICE) team, often use the free coding editor Visual Studio Code (VSC) for development.

One of our projects - the Librarian Portal - uses a Node.js web server engine called Express. The express server communicates with the back end to fetch information - which it then uses to compile HTML. For local development, we run an Express server to emulate the the real backend server, allowing us to test our code with the data required to render the pages.

VSC provides a feature called “Run and Debug” which allows you to conveniently run and debug the server-side code. In order to use this, one needs to set up a configuration file called launch.json. This file can be set up to not only run one program, but also several programs at the same time, allowing debugging of all at once.

VSC's "Run and Debug" button

So I experimented a bit until I had a launch.conf file that would start up both our frontend and backend mock servers at the click of a button.

This article describes how to achieve this for a Node.js project that is run locally invoking a script command defined in the project’s package.json file.

The Premise

I wanted to turn the start script of our package.json file into a launch.json file for VSC’s Run and Debug feature.

The Solution

Analyse the package.json file

The first step is to decide which programs you want to run. The second step is to find out what settings you want the programs to run with.

This can be done by looking at the configuration of the package.json’s script you use to start your application. In our case this is the start:mock:app script which we run from the command line with the command npm run start:mock:app. The following code snippet is an abbreviated version of the scripts section of our package.json file. It contains the entry for the script that starts our frontend and (mocked) backend servers followed by other scripts that are used by the first one:

  "scripts": {
    "start:mock:app": "concurrently \"npm run start:mock:be\" \"npm run start:mock:fe\"",
    "start:mock:be": "DEBUG=libportal:* NODE_ENV=development nodemon --config nodemon-mock-api.json mock-backend-api/mock-server.js",
    "start:mock:fe": "concurrently \"npm run build:dev:watch\" \"DEBUG=libportal:* NODE_ENV=development MOCK=true nodemon index.js\"",
    "build:dev:watch": "NODE_ENV=development gulp watch",
    //
  },

As one can see, we use concurrently for running the following scripts, in parallel:

  • npm run start:mock:be sets two environment variables. It then runs nodemon with an option and the JS file to start. This can be translated into entries for a configuration in our launch.json file.
  • npm run start:mock:fe again uses concurrently to run another script and a program at the same time. The script is a gulp watch task which takes care of updating the compiled code after source files have been changed. I did not think this script was important for our setup (but I may add it at a later time). I skipped it and just took the call to nodemon, which is constructed in the same way as the command above: It sets two environment variables and calls nodemon with the name of the JS file to start.

Create a draft launch.conf file

The Run and Debug tool of VSC uses a configuration file, called launch.json, which by default will be located under ./vscode/launch.json inside your project folder. To create this file for the first time, open your project in VSC and click on the Run and Debug button in the (per default) left Activity bar:

VSC's Run and Debug button

This will switch to the RUN AND DEBUG view of the Primary Side Bar. If you haven’t created a launch.json file before in that project, you will see this “welcome screen” which offers a create a launch.json file link:

The welcome screen of VSC's Run and Debug view

If you click on this link, VSC will ask you for a workspace folder to create a launch.json file in or else whether the config should be added to the workspace config file:

VSC Dialog for selecting a workspace folder or adding the configuration to the workspace file itself

I would highly recommend to create in as a file within your respository, so that you can easily share it with the other developers or computers working on that project. After choosing your option VSC will ask you to select a debugger - depending on your project. In our case, the dialog looks like this:

VSC dialog for selecting one of the available debuggers to use in the configuration for Run and Debug

As we are working with Node.js here, we select Node.js.

Then, a launch.json file will automatically be created in a subfolder called .vscode (given that you chose the recommended option to create that file in your workspace folder) and opened in the editor. It will look something like this:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${file}"
        }
    ]
}

This file has now to be filled in with the details we gathered from our package.json’s script configuration section.

Fill in the needed attributes and values

With the support of “IntelliSense”, VSC will offer you a long list of valid attributes to use in this launch.json file:

Valid configuration options suggested by IntelliSense

You can use these for experimenting with additional setups for your needs.

First, note that the draft file says configurations with an s in the end. That means that you can add more than one configuration - one configuration sets up one program to run and debug. You can then later combine several of those into a so called compound that can be selected to be run with the Run and Debug tool.

So let’s start with filling in the details for the first program to run; our backend mock server. We already figured out that we need the following details:

  • The environment variable settings DEBUG=libportal:* and NODE_ENV=development
  • The program nodemon that should be started
  • The option --config nodemon-mock-api.json for nodemon
  • The value for the JS file to be started by nodemon, mock-backend-api/mock-server.js

We set the name we want to use for our configuration as the value of the name configuration attribute. The name and path of the file to execute is set as the value of the program entry. The environment variables you want to use can be set in an additional configuration attribute called env and the program arguments go into an additional configuration attribute called args:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "runtimeVersion": "16.13.0",
            "request": "launch",
            "name": "Mock-Server",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/libportal-frontend/node_modules/.bin/nodemon",
            "args": [
                "--config", "${workspaceFolder}/libportal-frontend/nodemon-mock-api.json",
                "${workspaceFolder}/libportal-frontend/mock-backend-api/mock-server.js"
            ],
            "env": {
                "DEBUG": "libportal:*",
                "NODE_ENV": "development"
            },
            "cwd": "${workspaceFolder}/libportal-frontend"
        }
    ]
}

You can see that we make use of a predefined variable called workspaceFolder, so that the path to the program (nodemon) can be found on any other computer that has the same repository checked out. Also, we added a runtimeVersion that defined the Node.js version to use, as VSC will not automatically respect a .nvmrc file in your project folder. Another helpful setting is the cwd entry "cwd": "${workspaceFolder}/libportal-frontend". With this setting, you don’t need to define the full paths and instead can use relative paths in your other attributes. For the sake of clarity, you should probably not use both like in the example above, but instead choose one setting or the other. We’ll ditch the setting of the absolute paths in the other options from now on - the above is just an example for illustrating both possibilities.

Using the same approach as for the mock server, we now add the second configuration for our frontend server, giving us the following state:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "runtimeVersion": "16.13.0",
            "request": "launch",
            "name": "Mock-Server",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "./node_modules/.bin/nodemon",
            "args": [
                "--config", "./nodemon-mock-api.json",
                "./mock-backend-api/mock-server.js"
            ],
            "env": {
                "DEBUG": "libportal:*",
                "NODE_ENV": "development"
            },
            "cwd": "${workspaceFolder}/libportal-frontend"
        },
        {
            "type": "node",
            "runtimeVersion": "16.13.0",
            "request": "launch",
            "name": "Frontend",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "./node_modules/.bin/nodemon",
            "args": [
                "./index.js"
            ],
            "env": {
                "DEBUG": "libportal:*",
                "NODE_ENV": "development",
                "MOCK": "true"
            },
            "cwd": "${workspaceFolder}/libportal-frontend"
        }
    ]
}

With this launch.json file set up we already can run and debug either of these configurations, but not both at the same time. In order to achieve this, we need an additional configuration attribute, namely the compounds:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "runtimeVersion": "16.13.0",
            "request": "launch",
            "name": "Mock-Server",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "./node_modules/.bin/nodemon",
            "args": [
                "--config", "./nodemon-mock-api.json",
                "./mock-backend-api/mock-server.js"
            ],
            "env": {
                "DEBUG": "libportal:*",
                "NODE_ENV": "development"
            },
            "cwd": "${workspaceFolder}/libportal-frontend"
        },
        {
            "type": "node",
            "runtimeVersion": "16.13.0",
            "request": "launch",
            "name": "Frontend",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "./node_modules/.bin/nodemon",
            "args": [
                "./index.js"
            ],
            "env": {
                "DEBUG": "libportal:*",
                "NODE_ENV": "development",
                "MOCK": "true"
            },
            "cwd": "${workspaceFolder}/libportal-frontend"
        }
    ],
    "compounds": [
        {
            "name": "Mock-Server/Frontend",
            "configurations": ["Mock-Server", "Frontend"],
            "stopAll": true
        }
    ]
}

There, in the array defining the configurations, we list all previously defined configurations we want to run and debug at the same time by using their configuration’s name values. Setting the stopAll attribute to true ensures that when we manually stop either of the running configurations, the other configurations are stopped automatically as well. This is quite useful in our case, you may find useful in your project as well.

With this all set up, you can now choose to run either of the configurations for themselves or one of the defined compounds:

VSC dialog for choosing one of the previously defined configuration names that should be run

In the debug window, you then can switch between all of the configurations that are currently running:

VSC dialog for choosing one of the available debug window outputs to be displayed

Outlook

I believe there is also an easier way to set up your launch.json file. If I remember correctly, I saw somewhere an option to just point to an entry in the scripts section of an existing package.json file. By the time I spotted this, I was already done with my setup, so I did not investigate further.

… feel free to write your own blog article if you figure that out!

Happy debugging.


Find this post useful, or want to discuss some of the topics?

About The Authors