Writing your first “Hello World” program in TypeScript with ease
By: Uday Hiwarale
Tags: Typescript, Nodejs, JavaScript, Programming, Web Development

Hello World Program

A simple Hello World program in JavaScript would be to print Hello World! message to the console. In TypeScript, just like in JavaScript, it would be using the console.log function call with a "Hello World!" string literal.

Let’s create a simple project with the directory name hello-world and place a hello.ts file inside it. The .ts extension will help us and the TypeScript compiler understand that it is a TypeScript file.

/hello-world

└── hello.ts

The hello.ts program looks like above. It’s plain and simple JavaScript, nothing TypeScript specific at the moment. To run this program inside a browser (by importing it using a <script> tab) or in Node.js, we need to convert it to the .js file. So let’s use the tsc command and provide the hello.ts as a source file.

The TypeScript compiler accepted the hello.ts file and created a hello.js file right beside the source file. Now we can import this hello.js file in the browser or run directly inside Node as shown above.

Adding Type Support

Let’s modify the previous example and let’s try to mimic a little modular project structure. Let’s also focus on making the program safe by adding TypeScript features such as type annotations.

/hello-world

└── src/

├── lib/

| └── utils.ts

└── program.ts

In the modified project structure, the src directory contains all the source ( .ts ) files. The utils.ts contains a sayHello function that the program.ts imports and executes. These program files look like below.

The :number part of result variable declaration is called the Type Annotation. The type annotation declares the ultimate data type of an entity such as a variable, an argument of the function, or the return value of a function. This type annotation syntax placed just behind the name of the entity in the entity declaration statement.

The number here in the type annotation is the built-in data type provided by the TypeScript that represents all the numbers. You can also create your custom types that may represent complex values such as an object of a specific shape or a function of a specific signature.

Once an entity is annotated with a type, its type can’t be modified. That means an entity can represent only a set of values once it is declared. Therefore the result variable in the above program can only contain number values during the lifetime of this program. You will see in a minute why the above program fails to compile simply because of this reason.

When we compile these program files using the tsc command, we could provide file paths of both the files or just the program.ts since it imports utils.ts therefore it is auto included in the compilation process. Focus on the type annotations in the program. The sayHello function accepts an argument of type string and returns a value of type string .

💡 We will learn more about type annotation, basic and abstract types and the type system in general in the upcoming lessons. We have used import statement in the program above. We are also going to discuss these in upcoming lessons.

This type information will be used by program.ts and we can already see a problem here. The result variable assignment statement shows an error. If you hover on it, you will be able to see the problem. But let’s try to compile the program and see what TypeScript compiler says about it.

Oops, looks like we made a mistake. The sayHello function returns a value of type string but the result variable is a type of number . We can save a number value as a string value. So we need to either change the type of result to string , to change the return type of sayHello function.

Now if we compile the program, TypeScript won’t complain about anything and we also do not see any errors in the IDE. When the TypeScript compiler generated the compiler .js output files, it places them right beside the source files, since it likes to maintain the original file paths.

/hello-world

└── src/

├── lib/

| ├── utils.js

| └── utils.ts

├── program.js

└── program.ts

Generally, we do not like to pollute our working directory. This is obviously bad and we need to fix it. What we can do is to provide an output directory where these files should be emitted. We can do that by invoking the tsc command with the --outDir command-line flag with the directory path.

Now the output files are placed inside dist directory but the TypeScript held on to the original file structure of the source file which is a good thing. Let’s see how compiled JavaScript looks like and what I said it’s a good thing.

By default, TypeScript converts ES6 import statement into CommonJS require() calls by default so that the output code can be run inside Node. This however doesn’t work inside a browser since the browser doesn’t support the CommonJS module system. But you can change it using --module flag.

You can also see that the TypeScript compiler performed some operations on the source code. It converted ES6 template string (inside sayHello function) into normal string concatenation using the + operator.

This process is called downlevelling since it down-compiles the code from a higher language version to a lower language version. This is done by the TypeScript compiler since the default target is set to ES3 (JavaScript version) which do not support template strings. You can change the target to ES6 or above to bypass this process using --target flag.

💡 You can learn more about these flags from the Compiler Flags lesson.

If we want to run this project using Node, we just need to invoke the node command and provide the dist/program.js file since it already imports the ./lib/utils.js file (relative to itself) using the require() call. Now you can see why keeping the original file structure in the output was a good thing. Had the output file structure different, Node would not have been able to find the ./lib/utils.js file relative to the program.js file.

Using tsconfig.json configuration file

So far we talked about --target and --module command-line flags and used the --outDir flag to change the output directory. These flags configure the compilation settings of the TypeScript compiler.

Similarly, we provided the source files to the TypeScript command from the tsc command itself. When the compiler-options get larger, so does the tsc command and it could be overwhelming to handle such a big command.

To solve this issue, TypeScript provides specifying these options through a JSON configuration file mainly named tsconfig.json . This file should be placed in the root directory of the project. When we invoke the tsc command from this root directory, the TypeScript compiler uses this file to extract the compilation settings just like values from the command-line options/flags.

So let’s create the tsconfig.json in the hello-world project directory.

In the above tsconfig.json , we have specified the source files to include in the compilation using the files field. The uitls.ts file is redundant since it is already imported inside program.ts . The compilerOptions field contains the actual compilation settings.

You can provide a custom file path of this configuration file using the tsconfig.json using the --project or -p flag such as tsc --project tsconfig.prod.json . You can also override compilerOptions of the imported tsconfig.json file by using the command-line flags.

In the above example, even though the tsconfig.json says the output directory is dist but we have overridden it using the --outDir flag. This could be useful while performing automated tasks.

Working with ts-node

So far we have learned that in order to run a TypeScript program, you first need to compile it and then run the output JavaScript ( .js ) files. But sometimes you really do not care about the output .js files, you just need to run the program using Node. So having this extra compilation step kinda seems unnecessary, though it is mandatory.

To solve this issue, some brilliant people came together and made ts-node . It is an open-source command-line utility to run .ts directly on Node without having to manually compile these source files to .js file since ts-node does that internally with added optimizations. You can just run ts-node program.ts command and the process we went through manually in the above example is taken care of by the ts-node under the hood.

To install this tool, follow this official documentation on the GitHub. I would recommend you to install this tool globally so that you can access it using the command ts-node from anywhere on your system. Once the installation is done, let’s move to our project and execute the ts-node command.

As you can see from the above example, we just executed the ts-node command and provided the src/program.ts file. The ts-node tool uses the TypeScript compiler to first compile the program and then run the output .js file using the node command. So in a nutshell ts-node is just a combination of tsc and node command but with added improvements.

If you want to provide custom compiler-options to ts-node , then you should put the tsconfig.json file in the directory where the ts-node command is being invoked, perhaps in the root directory of the project.

Just like the tsc command, the ts-node command looks for the tsconfig.json file but only to extract compilerOptions . The files field is ignored (also the include and exclude fields) so that we can provide the executable source file path from the command-line itself.