Up until relatively recently, I had heard a lot of buzz about Node.js, but had not really made my hands dirty trying it out. However, recently, work required me to dabble in some code-bases for application written using Node.js and Typescript.
What is Node.js?
I had come to the impression that it is a Web Application Development framework using JavaScript on the server side as well. While this definition is the popular one, and is based on truth about what Node.js is often used for, this definition is wrong in a rather misleading way.
A better definition I found was (source) that Node.js is an environment that can run JavaScript outside of a browser - essentially making it a way to make desktop applications on JavaScript.
And since a Web Server is essentially a desktop application that binds to, listens on, and services requests on particular TCP Port, one can also write a Web Server using Node.js. It is, as I understand, only incidental that client side programming is also JavaScript - but it is convenient, since one doesn't need to learn another language.
TypeScript
I hate JavaScript. It just doesn't make sense. So I wanted to learn Node.js with TypeScript right from the start. There are a lot of Node.js tutuorials - everyone who spent fifteen minutes with it wants to write a blog post about it. But there doesn't seem to be that many good ones that detail starting with TypeScript.
Hence I decided to note down what I learned in poking around - primarily for my own future self as notes for when I want to come back and revisit what I had figured out. But hey, if it benefits others, even better. I can at least promise that this has at least a slightly better signal to noise ratio that much of what one finds on the internet :D
Visual Studio Code
In this particular case, the application code I had to poke around with had chosen to use Visual Studio Code. And it seems like a popular editor / development environment. So I wanted to figure out how to not just use it as a text editor, but to actually make the debugging features work for me with TypeScript Node.js applications.
References
- How to Setup a TypeScript + Node.js project: https://khalilstemmler.com/blogs/typescript/node-starter-project/ While this helps one figure out Node.js using TypeScript, I needed that to integrate with Visual Studio Code. There is also a lot of stuff here that becomes unnecessary when using Visual Studio Code.
- Node.js HTTP Server example: https://wanago.io/2019/03/25/node-js-typescript-7-creating-a-server-and-receiving-requests/ I didn't want to just make another Console.log("Hello World!") application. I wanted the TypeScript Node.js HTTP Server analogous to the JavaScript HTTP Server example most often shown in JavaScript Node.js tutorials. I did have to tweak the code listed here, since it had compile errors out of the box.
- This YouTube video was the first step towards debugging JavaScript Node.js in Visual Studio Code: https://www.youtube.com/watch?v=DsH-fizHkcY
- Combining that with this page https://fettblog.eu/typescript-node-visual-studio-code/ helped me take it all the way to debugging TypeScript in Visual Studio Code.
Prerequisites
- Install Node.js from: https://nodejs.org/en/download/
- I did not check the box for Tools for Native Modules: "Automatically install the necessary tools..."
- Install Visual Studio code from: https://code.visualstudio.com/download
- I used the server install, but I'm sure the user install will also work.
- I chose to have the "Open with Code" option on both files and directories
Creating an HTTP Server in TypeScript using Node.js
First, create a folder which you want to be your project folder, somewhere on your computer. I've named it "nodejs-ts2" but the specific name doesn't matter.
Right click on this folder, and open with Visual Studio code. From the global menu, select View > Terminal. This brings up a command prompt inside Visual Studio Code so that you can do all the commands necessary right from there. Make sure the current folder in the command prompt is your project folder.
At the terminal, do npm init -y. This initializes the node.js project by setting up a package.json file. The "-y" option is to skip the questionnaire node will otherwise throw at you. Once you have the package.json, go ahead and update it with any detail you'd like to provide such as license, author, description etc.
Next, install typescript dependency by typing npm install typescript --save-dev
at the command prompt. The --save-dev option, as opposed to the --save option, is because TypeScript is only required for development and not for running the application itself. (Node: --save-dev can be abbreviated with -D). Also install Ambient types for Node.js for typescript using the command npm install @types/node --save-dev
. These are TypeScript type declarations for JavaScript libraries used by Node.js. (Note: this can be consolidated into one command as npm install -D typescript @types/node).
Next, create a TS Config file. This file holds the compiler options for the typescript compiler to transpile TypeScript code into JavaScript. npx tsc --init --rootDir src --outDir build --esModuleInterop --resolveJsonModule --lib es6 --module commonjs --allowJs true --noImplicitAny true . We will not go into what each of these options mean; see documentation / references for that. Notice however, that we are calling our source folder "src", and our output folder "build". (Note: later down below, we will add the --sourceMap
option to this to make it be npx
tsc --init --rootDir src --outDir build --esModuleInterop
--resolveJsonModule --lib es6 --module commonjs --allowJs true
--noImplicitAny true --sourceMap true).
Now we'll clicnk on the main folder name on the left in Visual Studio Code, and add a folder named "src" under it using the context menu. Within this "src" folder, we will add a file named "index.ts". The content of this file should be as follows:
import { createServer, IncomingMessage, ServerResponse } from 'http';
const port = 8080;
const server = createServer((request: IncomingMessage, response: ServerResponse) => {
response.end('Hello world!');
});
try {
server.listen(port, () => {
console.log(`Started HTTP Server listening on port ${port}`);
});
}
catch(e){
console.log(`Error encountered. Details follow:`);
console.log(e);
}
This code is the TypeScript equivalent of the standard JavaScript Node.js HTTP Server example below:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World!');
}).listen(8080);
At this point, you can type npx tsc in the terminal to compile your src/index.ts file into build/index.js, and you can run the HTTP server by typing node .\build\index.js at the terminal. You can test this out by launching a browser and navigating to localhost:8080. Once you test, type CTRL+C at the terminal to stop running the node application.
Connecting to Visual Studio Code for debugging
To allow debugging in Visual Studio Code, first you need a TypeScript build task. Go to Terminal > Configure Tasks. Select the "tsc: build - tsconfig.json" task in the prompt that shows. This adds a tasks.json file to your project with a task of type "typescript" labeled "tsc: build - tsconfig.json". At this point, you can do Terminal > Run Task / Run Build Task, and see that all .ts files in the "src" folder are built to corresponding .js files in the "build" folder. Your tasks.json file looks like this:
{
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"problemMatcher": [
"$tsc"
],
"group": "build",
"label": "tsc: build - tsconfig.json"
}
]
}
Next, make sure that you have the index.ts file open in the main part of the editor. Then, on the left in Vistual Studio Code, go to the debugging icon. There, you will se a link to "create a launch.json file". Click this. It will prompt you to choose the environment. Select Node.js as the environment. This adds a launch.json file to your project. At this point you can try running the project, by clicking the green run icon, but it just gives you a lot of errors.
First off, the program it is trying to run is incorrect. It should point to our TS file:
"program": "${workspaceFolder}/src/index.ts"
Second, there is nothing forcing a build before debugging. So we should add a pre-launch task:
"preLaunchTask": "tsc: build - tsconfig.json"
Note that the "label" from the task in the tasks.json file is the value that goes for property "preLaunchTask".
Finally, the current value of outFiles is any js file in anywhere in the tree of our project folder. We can optionally fix it to only look within the build folder.
"outFiles": ["${workspaceFolder}/build/**/*.js"],
At this point,your launch.json file looks like this:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Run HTTP Server",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/src/index.ts",
"outFiles": [
"${workspaceFolder}/build/**/*.js"
],
"preLaunchTask": "tsc: build - tsconfig.json"
}
]
}
If you now try to run, it should have worked, but instead, you get a bunch of errors in your debug console. This is because, right now, our tsconfig.json doesn't specify "sourceMap": true. Uncomment that line in the tsconfig.json file, and everything starts working. You can put a breakpoint on the Console.log() line that writes that an HTTP Server was started, and you will see that we break at that line. When we step over it, this message is written on the console.
An alternate way you can do your tasks.json file is as an "npm: build" task as shown below
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"label": "npm: build",
"detail": "rimraf ./build && tsc"
}
]
}
In this case, the "detail" provides the build commands. Update your "preLaunchTask" in your launch.json file appropriately.
Other ways to run
If you follow along with one of the references I had listed, you could also add scripts in the package.json file to run from the terminal.
First, we will install rimraff using the command npm install --save-dev rimraf
at the terminal.
Next, we add a build script and a start script to the package.json file. The package.json file looks like this:
{
"name": "nodejs-ts2",
"version": "1.0.0",
"description": "Developing Typescript Node.js applications using visual studio code",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rimraf ./build && tsc",
"start": "npm run build && node build/index.js"
},
"keywords": [
"TypeScript",
"Visual Studio Code",
"Node.js"
],
"author": "Matt Varghese",
"license": "UNLICENSED",
"devDependencies": {
"@types/node": "^14.0.27",
"rimraf": "^3.0.2",
"typescript": "^3.9.7"
}
}
This means, you can now build as well as run from the terminal by the commands npm run build, and npm run start respectively.
Subversion Note
(Visual
Studio Code has git integration, but I have not played with it. I use
subversion rather). It is pretty annoying to add the node_modules folder with all that content into the SVN repository. Instead, I've just made a restore-modules.bat file in the project folder that has all the npm install commands. This way, I can add the node_modules folder (and the build folder) to the ignore list in SVN and save a ton of storage space. I just have to run .\restore-modules.bat every time I checkout or cleanup the repository.
Optionally, you can check if the folder node_module exists before doing npm install in the BAT file. Example:
IF NOT EXIST node_modules CALL npm install -D typescript @types/node rimraf
Then, you can add the restore modules BAT file to your build script if you have a build script in your package.json.
"scripts": {
"build": "restore-modules.bat && rimraf ./build && tsc",
"start": "npm run build && node ./build/index.js"
}
Standalone Executables
You can use the pkg package to quickly build standalone executable (reference). First install the package: npm install -D pkg (or you can install globally: npm i pkg -g). Then in your package.json file, add a property "bin" that points to your main entry point .js file. Finally, run the command to package .\node_modules\.bin\pkg . (or pkg . if you installed globally). This produces Windows, Mac, and Linux executables.
Summary of steps
Create a folder for the project. Open in Visual Studio Code. Open terminal.
mkdir src,build
npm init -y
npm install -D typescript @types/node
npx tsc --init --rootDir src --outDir build --esModuleInterop --resolveJsonModule --lib es6 --module commonjs --allowJs true --noImplicitAny true --sourceMap true
Add a ts source file in the src folder, with at least some test content such as:
console.log("Hello World!");
Add build task from Terminal > Configure tasks for "tsc: build - tsconfig.json"
Go to Debug tab, create launch.json for "Node.js". Fix "program" and "outFiles", add
"preLaunchTask": "tsc: build - tsconfig.json"
Further things to explore
- Publishing to Azure: https://docs.microsoft.com/en-us/visualstudio/javascript/publish-nodejs-app-azure
.
No comments:
Post a Comment