TypeScript

Hello World - A full Tutorial

When you start programming, probably one of the first things you will learn is how to output “Hello World!“. The program is simple to write and lets you get familiar with the syntax of the programming language.

If we take JavaScript for example. The code needed for hello world is:

console.log('Hello World!');

With this piece of code, you learn several things

  • There are build-in objects like console
  • you can call functions with a .
  • objects have functions like log
  • strings like Hello World! exist

But this is only one small part of learning a language. It is also important to learn about the surrounding tooling and best practices. Let’s approach “Hello World” as a production-ready project. This project will need to take tooling, documentation, and long-term support into account.

How would we approach it?

tl;dr: The final codebase is on Github

Program Definition

We would first start by actually defining what the program should do. The most common description of “Hello World” is this:

The “Hello World” program is to output “Hello World”.

Note: In the Hello World Collection you can view examples in different programming languages.

Questions before writing the code

Every definition of a program is incomplete. You will have to ask many questions to avoid miscommunication. Usually, they are many different things implied and nothing is obvious. To ensure that you develop the right software, you must ask clarifying questions.

Here are a couple of questions you should ask yourself before you start coding:

How should the output look like?

This question may seem unnecessary - We want to output Hello World in the terminal window.

But was that part of the original definition? JavaScript can run in very different environments. We could mean by ‘output’ that it is visible in the browser, or it could be that we want to print it on paper, audio output, 3D Printer, etc.

To keep it simple we will say the terminal should display the output.

Which programming language should you use?

If we treat this as a real-world application there might not be an obvious answer.

Assuming the program should work on a mobile device. We have to do a cost analysis of the available options.

  • Android supports Java / Kotlin
  • iOS supports Objective-C / Swift
  • Are there alternatives? react-native, PhoneGap, flutter, ionic, etc.

Is a native app even needed?

  • Progressive Web App - HTML, JavaScript, CSS

Then you have to take more factors into account:

  • Preference of available developers in the team
  • The ability to hire new developers.
  • A strategic choice to create a new programming language

Not doing research on this subject beforehand can lead to a costly rewrite or that the project fails.

For our application, we will be targeting NodeJS, a runtime environment for JavaScript. So I guess we will go with JavaScript.

What is your deliverable to the end-user?

Or “How should the end-user run the program?

Depending on the intended purpose of your code you may need to ensure extra steps:

  • Standalone Application: create a functioning binary executable
  • Executable Code: execute output with its native runtime environment
  • Library: Not executable, used by another application

We will go with executable code.

Reworking the Definition

  • The “Hello World” program is to output “Hello World”.
  • Output should be visible in a Terminal window.
  • Written in JavaScript
  • Executed by the Node.JS runtime environment

This definition is more exact and allows you to get started.

Starting the implementation

Now let’s start creating our Hello World application.

Set up the development environment

First, we will have to download and install the runtime environment.

For JavaScript this is NodeJS.

(For our example we will be using Node 10.x, use whatever version currently is available. The LTS is the long-term support version and the other one is the latest stable version.)

Then we need a text editor. We can use the simple Notepad editor.

Write the Program

We create a single file called HelloWorld.js and edit it in your text editor.

console.log('Hello World!');

Wait - How did I know to do that? You have to read the API from NodeJS. You can read up on the console object and the log function here https://nodejs.org/dist/latest-v12.x/docs/api/console.html#console_console_log_data_args

Manual Test

Now to see that our program works we execute the command:

node HelloWorld.js

And we see our results “Hello World” printed on the console. What we also see is that our nodeJS environment is set up and is working fine.

First Review

First thing: Great to see that the program is providing the output as defined by the program. However we need to consider that the next developer can understand the program and that it follows industry best practices.

If a new programmer, it would be unclear what files are needed for this application. The files should be structured in a way that it is clear which files are needed and what is the executable code.

Additionally you should be able to keep track of the version number of this application. You should add a package.json file to have a minimal documentation what this program does.

JavaScript was the wrong choice of programming language. It is lacking the ability to define types. Types provide useful information on how functions work. Types will improve how you write and debug your code. Especially when you use a code editor like VSCode.

Instead of using JavaScript you should use a language that compiles to JavaScript. Here are a couple of options:

I am going to go with TypeScript, as it is the most like JavaScript. It gives me the option to go back to JavaScript if needed. I would have to remove the extra ‘type information’. It is then again valid JavaScript. The tooling support in VSCode is excellent for TypeScript. Many popular packages on npm use it or have TypeScript type definitions available.

Refactoring Hello World into an Application

Project Directory Structure

Now let’s create a main folder called “hello-world-app”, with the sub-folders “src” and “dist”

hello-world-app
/src
/dist

Package.json

This uses a package.json file. A package.json file keeps track of the used dependencies, can provides a standardized set of scripts, and defines the entry point of your application.

