Chapter 1: Understanding TypeScript
JavaScript is an enormously popular and powerful language. According to GitHub, it is the most popular language in the world (yes, used even more than Python), and the new features in ES6+ continue to add useful capabilities. However, for large application development, its feature set is considered to be incomplete. This is why TypeScript was created.
In this chapter, we'll learn about the TypeScript language, why it was created, and what value it provides to JavaScript developers. We'll learn about the design philosophy Microsoft used in creating TypeScript and why these design decisions added important support in the language for large application development.
We'll also see how TypeScript enhances and improves upon JavaScript. We'll compare and contrast the JavaScript way of writing code with TypeScript. TypeScript has a wealth of cutting-edge features to benefit developers. Chief among them are static typing and Object-Oriented Programming (OOP) capabilities. These features can make for code that is higher quality and easier to maintain.
By the end of this chapter, you will understand some of the limitations of JavaScript that make it difficult to use in large projects. You will also understand how TypeScript fills in some of those gaps and makes writing large, complex applications easier and less prone to error.
In this chapter, we're going to cover the following main topics:
- What is TypeScript?
- Why is TypeScript necessary?
Technical requirements
In order to take full advantage of this chapter, you should have a basic understanding of JavaScript version ES5 or higher and some experience with building web applications with a JavaScript framework. You'll also need to install Node and a JavaScript code editor, such as Visual Studio Code (VSCode).
You can find the GitHub repository for this chapter at https://github.com/PacktPublishing/Full-Stack-React-TypeScript-and-Node. Use the code in the Chap1
folder.
What is TypeScript?
TypeScript is actually two distinct but related technologies – a language and a compiler:
- The language is a feature-rich, statically typed programming language that adds true object-oriented capabilities to JavaScript.
- The compiler converts TypeScript code into native JavaScript, but also provides the programmer with assistance in writing code with fewer errors.
TypeScript enables the developer to design software that's of a higher quality. The combination of the language and the compiler enhances the developer's capabilities. By using TypeScript, a developer can write code that is easier to understand and refactor and contains fewer bugs. Additionally, it adds discipline to the development workflow by forcing errors to be fixed while still in development.
TypeScript is a development-time technology. There is no runtime component and no TypeScript code ever runs on any machine. Instead, the TypeScript compiler converts TypeScript into JavaScript and that code is then deployed and run on browsers or servers. It's possible that Microsoft considered developing a runtime for TypeScript. However, unlike the operating system market, Microsoft does not control the ECMAScript standards body (the group that decides what will be in each version of JavaScript). So, getting buy-in from that group would have been difficult and time-consuming. Instead, Microsoft decided to create a tool that enhances a JavaScript developer's productivity and code quality.
So then, if TypeScript has no runtime, how do developers get running code? TypeScript uses a process called transpilation. Transpilation is a method where code from one language is "compiled" or converted into another language. What this means is that all TypeScript code ultimately is converted into JavaScript code before it is finally deployed and run.
In this section, we've learned what TypeScript is and how it works. In the next section, we'll learn about why these features are necessary for building large, complex applications.
Why is TypeScript necessary?
The JavaScript programming language was created by Brendan Eich and was added to the Netscape browser in 1995. Since that time, JavaScript has enjoyed enormous success and is now used to build server and desktop apps as well. However, this popularity and ubiquity have turned out to be a problem as well as a benefit. As larger and larger apps have been created, developers have started to notice the limitations of the language.
Large application development has greater needs than the browser development JavaScript was first created for. At a high level, almost all large application development languages, such as Java, C++, C#, and so on, provide static typing and OOP capabilities. In this section, we'll go over the advantages of static typing over JavaScript's dynamic typing. We'll also learn about OOP and why JavaScript's method of doing OOP is too limited to use for large apps.
But first, we'll need to install a few packages and programs to allow our examples. To do this, follow these instructions:
- Let's install Node first. You can download Node from here: https://nodejs.org/. Node gives us
npm
, which is a JavaScript dependency manager that will allow us to install TypeScript. We'll dive deep into Node in Chapter 8, Learning Server-Side Development with Node.js and Express. - Install VSCode. It is a free code editor and its high-quality and rich features have quickly made it the standard development application for writing JavaScript code on any platform. You can use any code editor you like, but I will use VSCode extensively in this book.
- Create a folder in your personal directory called
HandsOnTypeScript
. We'll save all our project code into this folder.Important Note
If you don't want to type the code yourself, you can download the full source code as mentioned in the Technical requirements section.
- Inside
HandsOnTypeScript
, create another folder calledChap1
. - Open VSCode and go to File | Open, and then open the Chap1 folder you just created. Then, select View | Terminal and enable the terminal window within your VSCode window.
- Type the following command into the terminal. This command will initialize your project so that it can accept
npm
package dependencies. You'll need this because TypeScript is downloaded as annpm
package:npm init
You should see a screen like this:
Figure 1.1 – npm init screen
You can accept the defaults for all the prompts as we will only install TypeScript for now.
- Install TypeScript with the following command:
npm install typescript
After all the items have been installed, your VSCode screen should look like this:

Figure 1.2 – VSCode after setup is complete
We've finished installing and setting up our environment. Now, we can take a look at some examples that will help us better understand the benefits of TypeScript.
Dynamic versus static typing
Every programming language has and makes use of types. A type is simply a set of rules that describe an object and can be reused. JavaScript is a dynamically typed language. In JavaScript, new variables do not need to declare their type and even after they are set, they can be reset to a different type. This feature adds awesome flexibility to the language, but it is also the source of many bugs.
TypeScript uses a better alternative called static typing. Static typing forces the developer to indicate the type of a variable up front, when they create it. This removes ambiguity and eliminates many conversion errors between types. In the following steps, we'll take a look at some examples of the pitfalls of dynamic typing and how TypeScript's static typing can eliminate them:
- On the root of the
Chap1
folder, let's create a file calledstring-vs-number.ts
. The.ts
file extension is a TypeScript specific extension and allows the TypeScript compiler to recognize the file and transpile it into JavaScript. Next, enter the following code into the file and save it:let a = 5; let b = '6'; console.log(a + b);
- Now, in the terminal, type the following:
tsc string-vs-number.ts
tsc
is the command to execute the TypeScript compiler, and the filename is telling the compiler to check and transpile the file into JavaScript. - Once you run the
tsc
command, you should see a new file,string-vs-number.js
, in the same folder. Let's run this file:node string-vs-number.js
The
node
command acts as a runtime environment for the JavaScript file to run. The reason why this works is that Node uses Google's Chrome browser engine, V8, to run JavaScript code. So, once you have run this script, you should see this:56
Obviously, if we add two numbers together normally, we want a sum to happen, not a string concatenation. However, since the JavaScript runtime has no way of knowing this, it guesses the desired intent and converts the
a
number variable into a string and appends it to variableb
. This situation may seem unlikely in real-world code but if left unchecked it could occur, because in web development, most inputs coming in from HTML come in as strings—even if the user types a number. - Now, let's introduce TypeScript's static typing into this code and see what happens. First, let's delete the
.js
file, as the TypeScript compiler may consider there to be two copies of thea
andb
variables. Take a look at this code:let a: number = 5; let b: number = '6'; console.log(a + b);
- If you run the
tsc
compiler on this code, you will get the errorType "'6'" is not assignable to the type 'number'
. This is exactly what we want. The compiler tells us that there is an error in our code and prevents the compilation from successfully compiling. Since we indicated that both variables are supposed to be numbers, the compiler checks for that and complains when it finds it not to be true. So, if we fix this code and setb
to be a number, let's see what happens:let a: number = 5; let b: number = 6; console.log(a + b);
- Now, if you run the compiler, it will complete successfully, and running the JavaScript will result in the value
11
:

Figure 1.3 – Valid numbers addition
Great, when we set b
incorrectly, TypeScript caught our error and prevented it from being used at runtime.
Let's look at another more complex example, as it's like what you might see in larger app code:
- Let's create a new
.ts
file calledtest-age.ts
and add the following code to it:function canDrive(usr) { console.log("user is", usr.name); if(usr.age >= 16) { console.log("allow to drive"); } else { console.log("do not allow to drive"); } } const tom = { name: "tom" } canDrive (tom);
As you can see, the code has a function that checks the age of a user and determines, based on that age, whether they are allowed to drive. After the function definition, we see that a user is created, but with no age property. Let's pretend that the developer wanted to fill that in later based on user input. Now, below that user creation, the
canDrive
function is called and it claims the user is not allowed to drive. If it turned out that usertom
was over 16 years old and this function triggered another action to be taken based on the user's age, obviously this could lead to a whole host of issues.There are ways in JavaScript to deal with this problem, or at least partially. We could use a
for
loop to iterate through all of the property key names of the user object and check for anage
name. Then, we could throw an exception or have some other error handler to deal with this issue. However, if we had to do this on every function, it would become inefficient and onerous very quickly. Additionally, we would be doing these checks while the code is running. Obviously, for these errors, we would prefer catching them before they make it out to users. TypeScript provides a simple solution to this issue and catches the error before the code even makes it into production. Take a look at the following updated code:interface User { name: string; age: number; } function canDrive(usr: User) { console.log("user is", usr.name); if(usr.age >= 16) { console.log("allow to drive"); } else { console.log("do not allow to drive"); } } const tom = { name: "tom" } canDrive (tom);
Let's go through this updated code. At the top, we see something called an interface and it is given a name of
User
. An interface is one possible kind of type in TypeScript. I'll detail interfaces and other types in later chapters, but for now, let's just take a look at this example. TheUser
interface has the two fields that we need:name
andage
. Now, below that, we see that ourcanDrive
function'susr
parameter has a colon and theUser
type on it. This is called a type annotation and it means that we are telling the compiler only to allow parameters of theUser
type to be given tocanDrive
. Therefore, when I try and compile this code with TypeScript, the compiler complains that whencanDrive
is called,age
is missing from the passed-in parameter, because ourtom
object does not have that property:Figure 1.4 – canDrive error
- So, once again, the compiler has caught our error. Let's fix this issue by giving
tom
a type:const tom: User = { name: "tom" }
- If we give
tom
a type ofUser
, but do not add the requiredage
property, we get the following error:Property 'age' is missing in type '{ name: string; }' but required in type 'User'.ts(2741)
However, if we add the missing
age
property, the error goes away and ourcanDrive
function works as it should. Here's the final working code:interface User { name: string; age: number; } function canDrive(usr: User) { console.log("user is", usr.name); if(usr.age >= 16) { console.log("allow to drive"); } else { console.log("do not allow to drive"); } } // let's pretend sometime later someone else uses the //function canDrive const tom: User = { name: "tom", age: 25 } canDrive (tom);
This code provides the required
age
property in thetom
variable so that whencanDrive
is executed, the check forusr.age
is done correctly and the appropriate code is then run.
Here's a screenshot of the output once this fix is made and the code is run again:

Figure 1.5 – canDrive successful result
In this section, we learned about some of the pitfalls of dynamic typing and how static typing can help remove and protect against those issues. Static typing removes ambiguity from code, both to the compiler and other developers. This clarity can reduce errors and make for higher-quality code.
Object-oriented programming
JavaScript is known as an OOP language. It does have some of the capabilities of other OOP languages, such as inheritance. However, JavaScript's implementation is limited both in terms of available language features and design. In this section, we'll take a look at how JavaScript does OOP and how TypeScript improves upon JavaScript's capabilities.
First, let's define what OOP is. There are four major principles of OOP:
- Encapsulation
- Abstraction
- Inheritance
- Polymorphism
Let's review each one.
Encapsulation
A shorter way of saying encapsulation is information hiding. In every program, you will have data and functions that allow you to do something with that data. When we use encapsulation, we are taking that data and putting it into a container of sorts. This container is known as a class in most programming languages and basically, it protects that data so that nothing outside of the container can modify or view it. Instead, if you want to make use of the data, it must be done through functions that are controlled by the container object. This method of working with object data allows strict control of what happens to that data from a single place in code, instead of being dispersed through many locations across a large application—which can be unwieldy and difficult to maintain.
There are some interpretations of encapsulation that focus mainly on the grouping of members inside a common container. However, in the strict sense of encapsulation, information hiding, JavaScript does not have this capability built in. For most OOP languages, encapsulation requires the ability to explicitly hide a member via a language facility. For example, in TypeScript, you can use the private
keyword so that a property cannot be seen or modified outside of its class. Now, it is possible in JavaScript to simulate member privacy through various workarounds, but again this is not part of the native code and adds additional complexity. TypeScript supports encapsulation with access modifiers such as private
natively.
Important Note
Privacy for class fields will be supported in ECMAScript 2020. However, as this is a newer feature, it is not supported across all browsers at the time of writing.
Abstraction
Abstraction is related to encapsulation. When using abstraction, you hide the internal implementation of how data is managed and provide a more simplified interface to the outside code. Primarily, this is done to cause "loose coupling." This means that it is desirable for code that is responsible for one set of data to be independent and separated from other code. In this way, it is possible to change the code in one part of the application without adversely affecting the code in another part.
Abstraction for most OOP languages requires the use of a mechanism to provide simplified access to an object, without revealing that object's internal workings. For most languages, this is either an interface or an abstract class. We'll review interfaces more deeply in a later chapter, but for now, interfaces are like classes whose members have no actual working code. You can consider them a shell that only reveals the names and types of object members, but hides how they work. This capability is extremely important in producing the "loose coupling" mentioned previously and allowing code to be more easily modified and maintained. JavaScript does not support interfaces or abstract classes, while TypeScript supports both features.
Inheritance
Inheritance is about code reuse. For example, if you needed to create objects for several types of vehicles—car, truck, and boat—it would be inefficient to write distinct code for each vehicle type. It would be better to create a base type that has the core attributes of all vehicles, and then reuse that code in each specific vehicle type. This way, we write some of the needed code only once and share it across each vehicle type.
Both JavaScript and TypeScript support classes and inheritance. If you're not familiar with classes, a class is a kind of type that stores a related set of fields and also may have functions that can act on those fields. JavaScript supports inheritance by using a system called prototypical inheritance. Basically, what this means is that in JavaScript, every object instance of a specific type shares the same instance of a single core object. This core object is the prototype, and whatever fields or functions are created on the prototype, they are accessible across the various object instances. This is a good way of saving resources, such as memory, but it does not have the level of flexibility or sophistication of the inheritance model in TypeScript.
In TypeScript, classes can inherit from other classes but they can also inherit from interfaces and abstract classes. Since JavaScript does not have these features, in comparison, its prototypical inheritance is limited. Additionally, JavaScript has no ability to inherit from multiple classes directly, which is another method of doing code reuse called multiple inheritance. But TypeScript does allow multiple inheritance using mixins. We'll dive deep into all these features later, but basically, the point is that TypeScript has a more capable inheritance model that allows for more kinds of inheritance and therefore more ways to reuse code.
Polymorphism
Polymorphism is related to inheritance. In polymorphism, it is possible to create an object that can be set to one of any number of possible types that inherit from the same base lineage. This capability is useful for scenarios where the type needed is not immediately knowable but can be set at runtime once the appropriate circumstances have arisen.
This feature is used less often in OOP code than some of the other features, but nevertheless can be useful. In the case of JavaScript, there is no direct language support for polymorphism, but due to its dynamic typing, it can be simulated reasonably well (some JavaScript enthusiasts will strongly disagree with this statement, but please hear me out).
Let's look at an example. It is possible to use JavaScript class inheritance to create a base class and have multiple classes that inherit from this one parent base class. Then, by using standard JavaScript variable declaration, which does not indicate the type, we can set the type instance at runtime to whichever inheriting class is appropriate. The issue I find is that there is no way to force the variable to be of a specific base type since there is no way to declare types in JavaScript, therefore there is no way of enforcing only classes that inherit from the one base type during development. So, again, you have to resort to workarounds such as using the instanceof
keyword in order to test for certain types at runtime, to try and enforce type safety.
In the case of TypeScript, static typing is on by default and forces type declaration when the variable is first created. Additionally, TypeScript supports interfaces, which can be implemented by classes. Therefore, declaring a variable to be of a specific interface type forces all classes instantiated to that variable to be inheritors of the same interface. Again, this is all done at development time before code is deployed. This system is more explicit, enforceable, and reliable than the one in JavaScript.
In this section, we have learned about OOP and its importance in large application development. We've also understood why TypeScript's OOP capabilities are significantly better and more feature-rich than JavaScript's.
Summary
In this chapter, we introduced TypeScript and learned why it was created. We learned why type safety and OOP capabilities can be so important for building large apps. Then, we saw some examples comparing dynamic typing and static typing and saw why static typing might be a better way of writing code. Finally, we compared the styles of OOP between the two languages and learned why TypeScript has the better and more capable system. The information in this chapter has given us a good high-level conceptual understanding of the benefits of TypeScript.
In the next chapter, we'll do a deeper dive into the TypeScript language. We'll learn more about types and investigate some of the most important features of TypeScript, such as classes, interfaces, and generics. This chapter should give you a strong foundation for using the various frameworks and libraries in the JavaScript ecosystem.