How to debug a Node.js server written in TypeScript running in Docker
Simon Abbt
February 1, 2019
TypeScript is cool, Node.js is great and Docker is also quite nice. Therefore, writing your server with Node.js in TypeScript and letting it run in Docker is awesome! Not so awesome is getting a nice debug experience running with this setup.
While TypeScript and Docker itself can improve the development experience by a fair margin, they also require more setup to get everything running.
If you now combine these technologies, you have two major layers between your source code and the code that will be executed: The compile step and the different file location inside your Docker container.
How do you debug your code in a constellation like this?
All hail the mighty console.log, right?
While it is an option which will work without any config and certainly can offer some insights, a setup where you can set breakpoints and step through and inspect your code is necessary at some point. So no **console.log**s, at least not for everything. But how do you get working breakpoints with all these abstractions?
I’ll show you a setup which we are currently using in a project and point out the main points leading to a working setup. The editor we’ll configure to debug the code is VS Code. Besides VS Code, TypeScript, Node.js and Docker, we’ll use Gulp and Nodemon to achieve a nice workflow and debug experience.
A high-level flow looks like this:
- You write some TypeScript code
- Gulp detects file changes and triggers a TypeScript compilation
- The compiled JavaScript is provided to the Docker container through a mounted volume
- A Nodemon process in the container detects file changes and restarts the Node.js server inside the container. It also exposes a debug port.
The folder structure which all of the following example configs are based on looks like this:
The configurations files for the described setup
docker-compose.yml
Dockerfile
gulpfile.js
tsconfig.json
In your package.json, you should add a debug script:
Note: You have to use —-legacy-watch to detect file changes inside a Docker container.
Additionally, we need a VS Code launch configuration. Luckily, there is already a default one for docker-node we can build on:
launch.json
With docker-compose up you should get your server running. The configured setup should give you a nice workflow which automatically compiles and restarts your server when you change the source code. But if you now set some breakpoints in VS Code, your breakpoints will look like this:
Why won’t senpai notice me ☹️— your breakpoint
So, what is necessary to fix that? There are two main parts we have to adjust to get our breakpoints in VS Code working.
- The debug launch config in VS Code
- A possibly necessary source map rewrite depending on your source, dist and container workspace folder locations
A working VS Code config for our structure and settings
The launch.json configuration file.
Note: The ${workspaceFolder} is the project folder that has been opened with VS Code. The localRoot\, ***remoteRoot\*** and ***outFiles\*** paths have to be adjusted to resemble your project structure and container settings.
In our example, we also need to rewrite the source maps because the TypeScript files aren’t located next to the compiled output files. To map them back to the original source, we need to adjust the path and go up two folders.
Changes in gulpfile.js
Debugging the debugger 😮
Setting "trace": "verbose" in your launch.json can help solving problems with the debugger. This will log all kind of config data in the debug console and can help you find some wrongly set paths. The sad thing, though: it only gives you all the configured paths if everything is working correctly.
E.g. in a working version the output would look like this:
- The remote path in the container is correct and maps to a valid path in the dist folder on my local machine.
- The path from the dist folder on my machine is mapped to the correct TypeScript files.
If all paths fit, you can just set breakpoints in your TypeScript code and VS Code will trigger them: Awesome! 🎉 🎉 🎉
But if they aren’t set up correctly, you’ll only receive an output like this:
That’s a lot of output 😩…
I’ve highlighted the important parts for you:
- setBreakpoints({"source": {"name": "File.ts", "path": "/projects/ts-node-in-docker/app/server/src/File.ts”
- "message": "Breakpoint ignored because generated code not found (source map problem?)."
That would, for example, show us that the source map paths in the compiled code are wrong — this is what we’ve already fixed with the Gulp source map rewrite.
Only if the Source Map mappings between the local compiled files and your source files match together, the debugger will try to map the compiled local files to the ones running in the Docker container.
So, fix the source map setup on your local machine first. When it is correct and you can set breakpoints, you can adjust the settings to map the compiled files to the ones in the container.
To summarize:
- Use "trace": "verbose" to understand problems with your breakpoints and your launch.json
- In your launch.json:
- "cwd" = your VS Code project directory
- "localRoot" = where your TypeScript source files are located
- "remoteRoot" = workspace folder as configured in your Dockerfile
- "outFiles" = glob pattern where the compiled JavaScript files are written to on your local machine
- If necessary, rewrite source paths so that VS Code can find the source files for the compiled ones
- Open port 9222 in Docker
- Start node server with --inspect=0.0.0.0:9222
JavaScript
TypeScript
Docker
Nodejs
Debugging
Read also
Francesca, Ricarda, 11/21/2024
Top 10 Mistakes to Avoid When Building a Digital Product
MVP development
UX/UI design
product vision
agile process
user engagement
product development
Leonhard, 10/22/2024
Strategies to Quickly Explore a New Codebase
Web App Development
Consulting
Audit
Leonhard, 07/15/2024
User Input Considered Harmful
TypeScript
Web App Development
Best Practices
Full-Stack
Validation