Introduction to TypeScript : Guide to Static Typing in JavaScript

Ritochit GhoshRitochit Ghosh
11 min read

Introduction

TypeScript is a statically typed, object-oriented, compiled programming language created by Microsoft. It is essentially a superset of JavaScript. It adds static typing, interfaces, and additional features to JavaScript, which are critical at the production level. TypeScript is compiled into JavaScript, so it can be run in any JavaScript environment (NodeJS, Browsers).

💡
Web Browsers do not natively understand TypeScript since browsers are built to run JavaScript, not TypeScript!

Before discussing further about TypeScript, let’s dive deep into the type of languages.

Types of Languages

Based on how programming languages handle datatypes and type enforcement, they can be categorized into two broad types:

  1. Loosely Typed Language: A loosely typed language allows implicit type conversions (coercion). This type of language is more flexible in handling different data types, often converting types automatically as needed.

    Characteristics:

    • Runtime Type Association - Values are related to data types at runtime. Therefore, type association is not done at the time of compilation but at execution time.

    • Dynamic Type Changes - Variables can be changed at the time of execution, providing more flexibility. Dynamic variable assignment and operations are provided, along with flexible type checking.

    • Runtime Error Detection - Error detection at runtime could result in unexpected behavior at certain points. This capability however grants more flexibility as a developer but demands more cautious use.

    • Examples of Loosely Typed Languages - JavaScript, PHP, Perl.

  2. Strongly Typed Language: A strongly typed language has strict type constraints. Variables and expressions are required to follow defined data types, and implicit type conversion (type coercion) is restricted or even not permitted.

    Characteristics:

    • Compile-Time Enforcement - The type of data a variable can hold is enforced strictly at compile time. That is, the compiler verifies and enforces that variables are handled in a manner consistent with their types at compile time.

    • Type Safety - The compiler enforces that operations are executed on compatible types only. Type-related errors are hence caught at the compilation time only.

    • Early Error Detection - Because of the type checking, errors are detected and fixed at the compile-time, offering early feedback to the developer. This aspect however hinders the liberty of a developer but is beneficial at the production level.

    • Examples of Strongly Typed Languages - Java, C++, TypeScript, OCaml

Why TypeScript?

JavaScript is loosely typed, meaning that variables may contain any kind of data. This makes it easy to get runtime errors that are difficult to debug. It can cause issues in production under unexpected circumstances, particularly when dealing with user input, database values, or API responses. Consider the following code example:

function authenticateUser (userInputPassword, originalPassword) {
    if (userInputPassword == originalPassword) {
        console.log("User Authenticated!");
        return true;
    }
    else {
        console.log("Access Denied!");
        return false;
    }
}

const isValidUser = authenticateUser(0, ""); // Output: User Authenticated!

This is due to the fact that 0 & "" (an empty string) both are falsy values and static comparison ( == ) provides such an error.

To avoid such problems, we require a statically typed language, which in this scenario is TypeScript, which is built on top of JavaScript by offering type safety. It gives developers the advantages of static typing without losing the flexibility and features of JavaScript. It has become popular in big applications and projects where code quality and error detection are important.

Execution of a TypeScript Code

TypeScript code doesn't run natively in browsers or JavaScript environments. Instead, it undergoes a compilation process to generate equivalent JavaScript code. Here's an overview of how TypeScript code is executed:

  1. Writing TypeScript Code:

    • Developers write TypeScript code using .ts or .tsx files, employing TypeScript's syntax with features like static typing, interfaces, and type annotations.
  2. TypeScript Compiler (tsc):

    • The TypeScript Compiler ( tsc ) is a command-line tool that processes TypeScript code.

    • Developers run tsc to initiate the compilation process, while transpiles the typescript code to javascript code.

  3. Compilation Process:

    • The TypeScript Compiler parses and analyzes the TypeScript code, checking for syntax errors and type-related issues.

    • It generates equivalent JavaScript code, typically in one or more .js or .jsx files.

  4. Generated JavaScript Code:

    • The output JavaScript code closely resembles the original TypeScript code but lacks TypeScript-specific constructs like type annotations.

    • TypeScript features that aren't present in JavaScript (e.g., interfaces) are often emitted in a way that doesn't affect runtime behavior.

  5. JavaScript Execution:

    • The generated JavaScript code can now be executed by any JavaScript runtime or browser.

    • Developers can include the resulting JavaScript files in HTML documents or use them in Node.js environments.

  6. Runtime Environment:

    • In the chosen runtime environment, the generated JavaScript code is interpreted or compiled by the JavaScript engine (e.g., V8 in Chrome, SpiderMonkey in Firefox).

    • Just-in-time (JIT) compilation or interpretation occurs to convert the code into machine code that the computer's processor can execute.

  7. Interacting with the DOM (Browser Environments):

    • In browser environments, the JavaScript code, generated from TypeScript, may interact with the Document Object Model (DOM) to manipulate web page structure and behavior.

