TypeScript Setup and Environment

Setting up a proper TypeScript development environment is crucial for a productive workflow. This guide will walk you through the process of installing TypeScript, configuring your development environment, and preparing the essential tools for TypeScript development.

Prerequisites

Before setting up TypeScript, ensure you have the following:

  1. Node.js and npm: TypeScript compiler runs on Node.js
  2. A code editor: Visual Studio Code is recommended for this course
  3. Basic familiarity with command-line operations

Installing TypeScript

A global installation allows you to use the TypeScript compiler tsc from any directory on your system. You can either install it through npm globally, or if you are using homebrew on a MacOS you can install it through homebrew.

After you've installed if, you can check the TypeScript version installed on your machine with the help of tsc --version.

You should see output similar to: Version 5.3.3 (or whatever version is currently installed).

using npm install

npm install -g typescript

using homebrew

brew install typescript

check ts version

tsc --version

For most projects, it's better to install TypeScript locally:

# Create a new directory for your project
mkdir my-typescript-project
cd my-typescript-project

# Initialize a new npm project
npm init -y

# Install TypeScript as a dev dependency
npm install --save-dev typescript

With a local installation, you can run the TypeScript compiler using npx:

npx tsc --version

This approach has several advantages:

  1. Version consistency: Everyone working on the project uses the same TypeScript version
  2. Project isolation: Different projects can use different TypeScript versions
  3. Better CI/CD integration: Continuous integration pipelines will automatically use the correct version

Setting Up Your First TypeScript Project

Let's set up a basic TypeScript project from scratch:

1. Create a Project Structure

To create a new TypeScript project we will need to have some familiarity with the terminal.

mkdir my-typescript-project
cd my-typescript-project
npm init -y
npm install --save-dev typescript

2. Initialize TypeScript Configuration

Create a TypeScript configuration file tsconfig.json:

npx tsc --init

This generates a tsconfig.json file with default settings and helpful comments.

3. Create a Simple TypeScript File

Create a source directory and your first TypeScript file:

mkdir src

Create a file at src/index.ts with the following content:

src/index.ts

function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("TypeScript"));

4. Compile Your TypeScript Code

Compile your TypeScript code to JavaScript:

npx tsc

By default, this will look for the tsconfig.json file and compile according to the settings defined there.

5. Run the Compiled JavaScript

node dist/index.js

You should see: Hello, TypeScript!

Configuring TypeScript with tsconfig.json

The tsconfig.json file controls the TypeScript compiler's behavior. Let's explore some important configuration options:

Essential Configuration Properties

Here's a basic tsconfig.json file with commonly used options:

basic tsconfig.json

{
  "compilerOptions": {
    "target": "es2016",           /* Set the JavaScript language version for emitted JavaScript */
    "module": "commonjs",         /* Specify what module code is generated */
    "outDir": "./dist",           /* Specify an output folder for all emitted files */
    "rootDir": "./src",           /* Specify the root folder within your source files */
    "strict": true,               /* Enable all strict type-checking options */
    "esModuleInterop": true,      /* Emit additional JavaScript to ease support for importing CommonJS modules */
    "skipLibCheck": true,         /* Skip type checking all .d.ts files */
    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports */
  },
  "include": ["src/**/*"],        /* Files to include in compilation */
  "exclude": ["node_modules", "**/*.spec.ts"] /* Files to exclude from compilation */
}

Commonly Used Compiler Options

Target JavaScript Version

The target option specifies which JavaScript version the TypeScript compiler should output:

"target": "es2016" // Options include: es3, es5, es6/es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022

Choose a target based on your deployment environment:

  • For modern browsers: es2016 or newer
  • For older browsers support: es5
  • For Node.js: Match your Node.js version (e.g., Node.js 14+ supports es2020)

Module System

The module option determines which module system the output JavaScript uses:

"module": "commonjs" // Options include: none, commonjs, amd, system, umd, es6/es2015, es2020, ESNext

Choose based on your runtime environment:

  • For Node.js: commonjs
  • For modern browsers or bundlers (webpack, Rollup): es2015 or ESNext

Type Checking Strictness

TypeScript offers various levels of type checking strictness:

"strict": true, // Enables all strict type checking options
// Or enable individual options:
"noImplicitAny": true,           // Raise error on expressions and declarations with an implied 'any' type
"strictNullChecks": true,        // Enable strict null checking
"strictFunctionTypes": true,     // Enable strict checking of function types
"strictPropertyInitialization": true, // Ensure non-undefined class properties are initialized in the constructor
"noImplicitThis": true,          // Raise error on 'this' expressions with an implied 'any' type
"alwaysStrict": true             // Parse in strict mode and emit "use strict" for each source file

