DEV Community

loading...
Cover image for Node.js with TypeScript, Debug inside VSCode and Nodemon

Node.js with TypeScript, Debug inside VSCode and Nodemon

oieduardorabelo profile image Eduardo Rabelo Updated on ・3 min read

Setup Node.js and TypeScript can be straightforward. Our daily work sometimes involves a lot of debugging and we also need to reload our server.

Let's walk through how we can setup Node.js with TypeScript + Nodemon + Debug inside VSCode (as an extra, you can debug it inside Chrome DevTools).

Less talk, more code! 🤓

📦 Dependencies and Configurations

$ mkdir devto-node-typescript
$ cd $_

Adding our dependencies:

$ yarn init -y
$ yarn add @types/express@^4.16.1 express@^4.16.4 nodemon@^1.18.10 ts-node@^8.0.3 typescript@^3.3.4000

Here is our dependencies list:

  • @types/express@^4.16.1
  • express@^4.16.4
  • nodemon@^1.18.10
  • ts-node@^8.0.3
  • typescript@^3.3.4000

Now, let's create our nodemon.json file:

$ touch nodemon.json
$ vim $_

And paste the following:

{
  "restartable": "rs",
  "ignore": [".git", "node_modules/**/node_modules"],
  "verbose": true,
  "execMap": { // [A]
    "ts": "node --require ts-node/register"
  },
  "watch": ["src/"],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js,json,ts"
}
  • // [A]: Here we're saying to nodemon: "Hey nodemon, if we run "nodemon" with a ".ts" file, please, use this line to execute the file". Because of that, now we can do nodemon server.ts and it will use ts-node to compile our file.

To learn more about Nodemon config, you can check the sample in their repository

Now, let's create our tsconfig.json file:

$ touch tsconfig.json
$ vim $_

And use:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",

    "strict": true,

    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,

    "esModuleInterop": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
}

There's no much difference from the default config (e.g. when you run tsc --init)

We are almost there, let's create a npm script to start our future server:

// ...

"scripts": {
  "dev": "nodemon src/entry.ts"
}

// ...

Now, it's time to create our server.

💻 Writing our server and some routes

As we saw in our nodemon and npm scripts configuration, we need to create a folder called src and file called entry.ts:

$ mkdir src
$ cd $_
$ touch entry.ts
$ vim $_

Let's write the following:

import express from 'express';

const server = express();

server.use('/_healthcheck', (_req, res) => {
  res.status(200).json({ uptime: process.uptime() });
});

server.listen(4004, () => { console.log('Running at localhost:4004') })

Now we can run:

$ yarn dev

And see our Node.js with TypeScript + Nodemon server running:

Fantastic! 🎉

But wait... one important part is missing, how I can debug this thing? 😱

📊 Attaching Debug configuration to our project

Using nodemon we simply pass the --inspect flag to make our Node process debuggable. Let's create a new npm script for that:

// ...

"scripts": {
  // ...
  "dev:debug": "nodemon --inspect src/entry.ts"
}

// ...

If we run:

$ yarn dev:debug

We can see the debug option in our logs:

Great! Let's create our VSCode configuration:

$ mkdir .vscode
$ cd $_
$ touch launch.json
$ vim $_

And paste the following:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Node: Nodemon",
      "processId": "${command:PickProcess}",
      "restart": true,
      "protocol": "inspector"
    }
  ]
}

This configuration is based in one of Microsoft Recipes for VSCode.

Let's open our VSCode editor and do go the "Debug" panel:

With our yarn dev:debug running, let's press the green arrow shown in the screenshot above.

First, we need to select the process pointing to our server:

After we select it, we can see a floating debug panel and some UI changes:

Now you can add a breakpoint in your code and have fun inside your editor with server reload by nodemon:

Delightful experience! ❤️ 🎉 😎

🌐 Extra: It also works in Google Chrome DevTools

Yep. We can use the following steps:

  • Navigate to chrome://inspect

  • Click inspect inside Remote Target section pointing to your server and have fund debugging your server using Chrome DevTools:

NOTE: I tried to use "Open dedicated DevTools for Node" option, but it doesn't work well :( If you know how to make it work, share with us!

☕️ I want it all and more!

