DEV Community

Cover image for TypeScript Basics
Sammy Abukmeil
Sammy Abukmeil

Posted on • Updated on

TypeScript Basics

Table of Contents





What is TypeScript

  • TypeScript extends JavaScript by adding "types" to the language
  • It provides a command line interface (cli) which we can run to compile our TypeScript to JavaScript via the cli command tsc
  • This helps spot bugs during development (i.e. when we're building our apps) and therefore prevent bugs from happening during "runtime" (when a user is using our apps)

For example, here's some TypeScript:

let firstName: string = "Sammy";
Enter fullscreen mode Exit fullscreen mode

The : string is TypeScript syntax which ensures that this variable will only ever contain a value of type string

If another developer attempts to assign a value to this variable of type number, TypeScript will throw an error in VS Code and won't compile via the tsc CLI


Setup a Local Environment

This section guides you through how to install and compile TypeScript code.

Create a folder to practice TypeScript

On your desktop create a typescript directory:

  • Open it in VS Code
  • Open a terminal

Setup a package.json file

npm init -y
Enter fullscreen mode Exit fullscreen mode

Install TypeScript as a dev dependency

We use TypeScript during development (not to actually run the app), so we'll add the --save-dev flag to indicate that it's a dependency for development

npm install typescript --save-dev
Enter fullscreen mode Exit fullscreen mode

We should now be able to run the cli

npx tsc
Enter fullscreen mode Exit fullscreen mode

Which should output something like Version 5.3.3

Create a tsconfig.json file

We can control how the TypeScript compiler behaves via a JSON file, let's create it

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

This will generate a tsconfig.json file with some default settings

Let's add a setting to tell the TypeScript compiler where to find our TypeScript files and where to send our compiled JavaScript files

{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "./build",
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Now create an src/ directory and inside of it create a index.ts

Add

let firstName: string = "Sammy";
Enter fullscreen mode Exit fullscreen mode

inside the index.ts and run

npx tsc
Enter fullscreen mode Exit fullscreen mode

You should now see a build directory with an index.js file inside.

Let's run

npx tsc --watch
Enter fullscreen mode Exit fullscreen mode

Which will watch for changes in the TypesScript file and automatically transpile it to index.js


Vanilla TypeScript

Here are a few of the core features of TypeScript.

For a more detailed breakdown, take a look at the TypeScript Documentation.


Typing Variables

When creating variables, we can either provide an explicit type assignment:

let firstName: string = "Sammy";
Enter fullscreen mode Exit fullscreen mode

In the code above, we're creating a variable and explicity telling TypeScript (via : string) that it must be a string.

TS Playground - Variables

  • There will be links to TS Playground for each section in this article going forward
  • It compiles TypeScript to JavaScript and shows the compiled code on the right
  • Click "Run" to execute the JavaScript and see any console logs

This makes your code easier to read/understand, and is more explicit.

You can also create a variable without a type and TypeScript will infer (i.e. guess) the type, based on the value:

let surname = "Abukmeil";
Enter fullscreen mode Exit fullscreen mode

This is called an implicit type assignment.

You can hover over a variable in VS Code to check what type TypeScript inferred:

VS Code type popup

If you try to assign a value with the wrong type to a typed variable, TypeScript will highlight an error in VS Code (squiggly red line) and the TypeScript compiler will throw an error:

Type 'number' is not assignable to type 'string'

TS Playground - Incorrect Type Error

In TS Playground, you'll see the errors in two places:

  1. In the panel on the left by hovering over the red squiggly line
  2. In the panel on the right by switching to the "Errors" tab

This happens with both explicity typed variables and implicity typed variables.


Typing Arrays

When creating arrays, we can add a type like so

const games: string[] = ["Space Invaders", "Pac Man"];
Enter fullscreen mode Exit fullscreen mode

The : string[] above says "this array can only contain strings"

TS Playground - Arrays

If we try to do the following

games.push(4);
Enter fullscreen mode Exit fullscreen mode

We get the following error

Argument of type 'number' is not assignable to parameter of type 'string'


Tuples

A tuple is an array with a specific length & specific types for each index

let inventoryItem: [number, string, number] = [
  1, 
  "Gaming Monitor", 
  100
];
Enter fullscreen mode Exit fullscreen mode

The : [number, string, number] above says "This array must have 3 items with the following types"

TS Playground - Tuples

So if we were to re-assign this variable, this is allowed:

inventoryItem = [2, "Microwave", 40];
Enter fullscreen mode Exit fullscreen mode

Whereas this isn't (since the last element in the array should be a number):

inventoryItem = [2, "Toaster", "35"];
Enter fullscreen mode Exit fullscreen mode

We get the following error Type 'string' is not assignable to type 'number'

When creating tuples, you can also give a name to each array item for clarity

let inventoryItem: [id: number, name: string, price: number] = [
  1,
  "Gaming Monitor",
  100,
];
Enter fullscreen mode Exit fullscreen mode

Typing Objects

When creating objects, we can add a type like so

const game: { name: string; releaseYear: number } = {
  name: "Resident Evil",
  releaseYear: 1996,
};
Enter fullscreen mode Exit fullscreen mode

where : { name: string; releaseYear: number } specifies the name and type for each property

TS Playground Example - Typing an object


Enums

An enum is a group of constants (i.e. variables that should never change).

For example

enum Directions {
  North,
  East,
  South,
  West
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we've defined some directions that we'll use in our app.

let currentDirection = Directions.North;
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Enum

In the code above, we're creating a variable and setting it equal to one of our enum values.

If we console.log() this variable, we'll get 0

console.log(currentDirection); // 0
Enter fullscreen mode Exit fullscreen mode

So each value in our enum is given a number starting from 0.

Now that variable can only ever be assigned a value from this enum.

If we try to set the variable to some other value

currentDirection = "Something Incorrect";
Enter fullscreen mode Exit fullscreen mode

We'll get the following error Type '"Something Incorrect"' is not assignable to type 'Directions'

We can also change the numbers that the enum values are linked to

enum StatusCodes {
  Success = 200,
  NotFound = 404,
  ServerError = 500,
}

const currentStatus = StatusCodes.NotFound;

console.log(currentStatus); // 404
Enter fullscreen mode Exit fullscreen mode

Or change the numbers that the enum values are linked to to be strings instead

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

const currentDirection = Direction.Up;

console.log(currentDirection); // UP
Enter fullscreen mode Exit fullscreen mode

Type Aliases

We can define a Type Alias which makes types reusable and gives them a name.

type GameReleaseYear = number;

const pacmanYear: GameReleaseYear = 1980;
const spaceInvadersYear: GameReleaseYear = 1978;
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Type Alias

In the code above, we created a Type Alias called GameReleaseYear which is a number.

This Type Alias is used for multiple variables.

We can define a Type Alias for any type, including objects:

type Game = {
  title: string;
  year: number;
};

const game: Game = {
  title: "Pac Man",
  year: 1980
}
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Type Alias for an object


Interfaces

An interface is similar to a type alias, however, you can only use them for objects

interface Game {
  title: string;
  year: number;
}

const game: Game = {
  title: "Pac Man",
  year: 1980,
};
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Interface

Interfaces have an additional feature compared to Type Aliases, that they can extend other interfaces:

interface Game {
  title: string;
  year: number;
}

interface IndieGame extends Game {
  developer: string
}

const indieGame: IndieGame = {
  title: "Stardew Valley",
  year: 2016,
  developer: "ConcernedApe"
};
Enter fullscreen mode Exit fullscreen mode

Union Types

A union type allows a value to be more than one type

function displayId(id: number | string) {
  console.log(`ID is: ${id}`);
}

displayId(5);
displayId("ff72ce73-322d-4eb2-8029-c739d30c3ef5");
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Union Type

In the code above, the id property could either be a string or a number


Typing Functions

We can specify what the return value type will be:

function getUserId(): number {
  return 5;
}
Enter fullscreen mode Exit fullscreen mode

The : number above says "this function will return a value of type number"

We can add types for each of the parameters:

function sum(x: number, y: number): number {
  return x + y;
}
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Typing Functions

In the code above, we've added types for the parameters and the return type via : number

We can use a type alias to define the function types separately:

type Multiply = (x: number, y: number) => number; 

const multiply: Multiply = (x, y) => {
  return x * y;
}
Enter fullscreen mode Exit fullscreen mode

Typing Classes

When creating classes, we can add types for the properties and the methods

class Car {
  engineType: string;

  constructor(engineType: string) {
    this.engineType = engineType;
  }

  getEngineType(): string {
    return this.engineType;
  }
} 

const car = new Car("Six cylinders");
Enter fullscreen mode Exit fullscreen mode

In the code above, we specify that the class has a property called engineType which is a string, and that the getEngineType() method returns a string

We can also use private and public keywords to specify if certain properties / methods are accessible outside of the class

class Car {
  private engineType: string;

  constructor(engineType: string) {
    this.engineType = engineType;
  }

  public getEngineType(): string {
    return this.engineType;
  }
}

const car = new Car("Six cylinders");
console.log(car.getEngineType());
console.log(car.engineType); // Error
Enter fullscreen mode Exit fullscreen mode

In the code above, the engineType property is set private and therefore only accessible within the class, whereas the getEngineType() method is public and can be accessed outside of the class.

Instead of typing private engineType: string; outside of the constructor and engineType: string which is repetitive, we can combine both into the constructor:

class Car {
  public constructor(private engineType: string) {
    this.engineType = engineType;
  }

  public getEngineType(): string {
    return this.engineType;
  }
}

const car = new Car("Six cylinders");
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Typing Classes

In the above code private engineType: string defines a property and constructs it.


Generics

Generic Functions

Generics allow your typed functions to accept any type

function getValue<Type>(value: Type): Type {
  return value;
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we define a generic function which can receive a parameter of any type, and return a value of any type.

const num = getValue<number>(10);
Enter fullscreen mode Exit fullscreen mode

In the code above, we're using the generic function, giving a type of number

const str = getValue<string>("Hello");
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Generic Function

In the code above, we're using the same generic function, giving a type of string

Here's another example

function reverse<Type>(array: Type[]): Type[] {
  return array.reverse();
}

const reversedNumbers = reverse<number>([1, 2, 3]);
const reversedStrings = reverse<string>(["Hello", "Bye"]);
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Generic Function Example 2

In the code above, the parameter array is typed as Type[] i.e. an array of whichever type was declared. The return value is also typed as Type[] i.e. it will return an array of the declared type

Generic Type Aliases

When creating a type alias (See section "Type Aliases" above for a refresher), we can make it generic

type Person<Type> = {
  name: string;
  age: number;
  job: Type;
};
Enter fullscreen mode Exit fullscreen mode

In the code above, we've defined a generic type, where the job property could be any type

const personOne: Person<boolean> = {
  name: "Jane",
  age: 21,
  job: false,
};
Enter fullscreen mode Exit fullscreen mode

In the code above, we're using our generic type alias and specifying that the job property will be a boolean

const personTwo: Person<string> = {
  name: "John",
  age: 35,
  job: "Doctor",
};
Enter fullscreen mode Exit fullscreen mode

TS Playground Example - Generic Type Alias

In the code above, we've used the same generic type alias, this time job will be a string


TypeScript With React

Here are a few way of using TypeScript in React

For a more detailed breakdown, take a look at the React Documentation.


Creating a Vite app

From this tutorial, we'll use Vite.

We can create a new Vite app with TypeScript by running the following command

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Choose the following options:

  • Select a framework -> React
  • Select a variant -> TypeScript

Note: Another common approach is to use Next JS

File Extension

Ensure your file extensions are .tsx (it wont work with .js or .jsx)

Typing Props

Let's start with an example:

// App.tsx
function App() {
  const handleTodo = (text: string) => {
    console.log(text);
  }

  return (
    <>
      <NewTodo handleTodo={handleTodo} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
// NewTodo.tsx
interface Props {
  handleTodo: (text: string) => void;
}

function NewTodo({ handleTodo }: Props) {
  return (
    <>
      <button onClick={() => handleTodo("Hello")}>Click</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here we've used an interface to define a type for the props object.

In this case, there's only one key value pair:

  • Key: handleTodo - the name of the prop
  • Value: (text: string) => void - This prop is a function, so this type describes the function parameters and return type (void means the function doesn't return anything)

Typing State

Let's start with an example:

interface Todo {
  id: string;
  text: string;
}

function App() {
  const [todos, setTodos] = useState<Todo[]>([]);

  return (
    <>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
      <button
        onClick={() => setTodos([...todos, { id: uuid(), text: "Hello" }])}
      >
        Add todo
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here we've used an interface to define a type for the state variable.

Todo[] means this state variable will contain an array of Todo type objects

useState() can be used as a Generic Function (as explained here) hence the following syntax useState<Todo[]>([])


TypeScript With Express

Here are a few way of using TypeScript in Express


Setting up an Express TypeScript app

Create an express app as usual:

npm init -y
Enter fullscreen mode Exit fullscreen mode
npm i express
Enter fullscreen mode Exit fullscreen mode

Install TypeScript as a dev dependancy, as well as some declaration packages for node and express

npm i -D typescript @types/express @types/node
Enter fullscreen mode Exit fullscreen mode

These declaration packages (e.g. @types/express) can be found in the DefinitelyTyped GitHub Repo, which is a large collection of TypeScript type definitions.

For example, here are the type definitions for Express JS: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/express

Running npm i -D @types/express brings these into your node_modules/ directory and makes them usable in your application.

Create a tsconfig.json file

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

and ensure you set a directory for your compiled JS files to be sent to

{
  "compilerOptions": {
    ...
    "outDir": "./dist"
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

This is how we'll run our app:

  • In development -> we want node to run our .ts files
  • In production -> we want node to run our comipled .js

Since node can't run .ts files natively, we need to install a library for executing TypeScript via node called ts-node

npm i -D ts-node
Enter fullscreen mode Exit fullscreen mode

And to watch for file changes in development, we'll use nodemon

npm i -D nodemon
Enter fullscreen mode Exit fullscreen mode

And setup some scripts to run our app

{
  "scripts": {
    "build": "npx tsc",
    "start": "node dist/index.js",
    "dev": "nodemon src/index.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Using Types in Express

Create a src directory and a index.ts file inside it with the following:

import express, { Express, Request, Response } from "express";

const app: Express = express();

app.get("/", (req: Request, res: Response) => {
  res.send("Express + TypeScript Server");
});

app.listen(3000, () => {
  console.log("Server is running on port 3000");
});
Enter fullscreen mode Exit fullscreen mode

Here we're importing types for:

  • Express -> Type for the return value from express()
  • Request -> Type for the req parameter
  • Response -> Type for the res parameter

These are imported from the express package, which will search from them in @types/express

Top comments (0)