For beginners, you might start with "strict": false and gradually enable stricter checks as you become more comfortable with TypeScript. But usually in React Web-App development we use the "strict": true so that we can create more robust webapps.

Source Maps

Source maps help with debugging by mapping compiled JavaScript back to the original TypeScript code:

"sourceMap": true // Generates corresponding .map file

Additional Important Options

Here are some more optional but important options you might want to include in your project:

"outDir": "./dist",         // Output directory for compiled files
"rootDir": "./src",         // Root directory of input files
"declaration": false,       // Generate .d.ts files
"removeComments": true,     // Remove comments in output files
"noEmitOnError": true,      // Do not emit outputs if any errors were reported
"esModuleInterop": true,    // Better interoperability with non-TypeScript modules
"resolveJsonModule": true,  // Allow importing .json files
"allowJs": true,            // Allow JavaScript files to be compiled
"checkJs": true             // Type check JavaScript files

Setting Up an IDE for TypeScript Development

While TypeScript works with any text editor, using an integrated development environment (IDE) with TypeScript support will significantly improve your productivity.

Visual Studio Code (Recommended)

Visual Studio Code (VS Code) is what we'll use in this TypeScript development course. Of course you can use other IDE's if you want.

1. TypeScript Support: VS Code has built-in TypeScript support, including:

  • Syntax highlighting
  • IntelliSense (code completion)
  • Error checking
  • Quick fixes and refactoring

2. Useful Extensions for TypeScript Development:

Setting Up a TypeScript Development Workflow

A robust TypeScript workflow typically includes the following components:

1. Compilation and Watching

For automatic recompilation when files change. This is what you'll usually use when developing with TypeScript, because it compiles the codebase whenever you save a file.

watch for code changes

# Watch mode
npx tsc --watch

To enhance it even further, you can add the following commans to your package.json:

package.json

...
"scripts": {
  "build": "tsc",
  "watch": "tsc --watch"
}
...

And then you can simply run it by typing the command npm run watch in your terminal.

Best Practices for TypeScript Project Setup

1. Structuring Your Project

In all the courses we will go through, a good project structure improves maintainability and is one of the most important things while creating a project.

Example project structure

my-typescript-project/
├─ src/
│  ├─ index.ts           # Application entry point
│  ├─ config/            # Configuration files
│  ├─ controllers/       # Request controllers (for APIs)
│  ├─ models/            # Data models
│  ├─ services/          # Business logic
│  ├─ utils/             # Utility functions
│  ├─ types/             # Type definitions
│  └─ __tests__/         # Test files
├─ dist/                 # Compiled output
├─ node_modules/         # Dependencies
├─ .eslintrc.js          # ESLint configuration
├─ .prettierrc           # Prettier configuration
├─ tsconfig.json         # TypeScript configuration
├─ package.json          # Project metadata and scripts
└─ README.md             # Project documentation

2. Managing Types

Keep your types organized:

  1. Place types close to their usage: For component-specific types, define them in the same file
  2. Create a dedicated types directory: For shared types across the application
  3. Use namespaces or prefixes: For related groups of types (e.g., Api.Response, User.Settings)
  4. Export types from a central location: Create barrel exports for easy imports

Example of a barrel file src/types/index.ts:

// Re-export all types from individual files
export * from "./user";
export * from "./auth";
export * from "./api";

3. Using Declaration Files

Declaration files \*.d.ts let you add type information to existing JavaScript libraries or define global types:

global.d.ts

global.d.tsx

// Add types to an existing module
import "express";

declare module "express" {
  interface Request {
    user?: {
      id: string;
      username: string;
    };
  }
}

module-augmentation.d.ts:

module-augmentation.d.ts

// Add types to an existing module
import "express";

declare module "express" {
  interface Request {
    user?: {
      id: string;
      username: string;
    };
  }
}

TypeScript Fundamentals Exercise

It's time for a simple exercise!

Overview

In this exercise, students will create a basic command-line inventory management system for a bookstore. This will help reinforce TypeScript fundamentals without requiring any frameworks or complex setup. Try to solve this without the solution collapsed.

Beginner TypeScript Exercise: Temperature Converter

Exercise Overview

In this exercise, you will create a very simple temperature conversion program that converts temperatures between Celsius and Fahrenheit. This exercise is designed to help you get comfortable with the most basic TypeScript types without any advanced features.

Learning Goals

  • Use basic TypeScript types (number, string, boolean)
  • Create simple functions with type annotations
  • Practice basic TypeScript syntax
  • Understand how to compile and run TypeScript code

Detailed Instructions

Step 1: Set Up Your Project

  1. Create a new folder named temperature-converter
  2. Open your command line and navigate to this folder
  3. Initialize a new npm project by running:
    npm init -y
    
  4. Install TypeScript by running:
    npm install typescript --save-dev
    
  5. Create a tsconfig.json file with very basic settings:
    {
      "compilerOptions": {
        "target": "es2016",
        "module": "commonjs",
        "outDir": "./dist",
        "strict": true
      }
    }
    