To create a package.json file you go into the directory and execute npm init. You will be then asked a series of questions. Alternatively you can use automatic generation npm init -y which will answer all questions with the default answer

Note: Automatic generation, adds a permissive open source license ISC to your code. If you are not planning on creating an open-source package you will need to change this. As the legal team in your organization for advice.

Let’s go with automatic generation and adjust the values later on.

For now, we only have to change the value for main that it points to the new entry point in the dist folder:

{
 ...
 "main" : "dist/index.js"
 ...
}

Adding Typescript

To use TypeScript, we will need to use the TypeScript compiler. We can add it to our project using NPM. This will download the compiler and add it as a dependency.

npm install --save-dev typescript

This command creates a folder “node_modules” and a “package-lock.json”. In the node_modules all dependencies are downloaded, including the dependencies of the dependencies. You can read up on the package-lock.json here - It is used for optimizing NPM and ensures the same package and dependency tree structure is installed when you reinstall the dependencies.

Configuring Typescript Compiler

You can either use the typescript compiler directly from the console providing flags, or the more convenient way is to use a tsconfig.json.

With the command npx tsc --init a standard configuration file will be created.

  • Change the line // "outDir": "./", to "outDir": "./dist",
  • Change the line // "rootDir": "./", to "outDir": "./src",

