/ TECH, TYPESCRIPT, BLOG, SOFTWARE ENGINEERING

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 usually quite 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!");

In this short example you learn that there are buildin objects like console, you can call functions with a . objects have functions like log and strings like Hello World! exist.

However this is only one small part of learning a language. It is also important to learn about the surrounding tooling and best practices. Now lets take a deeper look at this example and just assume “Hello World” would be a real programming project that needs to have longterm support.

How would we approach it?

tl;dr: You can view the final codebase 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: Hello World has been written in practically every programming language. There is even a dedicated Hello World Collection where you can view examples in practically every language.

Questions before writing the code

Whenever you get a definition of a program, usually they are not perfect and have very many different things implied. In order to ensure that you are developing the right software you should be asking a couple of 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. Obviously we want to output Hello World in the terminal window. But is that really true? With JavaScript being able to run on 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 to a sheet of paper, printed on paper, audio output, 3D Printer etc.

We will go with terminal window for our application, but always keep in mind that you actually have to define the form and type of your application beforehand.

Which programming language should you use?

Obviously the programming language we want to learn. However if we treat this as a real world application there might not be an obvious answer. The language is usually defined by the target environment, and even then you probably have options.

Assuming the program should work on a mobile device. We suddenly 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

In many cases the choice of programming language is predetermined by additional factors like

  • which developers are currently available etc.

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 should be delivered to the end user?

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

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

  • Standalone Application: create a functioning binary executable
  • Executable Code: execute output directly with its native runtime environment
  • Library: Not executable, but can be integrated into 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.
  • Should be written in JavaScript
  • Should be executed by the Node.JS runtime environment

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

Starting the implementation

Now lets actually 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 longterm 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 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 correctly 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 I would now join this project as a new programmer it would be unclear for me 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. NodeJS applications do this by providing a package.json file where a lot of important information about the application is written. Additionally this file adds the support for NPM (Node Package Manager). You should add this to have a minimal documentation what this program does.

Lastly JavaScript is lacking the ability to define types. Types provide very useful information to the next developer how functions work, and even if you do not write comments in your code - types will drastically improve the ability to write and debug your code. Especially when you use a dedicated 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 similar to JavaScript. If the TypeScript project is dead I do not have to fear about my application, I just have to remove all of the additional type Information and I have again valid JavaScript and in addition the tooling support in VSCode is excellent and many popular packages on npm use it or have typescript type definitions available.

Refactoring Hello World into a Application

Project Directory Structure

Now lets 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 is very useful as it keeps track of the used dependencies, can provide 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, automatically 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.

Lets 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

In order 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 to our project.

npm install --save-dev typescript

After this command you will see that a folder “node_modules” and a “package-lock.json” was created. In the node_modules all dependencies are downloaded, including the dependencies of the dependencies. You can read up on the package-lock.json here - In short it is used for optimizing NPM and ensuring always the exact 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