Step 2: Create Your TypeScript File

Create a new file called converter.ts in your project folder.

Step 3: Implement Temperature Conversion Functions

In converter.ts, implement the following:

  1. A function that converts Celsius to Fahrenheit
  2. A function that converts Fahrenheit to Celsius
  3. A function that displays the conversion result

Each function should have proper type annotations for parameters and return values.

Step 4: Add Test Cases

Add several test cases to try different temperature conversions.

Step 5: Compile and Run Your Code

  1. Compile your TypeScript code by running:
    npx tsc
    
  2. Run the compiled JavaScript file:
    node dist/converter.js
    

Complete Solution

/**
 * Converts a temperature from Celsius to Fahrenheit
 * @param celsius Temperature in Celsius
 * @returns Temperature in Fahrenheit
 */
function celsiusToFahrenheit(celsius: number): number {
  return (celsius * 9) / 5 + 32;
}

/**
 * Converts a temperature from Fahrenheit to Celsius
 * @param fahrenheit Temperature in Fahrenheit
 * @returns Temperature in Celsius
 */
function fahrenheitToCelsius(fahrenheit: number): number {
  return ((fahrenheit - 32) * 5) / 9;
}

/**
 * Displays the temperature conversion result
 * @param originalValue Original temperature value
 * @param convertedValue Converted temperature value
 * @param fromUnit Original temperature unit
 * @param toUnit Target temperature unit
 */
function displayConversion(
  originalValue: number,
  convertedValue: number,
  fromUnit: string,
  toUnit: string
): void {
  console.log(
    `${originalValue} degrees ${fromUnit} is equal to ${convertedValue.toFixed(1)} degrees ${toUnit}`
  );
}

// Test cases
function runTests(): void {
  console.log("Temperature Converter");
  console.log("--------------------");

  // Test case 1: Convert 0°C to Fahrenheit
  const freezingCelsius: number = 0;
  const freezingFahrenheit: number = celsiusToFahrenheit(freezingCelsius);
  displayConversion(freezingCelsius, freezingFahrenheit, "Celsius", "Fahrenheit");

  // Test case 2: Convert 100°C to Fahrenheit
  const boilingCelsius: number = 100;
  const boilingFahrenheit: number = celsiusToFahrenheit(boilingCelsius);
  displayConversion(boilingCelsius, boilingFahrenheit, "Celsius", "Fahrenheit");

  // Test case 3: Convert 32°F to Celsius
  const freezingPointF: number = 32;
  const freezingPointC: number = fahrenheitToCelsius(freezingPointF);
  displayConversion(freezingPointF, freezingPointC, "Fahrenheit", "Celsius");

  // Test case 4: Convert 98.6°F (body temperature) to Celsius
  const bodyTempF: number = 98.6;
  const bodyTempC: number = fahrenheitToCelsius(bodyTempF);
  displayConversion(bodyTempF, bodyTempC, "Fahrenheit", "Celsius");
}

// Run the tests
runTests();

Explanation

  1. Basic Types Used:

    • number: For temperature values
    • string: For unit names
    • void: For functions that don't return a value
  2. Function: celsiusToFahrenheit

    • Takes a number parameter (the temperature in Celsius)
    • Returns a number (the temperature in Fahrenheit)
    • Uses the formula: F = (C × 9/5) + 32
  3. Function: fahrenheitToCelsius

    • Takes a number parameter (the temperature in Fahrenheit)
    • Returns a number (the temperature in Celsius)
    • Uses the formula: C = (F - 32) × 5/9
  4. Function: displayConversion

    • Takes parameters for the original value, converted value, and unit names
    • Has a return type of void because it only logs to the console
    • Uses string interpolation to format the output
  5. Function: runTests

    • Creates various test cases for temperature conversions
    • Calls the conversion functions with different values
    • Displays the results using the displayConversion function

Expected Output

When you run this program, you should see output like:

Temperature Converter
--------------------
0 degrees Celsius is equal to 32.0 degrees Fahrenheit
100 degrees Celsius is equal to 212.0 degrees Fahrenheit
32 degrees Fahrenheit is equal to 0.0 degrees Celsius
98.6 degrees Fahrenheit is equal to 37.0 degrees Celsius

Next Steps for Learning

After completing this exercise, you might want to:

  1. Add input validation to check that temperature values are valid numbers
  2. Add support for the Kelvin temperature scale
  3. Create a simple command-line interface to accept user input

Advanced TypeScript Book Inventory System

1. Project Setup