Understanding the type safety of TS through an example

Now, let’s have a hands-on process of setting up a TypeScript Node.js Application locally on your machine.

Step - 1. Install TypeScript Globally:

To check whether you already have TypeScript already installed on your machine run the command:

tsc --version

If you already have one you can proceed to the next step.

In case you don’t have run the following command on the terminal of your machine:

npm install -g typescript

To confirm if it has been installed successfully check it using tsc —version command.

This walk through assumes you have node and node package manager pre-installed on your machine. In case you don’t have set that up first, to follow along with it.

Step - 2. Initialize a Node.js Project with TypeScript:

mkdir node-app && cd node-app
npm init -y
npx tsc --init

These commands create a new directory node-app , initializes a Node.js project inside it with default settings npm init -y , and lastly generates a tsconfig.json file using the npx tsc —init .

Step - 3. Create a TypeScript File ( prog.ts ):

let num: number = 18;
console.log(num);

Step - 4. Compile the TypeScript file into JavaScript:

tsc -b

The -b command tells TypeScript to build the project based on the configuration as mentioned as in tsconfig.json file. This generates a JavaScript file ( prog.js ) from the TypeScriot source ( prog.ts ).

Step - 5. Explore the generated JavaScript file ( prog.js ):

Run cat prog.js

"use strict";
const num = 1;
console.log(num);

Take a look that the transpiled js code lacks the strong type declaration unlike the prog.ts file.

Step - 6. Assign a string to a number in our program ( prog.ts ):

Run nano prog.ts to update the code in the terminal.

let num: number = 18;
num = "Ritochit";
console.log(num);

Step - 7. Try compiling again:

tsc -b

Upon compiling, TypeScript detects the type error and reports it in the console as:

Thus, this example illustrates one of the key benefits of TypeScirpt: catching type errors at the compilation time. By providing static typing TypeScript enhances code reliability and helps to identify potential issues before runtime. This is especially valuable in large codebases where early error detection can save time and prevent bugs.

Basic Types in TypeScript

In TypeScript, basic types serve as the building blocks for defining the data types of variables. Here's an overview of some fundamental types provided by TypeScript:

  1. Number:

    • Represents numeric values.

    • Example: let age: number = 20;

  2. String:

    • Represents textual data (sequences of characters).

    • Example: let name: string = "Ritochit";

  3. Boolean:

    • Represents true or false values.

    • Example: let isStudent: boolean = true;

  4. Null:

    • Represents the absence of a value.

    • Example: let myVar: null = null;

  5. Undefined:

    • Represents a variable that has been declared but not assigned a value.

    • Example: let myVar: undefined = undefined;

The tsconfig.json File in TypeScript

The tsconfig.json file in TypeScript is a configuration file that provides settings for the typescript compiler ( tsc ). It allows you to customize various aspects of the compilation process and define how the TypeScript code should be transpiled into javascript.

Here are some crucial options that you can change to handle the compilation process in the tsconfig.json:

1. Basic Compilation Options

These options control how TypeScript compiles your code.

  • target – Specifies the ECMAScript version the output should be compiled to.

      {
          "compilerOptions": {
              "target": "es6",
              // Other options...
          }
      }
    

    Example: es5, es6, es2020
    (ES6 and later provide modern syntax and better performance.)

  • module – Defines the module system to be used.

      {
          "compilerOptions": {
              "module": "CommonJS",
              // Other options...
          }
      }
    

    Example: CommonJS, ESNext, AMD
    (Use CommonJS for Node.js and ESNext for frontend apps.)

  • outDir – Specifies where the compiled JavaScript files should be placed.

      {
          "compilerOptions": {
              "outDir": "dist",
              // Other options...
          }
      }
    

    (Keeps the output clean by separating compiled files from source files.)

  • rootDir – Defines the root directory of your TypeScript files.

      {
          "compilerOptions": {
              "rootDir": "src",
              // Other options...
          }
      }
    

    (Ensures TypeScript keeps the project structure organized.)

2. Strict Type Checking Options

