Interested in getting started with Node.js and TypeScript? Let’s jump right in.
Node.js
Node.js is “a JavaScript runtime built on Chrome’s V8 JavaScript engine.” Effectively, some clever developers have separated the part of Google Chrome that runs JavaScript and made it available for people to use outside of the web browser. Node.js is open-source, cross-platform, and used by many small and large groups. Beyond just web applications, it may be used to write console-style applications, services, simple utility scripts, and much more.
Node.js Installation
If you’re in a corporate environment, you may have Node.js packaged for easier distribution - so look into that first. If you’re free to install it on your own though, start by downloading either the latest long term service (LTS) or current release at the Node.js website if you’re on Windows or MacOS. If you’re on Linux, check your distribution’s prefered method for Node.js installation. For this post, I’ll be using the latest LTS release (currently v14.18.1).
Once you’ve run the download and install, fire up a terminal (or Windows PowerShell) and check which version of Node.js you have installed:
node --version
It should match what you elected to download. If it doesn’t and you’re on Windows, check your PATH
environment variable to ensure the node
executable path is in it (the MSI installer of Node.js installs to C:\Program Files\nodejs\
, which needs to be either in your system or user PATH
environment variable).
Hello World!
With the runtime installed, you’re ready to write your first program using it.
Tip
You’re going to need a text editor of some kind to write code. While Notepad.exe
in Windows will work, I’d recommend using Visual Studio Code. It’s free and has some nice features for JavaScript development.
-
First thing’s first, we need a fresh directory to store our application.
mkdir nodejs-hello-world cd nodejs-hello-world
-
Let’s create a file for our JavaScript:
Windows
ECHO "" > index.js
Mac/Linux
touch index.js
-
Next you’ll open the file in your editor of choice (I’m using Visual Studio Code here):
code index.js
Tip
If you’re using Visual Studio Code, it’s actually better to open the directory. Try
code .
instead! -
With
index.js
open, let’s write some simple code inindex.js
:console.log("Hello World!");
-
Back in your terminal, let’s go ahead and run the program. This is done by providing
node
the location of the script.node index.js
-
Celebrate! Really. You just installed Node.js and wrote your first application using JavaScript. 😄
package.json
With Hello World out of the way, it’s time to learn about the package.json
file. What’s it for? What should you know about, and what are some cool things you can do with it?
The package.json
file is the “manifest” of your application. It’s a simple JSON
file that unlocks project configuration, usage of third-party dependencies, and additional developer productivity.
NPM Init
The easiest way to create one is to use the Node Package Manager (NPM) to initialize your project.
In your current project directory, run the following:
npm init
It’s going to ask you a few questions. At this point in time, the answers don’t matter. So you can hit the ENTER
key until it’s done asking them.
Looking back in your project directory, you’ll find a newly created package.json
file with not much in it:
{
"name": "nodejs-hello-world",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Let’s review each of these properties and what they’re for.
- name - the name of your project. This doesn’t really matter unless you’re creating a reusable NPM package.
- version - the semantic version of your application. Again, this matters much more for NPM packages. So don’t sweat changing it for your own applications unless there’s something more complex (and interesting!) that your team is doing with it.
- description - another one for NPM packages. It’s a brief description of the app/package. Next.
- main - the entry point of your application. By default, it’s
index.js
… which is what we need! If you ever change the location/name of yourindex.js
, make sure and update this. - scripts - this is a configurable set of node scripts that you can run. It comes with the default
test
, but we can (and will!) add some more. - author - the package author name. Another one good for NPM packages.
- license - indicates the license of the package.
Ouf of all of the above, the ones to watch at this point in learning are main and scripts.
Great you say. What good did this do me? Not much at this point. Consider this the foundation we’re going to build upon.
Tip
You can run the script defined in the main
property by running:
node .
Scripts
We’ll start by changing the package.json
to take advantage of the scripts
capability. Remove the test
script, and add a start
script like the below:
{
"name": "nodejs-hello-world",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "ISC"
}
Save the file, then run the below in your terminal:
npm start
If you’re successful, the output looks like this on Windows:
> [email protected] start C:\D\learning\nodejs-hello-world
> node index.js
Hello World!
Cool! What can we learn from this? The scripts
configuration section can run arbitrary terminal commands on our behalf. In this way, you can write something useful, share it with those on your team, and they don’t have to know the exact commands to run to start or interact with your application. They can just look at the scripts
section and run what seems most appropriate!
Dependencies
Node.js ships with the Node Package Manger (NPM). In fact, we just used to to run the start script above. NPM also allows us to install and manage Node packages. By default, dependencies are sourced from npmjs.com. In some corporate environments, this may be changed to look at an internal package management feed or just an alternate to npmjs. This allows groups to build and maintain their own reusable code, distributed using private NPM packages.
Warning
While most Node packages are non-malicious, you should always do your due diligence before using (or executing!) anyone else’s code on your machine.
Local or Global
When installing NPM packages, they can be installed either locally or globally. Local packages are available only to the application within the same directory as where they are installed. Global packages can be used by any Node.js package on the same machine, as well as from the terminal (if that package supports it). Unless you’re trying to achieve something specific, always default to installing your packages locally.
Direct Dependencies
Let’s install an NPM package and use it within our application. It’s time our Hello World app had some color! For this, we’ll be making use of the colors NPM package. Take a quick look at how the color module may be used.
npm install colors
Once complete, take a look at the package.json
file. It now lists colors
inside of the dependencies
section. This section is used to list the direct dependencies of your application. You should use it for any dependency directly called by your application code. There are other types of dependencies, and we’ll touch on at least one more of those before the end of the post.
Now let’s make use of it!
-
In your editor, open the
index.js
file. -
On line 1, require the
colors
module.require("colors");
-
Update your Hello World line to colorize it. Note that there are lots of available colors!
console.log("Hello World!".rainbow);
-
When you’re done, here’s what
index.js
should look like:require("colors"); console.log("Hello World!".rainbow);
-
Save the file, jump back to your terminal, and run the application.
Fancy!
What did we just do?
- We used the
require
keyword to inform Node.js that it should import thecolors
module. - We used the
.rainbow
extension that thecolors
module added to all strings.
Is there more we can do? No doubt!
Dependency CLI Usage
Many dependencies expose a CLI, or command line interface. The colors
package does as well. Maybe we like colors in our app, but others don’t? Simple. Let’s add a script that disables them, taking advantage of the colors
package capability to do so.
-
Add a
start:no-colors
script topackage.json
(just the script section shown below, leave the rest alone):"scripts": { "start": "node index.js", "start:no-colors": "node index.js --no-color" }
-
Save the file, then try it out back in your terminal:
npm run start:no-colors > [email protected] start:no-colors C:\D\learning\nodejs-hello-world > node index.js --no-color Hello World!
What did we just do?
- We added a new
script
to ourpackage.json
which makes use of the documented CLI capabilities of thecolors
package. - We ran that script using
npm run <script-name>
.
But wait! What’s this npm run
thing? We made use of npm start
before to run a script, but that was a special case that the start script and only a few others can use. Most of the time, you’ll need to use npm run
before your script name.
Tip
npm start
, npm stop
, npm restart
, npm test
, and npm run-script
will all run their corresponding script without having to make use of npm run <script-name>
syntax.
TypeScript
TypeScript is JavaScript with syntax for types. TypeScript code gets converted to plain JavaScript before running in the web browser, or with the Node.js runtime. Most commonly, developers use it to get better editor integration and safety, as many common errors are caught during the JavaScript conversion process.
TypeScript Installation
We can leverage NPM to install TypeScript. We’ll tweak how we perform the install to designate TypeScript as a development dependency as it is not directly used at runtime, only during our development of the application. Typically this will contain packages related to unit testing, packaging, document generation, etc.
npm install typescript --save-dev
Take a look at package.json
and notice how typescript
is referenced.
{
"name": "nodejs-hello-world",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"start:no-colors": "node index.js --no-color"
},
"author": "",
"license": "ISC",
"dependencies": {
"colors": "^1.4.0"
},
"devDependencies": {
"typescript": "^4.4.4"
}
}
Next, we need to setup the current directory as a TypeScript project. We’ll use the npx
command, which allows us to call packages with CLI exposure that aren’t installed globally (like TypeScript, in our case).
npx tsc --init
This creates a tsconfig.json
file in our project directory. It’s pretty big, so I won’t copy it here. But know that it sets some reasonable defaults for TypeScript transpilation. Depending upon your team and project needs, expect to see variations of the configuration.
Next, let’s setup a new script in package.json
to build our application:
{
"scripts": {
"build": "tsc"
}
}
Finally, rename the index.js
to index.ts
. All TypeScript files end in .ts
(or .tsx
for files containing JSX syntax).
TypeScript Transpilation
When we write TypeScript, we must transpile it into JavaScript before it can be used in web browsers or by the Node.js runtime. The TypeScript CLI (tsc
) will perform this transpilation for us, respecting the configuration in tsconfig.json
. Let’s give it a shot.
npm run build
Well… that didn’t go well, did it?
Let’s work through the errors and learn from them.
First error - it cannot find ‘require’? I thought that was valid? Yes and no. It’s valid JavaScript, but TypeScript is complaining because it can’t find types for Node and it’s even giving us guidance on what to do. Great! Copy+paste:
npm i --save-dev @types/node
What’s that doing anyway? Well, npm i
is the short syntax for npm install
. --save-dev
is an option, and while we normally put it last it can be in any position after the i
or install
. Lastly, @types/node
is an NPM package that will inform TypeScript about the types present in the Node.js runtime.
Second error - “Property ‘rainbow’ does not exist on type ‘“Hello World!”’. Another error having to do with types… My bet is that we need types for the colors
module. Many NPM packages come with their own types. Some don’t. First thing we should do is check and see if this package actually has types information.
- In the project directory, find and expand the
node_modules
directory. This is where NPM stores all of the packages we’ve set as dependencies (and all of those depedencies’ depedencies and so on). - Find the
colors
directory and expand. - Look for a file ending with
.d.ts
. This will contain types information so that TypeScript (and our editor) can make good decisions.
Wow. It does have a types file. What’s wrong then?
Let’s look back at our code:
require("colors");
console.log("Hello World!".rainbow);
While TypesScript can make use of the require
method from Node.js, it much prefers for us to import
packages instead. Let’s swap that out.
import "colors";
console.log("Hello World!".rainbow);
If you’re using Visual Studio Code, all of the red squiggly lines just went away, so you know you’re good. Let’s run our build now!
npm run build
No errors! Let’s take a look at our project directory. Hey! Our index.js
file is back! TypeScript transpiled it from our index.ts
. Reviewing the contents, it’s not all that different from our previous index.js
before we made all the changes for TypeScript. But does it still work?
npm start
Sure does. Let’s make one last change to package.json
to run our build
before our start
. We’re going to take advantage of some nice convention to run multiple scripts in a sequence. You can prefix any script name with pre
and it will execute before that script. Here’s our updated package.json
scripts
block:
"scripts": {
"build": "tsc",
"prestart": "npm run build",
"start": "node index.js",
"start:no-colors": "node index.js --no-color"
},
Running npm start
, you’ll now see that our build script runs first. Nice.
Fun with TypeScript
Now that we can perform Hello World with Node.js and TypeScript, let’s explore a couple more basics to get us started.
Refactoring Round 1
Right now, all of our code is in index.ts
. As index.ts
is just meant to be the entry point of the application, we ought to keep it very high level and put our detailed logic in other files. In this way, the index.ts
serves it’s single purpose, and the hello.ts
serves it’s single purpose (see Separation of Concerns and Single Responsibility Principle if you’re interested in why this is a good idea in your applications). Time to refactor! 😄
-
In your directory, create a
hello.ts
file alongsideindex.ts
. -
Move all of the code dealing with saying Hello World into
hello.ts
. -
Now, let’s wrap our
console.log
line in a function, substitute outWorld
for someone’s name, and export that function:import "colors"; export function hello(name: string) { console.log(`Hello ${name.rainbow}!`); }
What did we just do?
- The
export
directive exposes ourhello
function to other files who may want to call it. Without it, thehello
function wouldn’t be visible. - We provided the
string
type on thename
function parameter. This allows the IDE and the TypeScript transpiler to error out if you try to call thehello
function with a number, Date, or anything that’s not a string. - We made use of template literals (also sometimes referred to as string interpolation or template strings) to make our function include someone’s name in the output.
- I tweaked the logic a bit to colorize the name, but leave “Hello” alone. Might look neat!
- The
-
We’re not done yet. Save the file, the switch back to
index.ts
. -
We need to import our new
hello
module, and call the exportedhello
function:import { hello } from "./hello"; hello("Jon");
What did we just do?
- We imported the named export
hello
from the./hello
file. The name of the file and the method being the same is just a coincidence. You can export whatever you want from your TypeScript files - functions, constants, classes, etc. - We called the
hello
function and gave it a value.
Did it work? Yep.
- We imported the named export
Organize the Project and Transpile to a Single File
Right now, we have too many files in the root of the directory. Let’s follow JavaScript project convention and place all of our source code into a src
directory. Here’s what should be left in the root when you’re done:
node_modules
src
package-lock.json
package.json
tsconfig.json
Note - if you build now, it will be broken. Let’s finish out the next few steps.
Now that we have multiple files of source code, the TSC utility produces a .js
file for every .ts
. This is fine for some cases, but if you want to let users download your script in a webpage you probably want one file. For larger applications and more complex scenarios, bundlers are typically used (like WebPack or Vite). However, there’s also a very simple NPM package named @vercel/ncc
which we can use if we just want a single file output.
-
Install @vercel/ncc, saving it as a development dependency:
npm install @vercel/ncc --save-dev
-
Update the
build
script:"build": "ncc build ./src/index.ts -o ./dist"
-
Since the above command moved our application entry point (which needs to be JavaScript) into the
./dist
subdirectory, let’s also update themain
property of thepackage.json
:"main": "dist/index.js"
-
We’ll also switch over how we’re starting the application to use the shorter method (
node .
). Here’s the finalpackage.json
:{ "name": "nodejs-hello-world", "version": "1.0.0", "description": "", "main": "dist/index.js", "scripts": { "build": "ncc build ./src/index.ts -o ./dist", "prestart": "npm run build", "start": "node .", "start:no-colors": "node . --no-color" }, "author": "", "license": "ISC", "dependencies": { "colors": "^1.4.0" }, "devDependencies": { "@types/node": "^16.11.6", "@vercel/ncc": "^0.31.1", "typescript": "^4.4.4" } }
We can test the app using npm start
and see that it’s transpiling and producing a single file, located in the dist
directory, then running that with the Node.js runtime. While this doesn’t seem all that useful to us now, for many developers new to using TypeScript, this is a very easy path to producing the single file they need to use in their existing web applications without a lot of fuss or configuration.
Wrapping Up
I think this is enough for one blog post. Depending upon your interests and/or role, here are some good next steps:
- Aspiring Single Page App developer? Pick a framework (e.g. React, Vue, or Angular are three of the most popular right now - if you read this post in a year that list will probably change). Read their docs and try them out, then decide on a bundler (e.g. WebPack or Vite are both great, but there are others) in the same way - read, try, implement.
- QA Engineer or SDET? Use your newfound Node.js and TypeScript skills to drive Selenium or Cypress.js for End to End web testing; Appium for mobile app testing; or write all of your API tests with Jest and Axios. The sky is the limit to what you can automate and make testable.
- Backend developer? Become a fullstack dev by following the SPA dev path above. It’s great for the resume 😄
- Any IT role - Leverage the rich NPM package ecosystem to automate repetitive tasks as you work with files and websites on a day to day basis. While PowerShell or Bash are probably the first things you should learn on this path, Node.js’s available packages can achieve quite a bit with just some reading and light integration work.