(by removing the // you are uncommenting the lines)

Now when we execute the typescript compiler it will look for .ts files in the “src” dir and put the compiled output into the “dist” dir.

A full overview of the compiler flags can be found here

Adding a build step to package.json

By using TypeScript we will have a compile/build step before we can execute our code. To make this more simpler for all developers we add a build command into the scripts section of the package.json:

{
 ...
 "scripts": {
    "build": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
 ...
}

Now we can run the typescript compiler by executing the command npm run build.

By adding this command instead of directly using tsc we can ensure that every developer can build and run the application even without knowledge of the details of the TypeScript compiler. Additionally if the build process becomes more complicated we can simply add more steps to this command without changing the workflow of all developers or having the need to provide manual build instructions.

Adding the index.ts

Finally we can actually add our code. create the file src/index.ts

console.log('Hello World!');

When we now run npm run build the file dist/index.js is going to be generated.

Running the application

In order to run the application we need to execute npm run build and then node dist/index.js. To avoid forgetting to build before we start our application we can configure this in the “scripts” in package.json.

By using the special prefix “pre” we can ensure that a command is run before the actual command. More Info

{
 ...
 "scripts": {
    "prestart": "npm run build",
    "start": "node dist/index.js",
    "build": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
 ...
}

Now we can execute npm start and our application will start. - Just as before we should see “Hello World!” in the Terminal.

Review - Second Round

Once again - great work! Your program is still working just as before.

But if I read the code I am unsure if this was actually the intention of the program. you simply wrote

console.log('Hello World!');

You have two options to fix this, either you write a comment, which may be outdated as soon as the functional scope of the program changes. Your you rewrite the code in a manner that it is clear to the next developer that this was the actual intention of your code:

const printHelloWorld = (): void => {
  console.log('Hello World');
};
printHelloWorld();

Like this the functionality of your app is bundled in a function and when you read the index.ts file you see ah printHelloWorld() is the only function that is being called, it is the intention of this app to printHelloWorld();

You are also not using Version Control, that is important to track all of the changes to your project.

Additionally there is no test coverage. In fact when I run npm run test I get an Error - Tests not specified.

To ensure the quality of your software you need to add tests, not that when you refactor or add new functions you break existing functionality.

Even better would be if you consider that we may want to change the output in the future, we may not want to output it on the terminal but somewhere else.

Adding Version Control - Git

You need to get a Version Control Program. These days for JavaScript development it is standard to use Git

Initialize as git Repository

To put your existing code under version control you simply have to execute:

git init

Add .gitignore file

Additionally you will want to exclude the “node_modules” folder from the repository, as these files are controlled and downloaded from npm.

To do this add a .gitIgnore file with the contents

node_modules

Now git will ignore that folder.

Initial commit

Now you need to add all files to git and do your first commit:

git add .
git commit -m "Initial commit"

And now you are done. As soon as you make changes to your files you now can just commit them to your git repository.

Adding test tools - Jest

Jest is a JavaScript Testing Framework. you can use it with TypeScript as well, when you use the package ts-jest.

More info on Jest you can find here.

Install Dependencies

You will need a couple of packages

  • jest - the actual javascript Testing framework
  • ts-jest - the ability to use typescript with jest
  • @types/jest - the TypeScript Definition files for jest
npm install -D jest ts-jest @types/jest

Configure Jest

Running the following command will add a pre-configured jest.config.js to your project:

npx ts-jest config:init

We can now execute our tests by running npx jest.

Write a test

First we have to make the function “printHelloWorld” available from outside the file.

For this we need to add the keyword export to the function.

index.ts

export const printHelloWorld = (): void => {
  console.log('Hello World!');
};

printHelloWorld();

Now we can import the function in our test. To write a test we need to create an additional file index.test.ts.

import { printHelloWorld } from '.';

it('prints hello world', () => {
  const log = jest.spyOn(console, 'log');
  printHelloWorld();
  expect(log).toHaveBeenCalledWith('Hello World!');
  expect(log).toHaveBeenCalledTimes(1);
});

The Jest SpyOn function lets you analyse how often and with which arguments a function was called. In this case it should have been called with “Hello World!” and it only should have been called a single time.

With npx jest we can test if our tests are running correctly.

Adjust package.json

Now that we know that jest has been set up correctly. In the “scripts” section change the value for “test” to “jest”.

Additionally we want to ensure that the test are run at build time. For this we add a new script "prebuild": "npm run test --bail" (The —bail flag causes jest to exit as soon as the first test fails.)

{
 ...
 "scripts": {
    "prestart": "npm run build",
    "start": "node dist/index.js",
    "prebuild": "npm run test --bail",
    "build": "tsc",
    "test": "jest"
  },
 ...
}

Code Review - Third Round

Ok, I can see you added tests and did a small refactoring. However now if I run npm start I get to see an error message. You probably should fix that.

You are also missing any documentation. You should write a README.md file to further explain what this application is doing and how to run the application - it may not be obvious to the next developer.

You also should add a Linter to your project to ensure that all developers develop with the same coding guidelines.

Fixing the TypeScript Error

When you run npm start you will encounter following error:

node_modules/@types/babel__template/index.d.ts:16:28 - error TS2583: Cannot find name ‘Set’. Do you need to change your target library? Try changing the lib compiler option to es2015 or later.

You are seeing this message as the default tsconfig.json has as a compile “target”: “es5”. However looking at the ECMA Script compatibility table NodeJS 10.x supports “es2018”.

So we adjust the tsconfig.json as such:

{
  "compilerOptions": {
      "target": "es2018"
      ...
  }
}

Project Readme

The goal of this document is to give the person that want to use your project a starting point.

  • What does this project do?
  • What are the prerequisites to get this program running?
  • How should I use this project?

The file should be written in Markdown. This way when you use a Git Repository like Github or GitLab or Bitbucket - the file will be displayed as HTML when you browse the repository.

Our Readme.md should look something like this:

# Hello World Application
It prints to the console Hello World.

## Prerequisites
* [NodeJS 10.x](https://nodejs.org/en/)

## Run the program
Open a Terminal and enter:

\`node HelloWorld.js`

Adding ESLint

Eslint is a JavaScript / TypeScript Linter.

Adding dependencies

npm i -D eslint

Configure ESLint

npx eslint --init

This command will ask you a series of questions:

  • ? How would you like to use ESLint? To check syntax, find problems, and enforce code style
  • ? What type of modules does your project use? None of these
  • ? Which framework does your project use? None of these
  • ? Does your project use TypeScript? Yes
  • ? Where does your code run? Node
  • ? How would you like to define a style for your project? Use a popular style guide
  • ? Which style guide do you want to follow? Standard
  • ? What format do you want your config file to be in? JavaScript
  • [peerDependencies] ? Would you like to install them now with npm? Yes

After this you will have a file called .eslintrc.js

Execute eslint

Now you can run eslint:

npx eslint src/**/*.ts

It will probably find a lot of errors:

/Users/neal/projects/advanced-hello-world/src/index.test.ts
  1:33  error  Strings must use singlequote                   quotes
  2:34  error  Extra semicolon                                semi
  4:1   error  'it' is not defined                            no-undef
  5:1   error  Expected indentation of 2 spaces but found 4   indent
  5:17  error  'jest' is not defined                          no-undef
  5:43  error  Extra semicolon                                semi
  6:1   error  Expected indentation of 2 spaces but found 4   indent
  6:22  error  Extra semicolon                                semi
  7:1   error  Expected indentation of 2 spaces but found 4   indent
  7:5   error  'expect' is not defined                        no-undef
  7:55  error  Extra semicolon                                semi
  8:1   error  Expected indentation of 2 spaces but found 4   indent
  8:5   error  'expect' is not defined                        no-undef
  8:41  error  Extra semicolon                                semi
  9:3   error  Extra semicolon                                semi
  9:4   error  Newline required at end of file but not found  eol-last

/Users/neal/projects/advanced-hello-world/src/index.ts
  1:34  error  Extra semicolon                                semi
  4:1   error  Expected indentation of 2 spaces but found 4   indent
  4:34  error  Extra semicolon                                semi
  7:18  error  Extra semicolon                                semi
  7:19  error  Newline required at end of file but not found  eol-last

Fix Issues found

The quickest way to fix a lot of the style issues is just to run:

npx eslint src/**/*.* --fix

Now the remaining issues should be:

  3:1   error  'it' is not defined      no-undef
  4:15  error  'jest' is not defined    no-undef
  6:3   error  'expect' is not defined  no-undef
  7:3   error  'expect' is not defined  no-undef

These are all related to jest. To eliminate thes issues you need to modify the eslint config file. You need to add “jest: true” to the env(ironment) variable:

module.exports = {
  env: {
    es6: true,
    node: true,
    jest: true
  },
  ...
}

Now when you run eslint again all errors should be resolved.

Add eslint to your build step

Again we add eslint to our build steps. As it is part of “prebuild” and we want to execute two commands we add a && inbetween.

{
 ...
 "scripts": {
    "prestart": "npm run build",
    "start": "node dist/index.js",
    "prebuild": "eslint src/**/*.* --fix && npm run test --bail",
    "build": "tsc",
    "test": "jest"
  },
 ...
}

Code Review

Ok great, the environment is looking good. Let’s just do a quick code review.

I think we could improve this piece of code.

export const printHelloWorld = (): void => {
  console.log('Hello World!');
};

You see we just are using “Hello World!” directly. We should convert that into a constant. This way we also can ensure that the string in the test is identical.

index.ts

export const HELLO_WORLD = 'Hello World!';

export const printHelloWorld = (): void => {
  console.log(HELLO_WORLD);
};

printHelloWorld();

index.test.ts

import { printHelloWorld, HELLO_WORLD } from '.';

it('prints hello world', () => {
  const log = jest.spyOn(console, 'log');
  printHelloWorld();
  expect(log).toHaveBeenCalledWith(HELLO_WORLD);
  expect(log).toHaveBeenCalledTimes(1);
});

Hmn that would solve it, however what should we do when the string changes. We would always have to search multiple files to find where that string was initially defined. Or we need to add internationalization (i18n) support in the future - so all strings need to change for the correct language.

Simple i18n support

We should extract the string in its own json config file. for this we create a new subdirectory called i18n in the src folder.

Key Value Config file

Now we create a file /src/i18n/en.json. The name should follow the ISO 639-1 Language Codes. That makes it easy for you to identify the language of the translation file.

{
  "Hello_World": "Hello World!"
}

TypeScript Config

Out of the box TypeScript is not able to import a JSON file as an object. You need to enable it by activating it in the tsconfig.json by adding the flag "resolveJsonModule": true;

Note: if you are using generated tsconfig.json file, in Version 3.6 there is nothing to uncomment, just add it as the last item of the json object, you also have to add a comma at the end of the last flag "esModuleInterop": true,

Refactor

index.ts

import i18n from './i18n/en.json';

export const printHelloWorld = (): void => {
  console.log(i18n.Hello_World);
};

printHelloWorld();

index.test.ts

import { printHelloWorld } from '.';
import i18n from './i18n/en.json';

it('prints hello world', () => {
  const log = jest.spyOn(console, 'log');
  printHelloWorld();
  expect(log).toHaveBeenCalledWith(i18n.Hello_World);
  expect(log).toHaveBeenCalledTimes(1);
});

Final Review

Ok this is looking great, of course this is not real i18n support, however if we need it in the future we can add it without a lot of effort.

Conclusion

The Hello World program can be used for two things:

  • To print out “Hello World” - Learning your first step in an new language.
  • To have a quick check that your development environment works.

You can say this project was overengineered for such a trivial small program as “Hello World!” - and I would somewhat agree with you. However most ‘simple’ projects in the real world have a tendency to spiral out of control and there it would be quite helpful to already have a development toolchain.

I just wanted to show that In order to create high quality code, that is maintainable and multiple people can work on you need a couple of additional tools:

  • General knowledge of the command line
  • General knowledge of the Industry Best Practices
  • Runtime Environment - You need to execute the code somehow
  • Code Editor - You need to write the code
  • Version Control - You need to ensure that you can restore things when things go wrong
  • Compiler - you need to convert your code to be executable
  • Test Framework - you need to ensure that new functionality does not break old functionality
  • Code Style Enforcer - you need to ensure everyone on your team is creating similar readable code
  • Documentation - A minimal documentation how to start and use the program. Maybe even more is needed
  • Handling of Strings - The world is international sooner or later you probably will have to translate your application

One last thing

The proposed build chain is in no way a final recommendation that you should use this setup for your project. You need to make for everything your own decision. For practically every tool, Git, NPM, NodeJS, VSCode, Git, TypeScript, Jest, ESLint - there are alternatives out there. Also I am just using the default configurations, you probably will want to adjust them to your specific project accordingly.

You can view the final codebase on Github