These options help catch errors early by enforcing stricter type rules.

  • strict – Enables all strict type-checking options.

      {
          "compilerOptions": {
              "strict": true,
              // Other options...
          }
      }
    

    (Best practice to catch potential issues early.)

  • noImplicitAny – Prevents variables from being implicitly typed as any.

      {
          "compilerOptions": {
              "noImplicitAny": true,
              // Other options...
          }
      }
    

    (Forces explicit type annotations, reducing runtime errors.)

  • strictNullChecks – Ensures variables are not null or undefined unless explicitly stated.

      {
          "compilerOptions": {
              "strictNullChecks": true,
              // Other options...
          }
      }
    

    (Prevents accidental usage of null values.)

3. Module Resolution Options

These options control how TypeScript resolves modules and imports.

  • moduleResolution – Determines how modules are resolved.

      {
          "compilerOptions": {
              "moduleResolution": "node",
              // Other options...
          }
      }
    

    (Use node for projects using npm packages.)

  • baseUrl – Sets a base directory for module resolution.

      {
          "compilerOptions": {
              "baseUrl": "src",
              // Other options...
          }
      }
    

    (Useful for avoiding long relative import paths.)

4. Output and Emit Options

These options control what gets emitted during compilation.

  • removeComments – Removes comments in the compiled JavaScript files.

      {
          "compilerOptions": {
              "removeComments": true,
              // Other options...
          }
      }
    

    (Reduces file size and improves performance.)

  • noEmit – Prevents TypeScript from generating JavaScript files.

      {
          "compilerOptions": {
              "noEmit": true,
              // Other options...
          }
      }
    

    (Useful when running TypeScript purely for type checking.)

Interfaces

In TypeScript, an interface is a way to define a contract for the shape of an object. It allows you to specify the expected properties, their types, and whether they are optional or required. Interfaces are powerful tools for enforcing a specific structure in your code.

Suppose you have an object representing a user:

const user = { 
    firstName: "Ritochit", 
    lastName: "Ghosh", 
    email: "random@gmail.com", 
    age: 20, 
};

To assign a type to the user object using an interface, you can create an interface named User:

interface User { 
    firstName: string; 
    lastName: string; 
    email: string; 
    age: number; 
}

Now, you can explicitly specify that the user object adheres to the User interface:

const user: User = { 
    firstName: "Ritochit", 
    lastName: "Ghosh", 
    email: "random@gmail.com", 
    age: 20, 
};

Types

In TypeScript, types allow you to aggregate data together in a manner very similar to interfaces. They provide a way to define the structure of an object, similar to how interfaces do. Here's an example:

type User = { 
    firstName: string; 
    lastName: string; 
    age: number; 
};

Features

  1. Unions: Unions allow you to define a type that can be one of several types. This is useful when dealing with values that could have different types. For instance, imagine you want to print the ID of a user, which can be either a number or a string:

     type StringOrNumber = string | number;
    
     const printId = (id: StringOrNumber) => { 
         console.log(ID: ${id}); 
     }
    
     printId(101); // ID: 101 
     printId("202"); // ID: 202
    
  2. Intersection: Intersections allow you to create a type that has every property of multiple types or interfaces. If you have types like Employee and Manager, and you want to create a TeamLead type that combines properties of both:

     type Employee = { 
         name: string; 
         startDate: Date; 
     };
    
     type Manager = { 
         name: string; 
         department: string; 
     };
    
     type TeamLead = Employee & Manager;
    
     const teamLead: TeamLead = { 
         name: "ritochit ghosh", 
         startDate: new Date(), 
         department: "student!" 
     };
    

Conclusion

Thus, we can conclude that TypeScript enhances JavaScript by adding static typing, making code more reliable & easier to maintain at the production level. It helps catch errors early, improves code organization, and offers better tooling support. Also, TypeScript provides the structure and reliability needed for efficient development.

This article covers in a much more detailed way different aspects of TypeScript, its need, how it helps to improve existing JavaScript, how it makes development at the production level easier, and how it can be incorporated with your prior knowledge of JavaScript.

Thank you for reading! I hope you found this article insightful and valuable. If you enjoyed it, feel free to share your thoughts and feedback in the comments.

Stay tuned for more of such content!

51
Subscribe to my newsletter

Read articles from Ritochit Ghosh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ritochit Ghosh
Ritochit Ghosh

I am a second-year student pursuing a BTech in CSE here at Maulana Abul Kalam University of Technology. I am a tech enthusiast and an Open-Source advocate, having an interest in web Development and AI/ML. Here I will share my tech journey and talk about different related topics.