1. Create project directory and initialize:

  • Create a new directory for the project named bookstore-inventory
  • Initialize a new npm project
mkdir bookstore-inventory
cd bookstore-inventory
npm init -y

2. Install TypeScript and required dependencies:

  • Install TypeScript locally to the project
npm install typescript --save-dev
npm install @types/node --save-dev

3. Create TypeScript configuration:

  • Create a tsconfig.json file in the root directory with the following contents:
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

4. Set up project structure:

  • Set up the project structure with a src folder
mkdir src

2. Creating the Data Types (src/types.ts)

Create a file called types.ts in the src folder with the following requirements:

1. Define a BookGenre type:

  • Should be a string literal type with exactly these options: "fiction", "non-fiction", "science", "fantasy"
  • Use a union type to define these options

2. Define a Book interface with these properties:

  • id string: A unique identifier for each book
  • title string: The title of the book
  • author string: The author's name
  • year number: The publication year
  • genre BookGenre: The genre, using the type defined above
  • price number: The price of the book (in dollars)
  • inStock boolean: Whether the book is currently in stock

3. Define an Invetory type:

  • hould represent an array of Book objects

3. Implementing Inventory Management (src/inventory.ts)

Create a file called inventory.ts in the src folder and implement these functions:

1. addBook(inventory: Inventory, book: Book): Inventory

  • Purpose: Add a new book to the inventory
  • Parameters: The current inventory and the book to add
  • Return: A new inventory array containing the added book
  • Validation: Check if a book with the same ID already exists
  • Note: Do not modify the original array; return a new array

2. removeBook(inventory: Inventory, id: string): Inventory

  • Purpose: Remove a book from the inventory by ID
  • Parameters: The current inventory and the ID of the book to remove
  • Return: A new inventory array without the removed book
  • Feedback: Log a message indicating success or failure
  • Note: Do not modify the original array; return a new array

3. searchBooks(inventory: Inventory, searchTerm: string, searchBy: "title" | "author" | "genre"): Book[]

  • Purpose: Search for books by title, author, or genre
  • Parameters: The inventory, search term, and field to search by
  • Return: An array of books that match the search criteria
  • Note: Make the search case-insensitive

4. calculateTotalValue(inventory: Inventory): number

  • Purpose: Calculate the total value of all books in the inventory
  • Parameter: The inventory
  • Return: The sum of prices for all books that are in stock
  • Note: Only include books where inStock is true

5. getBooksByGenre(inventory: Inventory, genre: BookGenre): Book[]

  • Purpose: Get all books of a specific genre
  • Parameters: The inventory and the genre to filter by
  • Return: An array of books that match the specified genre

6. displayBook(book: Book): void

  • Purpose: Display a single book's information
  • Parameter: The book to display
  • Output: Format and log all book details to the console

7. displayInventory(inventory: Inventory): void (extra helper function)

  • Purpose: Display all books in the inventory
  • Parameter: The inventory
  • Output: Format and log all books, plus the total inventory value

4. Creating the Main Application (src/index.ts)

Create a file called index.ts in the src folder with the following:

  1. Imports all the types and functions from the other files
import { Book, Inventory, BookGenre } from "./types";
import { addBook, removeBook /* ... other functions */ } from "./inventory";
  1. Creates an initial inventory:
    • Add at least 5 different books with different properties
    • Make sure to include books from each genre
    • Include at least one book that is not in stock
  2. Implements a menu system:
    • Display a menu of options for the user

Options should include:

  • View all books
  • Add a new book
  • Remove a book
  • Search for books
  • View books by genre
  • Calculate total inventory value
  • Exit the application
  1. Implement simulated user input functions:
  • For this exercise, you can simulate user input
  • Create functions like promptForNewBook(), promptForBookId(), etc.
  • Each function should return appropriately typed data

5. Building and Running the Application

  1. Add compile and run scripts to the package.json file
"scripts": {
  "build": "tsc",
  "start": "node dist/index.js",
  "dev": "tsc && node dist/index.js"
}
  1. Compile the TypeScript code to JavaScript
npm run deb
  1. Testing requirements:
    • Verify that all functions work as expected
    • Test adding a book with a duplicate ID
    • Test removing a non-existent book
    • Test searching with different criteria
    • Test calculating inventory value with some books out of stock

Conclusion

Setting up a proper TypeScript environment is an investment that pays dividends throughout the development process. A well-configured project with appropriate tooling will catch errors early, improve code quality, and enhance the development experience for all team members. As you become more comfortable with TypeScript, you can further customize your setup to match your specific needs and preferences. The configuration described in this guide provides a solid foundation from which you can build increasingly sophisticated applications with confidence. In the next section, we'll explore the key differences between TypeScript and JavaScript to deepen your understanding of how TypeScript enhances the JavaScript language.