What about tests? Folder structure? There's a default project?

Say no more! For my personal use, I've a small non-opinionated default project in:

https://github.com/oieduardorabelo/node-typescript

You will find:

  • Node.js + TypeScript
  • Jest
  • Nodemon

Feel free to fork, contribute and use!

Happy coding! 🙌

Discussion (9)

pic
Editor guide
Collapse
sirluis profile image
Luis Henrique

Nice, thanks for sharing.
For anyone having problems with vscode not showing the node process, do this:

  • Specify a port to the debbuger process on package.json nodemon script: nodemon --inspect:9229 src/server.ts
  • Replace this line: `"processId": "${command:PickProcess}" with: "port": 9229 inside the launch.json file. Thats all.
Collapse
briancodes profile image
Brian • Edited

This is very handy to know, and I've used some of this to debug Angular Universal server side code.

In the GitHub project you linked, I notice you use webpack for production build. Is it necessary to use webpack when using TypeScript server side?

Collapse
oieduardorabelo profile image
Eduardo Rabelo Author

oh, thanks mate! didn't noticed it, it should be in a separated branch, not master.

it is not necessary, it is my personal approach to always bundle my server-side code, inspired by github.com/jaredpalmer/backpack

i'll update my master branch

Collapse
kspence profile image
Kevin

Exactly what I was looking for... Thank you Eduardo!

Collapse
mausconi profile image
nizar

Thanks for sharing Eduardo.

Collapse
dtx92 profile image
DTX-92

Awesome, thanks
But can you please explain, how do I add support for module aliases?

Collapse
oieduardorabelo profile image
Eduardo Rabelo Author • Edited

thanks for your comment,

absolutely, for this setup it would be a little bit tricky, because:

  • we are not using a bundler (e.g. webpack, rollup etc)
  • if you use a module alias, the output Node.js code (e.g. inside "dist/*.js"), will have the aliased path, since TypeScript does not replace the import path string
  • because of that, you will need to maintain some kind of module alias resolution in the prod version for Node.js only (e.g. you need to start your server with a module alias solution)
  • we also need to keep mappings for Jest, because Jest doesn't use TypeScript module resolution

let's put the production scenario above aside, and focus on "dev" only, we can do:

let's clone the repo:

git clone https://github.com/oieduardorabelo/node-typescript
cd node-typescript
npm install

and install tsconfig-paths

npm install tsconfig-paths

inside our tsconfig.json we can define baseUrl and paths:

{
  "compilerOptions": {
     ...
    "baseUrl": ".",
    "paths": {
      "#domains/*": ["src/domains/*"]
    }
  },
  ...
}

now we can use the alias #domains in any file, let's update server.ts:

...
import bookRoutes from '#domains/book/routes';
import reviewRoutes from '#domains/review/routes';

const server = express();
...

now, let's register the module alias in our nodemon.json:

{
  ...
  "execMap": {
    "ts": "node --require ts-node/register --require tsconfig-paths/register"
  },
  ...
}

now we can start the server with aliases:

npm run dev

and it should work as before, but with aliases!

now, for the Jest mappings, you need to keep the same mappings from tsconfig.json but in the Jest syntax/config, we need to use moduleNameMapper. I'll add the Jest config in package.json for this example:

{
  ...
    "moduleNameMapper": {
      "^#domains/(.*)": "<rootDir>/src/domains/$1"
    }
  ...
}

and now you can run your tests as before:

npm run test

so far, so good.

now, for the production scenario, I do not have a good straight answer, because it requires a considerable amount of work to adapt it per project.

but thinking about the process to make it work, we could use something like module-alias

and we would need:

  • build the project with npm run build (e.g. typescript => node.js in dist/)
  • have a script to generate a package.json with module-alias inside dist/
  • add the same mappings from tsconfig.json inside dist/package.json but using module-alias syntax
  • run cd dist/ && npm install to install module-alias for production
  • and start your production server with node --require module-alias/register entry.js

phew, what a setup! ahahah

i hope it helped! 👋

Collapse
pak11273 profile image
Isaac Pak

It would be nice if you could do an example with an async/await application on node and typescript.

Collapse
diass_le profile image
Leonardo Dias

Thank you, Eduardo!
This help me a lot.