---
title: "Working With TypeScript: A Practical Guide for Developers"
description: "TypeScript Practical Introduction"
authors:
  - name: "Vincenzo Chianese"
    url: "https://auth0.com/blog/authors/vincenzo-chianese/"
date: "Apr 27, 2021"
category: "Developers,Tutorial,TypeScript"
tags: ["typescript", "javascript", "ecmascript"]
url: "https://auth0.com/blog/working-with-typeScript-a-practical-introduction/"
---

# Working With TypeScript: A Practical Guide for Developers

## What is TypeScript

[TypeScript](https://www.typescriptlang.org/) is a popular JavaScript superset created by Microsoft that brings [a type system](https://en.wikipedia.org/wiki/Type_system#Type_checking) on top of all the flexibility and dynamic programming capabilities of JavaScript.

The language has been built as an open-source project, [licensed under the Apache License 2.0](https://github.com/Microsoft/TypeScript/blob/master/LICENSE.txt), has a very active and vibrant community, and has taken off significantly since its original inception.

## Installing TypeScript

To get started with TypeScript and try out all the examples, you can either install the TypeScript transpiler on your computer (more about this in the following paragraph), use the [official online playground](https://www.typescriptlang.org/play) or any other [online solution](https://stackblitz.com) you prefer.

In case you want to try the examples locally, you need to install the command-line transpiler, which runs on Node. First, you need to [install Node.js and npm](https://nodejs.org/en/download/current/) on your system. Then, you can create a Node.js project and install the TypeScript transpiler package:

```bash
# Create a new directory for your project
mkdir typescript-intro

# Make your project directory the current directory
cd typescript-intro

# Initialize a new Node.js project
npm init -y

# Install the TypeScript compiler
npm i typescript
```

This will install the `tsc` (TypeScript Compiler) command in the current project. To test the installation, create a TypeScript file called `index.ts` under your project directory with the following code:

```typescript
console.log(1);
```

Then, use the transpiler to transform it to JavaScript:

```bash
# transpiling index.ts to index.js
npx tsc index.ts
```

This will generate a new file called `index.js` with the exact same code of the TypeScript file. Use the `node` command to execute this new file:

```bash
# this will output 1
node index.js
```

Although the transpiler did nothing else besides creating a JavaScript file and copying the original code to it, these steps helped you validate that your TypeScript installation is in good shape and ready to handle the next steps.

> **Note:** TypeScript versions can have substantial differences even though they get released as **minor** revisions. It's common to bump into transpilation problems after a minor version update. For that reason, it is better to install TypeScript locally in your project and execute it using `npx` when needed _instead_ of relying on a global TypeScript installation.

## Defining a TypeScript Project

To define a TypeScript project within your Node.js project, you need to create a `tsconfig.json` file. The presence of this file in a directory denotes that the directory is the root of a TypeScript project.

`tsconfig.json` contains [a number TypeScript configuration options](https://www.typescriptlang.org/tsconfig) that change the transpiler's behavior, such as which files to check or ignore, the transpilation target, the imported types, among many others.

You can create the TypeScript configuration file easily by running the following command:

```bash
# create a default tsconfig.json file
npx tsc --init
```

The generated `tsconfig.json`  file contains almost all available options with a brief description of what they let you accomplish. Fortunately, most of these options have a good default value, so you can remove most of them from your file.

This blog post will spend some time talking about the compiler options later on. For now, let's focus on writing some code.

## TypeScript Features

The features of TypeScript are thoroughly explained in the [TypeScript handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html). However, this article will focus on a more practical approach to some of these features. It will also give light to some **features that are often left out from content you find on the internet**.

### Typing fundamentals

TypeScript's basic idea is to keep the dynamism and flexibility of JavaScript under control through the usage of types. Let's see this concept in action through a practical exercise.

Create a file called `test.js` under your project directory and populate it with the following code:

```javascript
const addOne = (age) => {
  return age + 1;
};

const age = "thirty two";

console.log(addOne(age));
console.log(addOne(20));
```

Execute that file as follows:

```bash
node test.js
```

1. What was the output of the program?
2. Do you think the output is correct?

It turns out that running it on Node.js, or on any browser for that matter, would output `thirty two1` without generating any warning. Nothing new here; it's just JavaScript behaving as flexible as always.

But, what if you want to guarantee that the `addOne()` function accepts only numbers when called? You could change the code to validate the parameters [`typeof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) during runtime, or you could use TypeScript to restrict that during compile time.

Head back to the `index.ts` file you created earlier and replace its content with the following:

```typescript
const addOne = (age: number): number => {
  return age + 1;
};

console.log(addOne(32));
console.log(addOne("thirty two"));
```

Note that you are now restricting the parameter `age` to only accept values of type `number` as valid.

Transpile the file again:

```bash
npx tsc index.ts
```

Using the TypeScript compiler to generate JavaScript produces the following error:

```bash
index.ts:6:20 - error TS2345: Argument of type 'string' is not
assignable to parameter of type 'number'.

6 console.log(addOne("thirty two"));
                     ~~~~~~~~~~~~

Found 1 error.
```

Defining types during application design can help you avoid mistakes like passing the wrong variable type to functions.

`string` and `number` are two of the [basic types that TypeScript supports](https://www.typescriptlang.org/docs/handbook/basic-types.html). Besides these, TypeScript supports all the [JavaScript primitive types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures), including `boolean` and `symbol`.

On top of these, TypeScript defines some types that do not map to anything in JavaScript directly but are very useful to represent some of the methodologies that are commonly used in the ecosystem:

* `enum` is a constrained set of values.
- `any` indicates that a variable/parameter can be anything, effectively skipping the type system.
- `unknown` is the type-safe counterpart of `any`.
- `void` indicates that a function won't return anything.
- `never` indicates that a function always throws an exception or never finishes its execution.
- Literal Types are concrete subtypes of `number`,`string`, or `boolean`. What this means is that "Hello World" is a  `string`, but a `string` is not "Hello World" inside the type system. The same goes with `false` in the case of booleans or `3` for a number:

```ts
declare function processNumber(s: 3 | 4); // This function won't accept a number, but only 3 or 4.
declare function processAnyNumber(n: number);

const n: number = 10;
const n2: 3 = 3;

processNumber(n); // Error: number is not 3 | 4
processAnyNumber(n2) // Works. 3 is still a number

```

### Aggregates

TypeScript supports aggregate types (maps, array, tuples), allowing a first-level of type composition:

#### Maps

Maps are commonly used to manage an association of keys to values and to represent domain application data:

```typescript
// Creates a map-like type
type User = {
  id: number,
  username: string,
  name: string
};

// Creates an instance of the user object
const user: User = {
  id: 1,
  username: "Superman",
  name: "Clark Kent",
};
```

#### Vectors

Vectors are a sequential and indexed data structure that has a fixed type for all its elements. While this is not a feature that JavaScript supports, TypeScript's type system allows developers to emulate this concept:

```typescript
// Creates a map-like type

type User = {
  id: number;
  username: string;
  name: string;
};

// Creates instances of the user object

const user1: User = {
  id: 1,
  username: "Superman",
  name: "Clark Kent",
};

const user2: User = {
  id: 2,
  username: "WonderWoman",
  name: "Diana Prince",
};

const user3: User = {
  id: 3,
  username: "Spiderman",
  name: "Peter Parker",
};

// Create a vector of users

const userVector: User[] = [user1, user2, user3];
```

#### Tuples

Tuples are also a sequential indexed data structure, but its elements' type can vary according to the fixed definition:

```typescript
// Creates a map-like type
type User = {
  id: number;
  username: string;
  name: string;
};

// Creates instances of the user object
const user1: User = {
  id: 1,
  username: "Superman",
  name: "Clark Kent",
};

// Create a user tuple

const userTuple: [User, number] = [user1, 10];
```

### Unions

Another way to compose types is through unions which are very handy when a function argument can have multiple types.

Suppose you want to write a function that will fetch the user's address details using either a `User` object or a `string` representing an email address.

First of all, let's install `node-fetch` in our project so that we can use the `fetch` function:

```bash
npm i node-fetch @types/node-fetch
```

…and then in the code, we can discriminate the two cases by type using the `typeof`operator:

```typescript
import fetch from 'node-fetch';

type User = {
	id: number,
	username: string,
	name: string
  email: string
};

async function fetchFromEmail(email: string) {
	const res = await fetch('https://jsonplaceholder.typicode.com/users');
	const parsed: User[] = await res.json();
  const user = parsed.find((u: User) => u.email === email);

  if (user)
	  return fetchFromId(user.id);
  return undefined;
}

function fetchFromId(id: number) {
	return fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
		.then(res => res.json())
		.then(user => user.address);
}

function getUserAddress(user: User | string) {
	if (typeof user === 'string')
		return fetchFromEmail(user);
	return fetchFromId(user.id);
}

getUserAddress("Rey.Padberg@karina.biz")
  .then(console.log)
  .catch(console.error)
```

The type system is smart enough that note that, according to the `if` result, the type under check is a string or not; this is an implicit type guard. Let's take a look at that.

As side note, tuples and unions play well together:

```typescript
const userTuple: Array<User | number> = [u, 10, 20, u, 30]; // Any item can be either an User or a number
```

It is also possible to specify both the size and the type of every element in the array:

```typescript
const userTuple: [User, number] = [u, 10, 20, u, 30]; // Error: the array must have a size of 2 and be an User and a number
const anotherUserTuple: [User, number] = [u, 10]; // Correct
```

### Type guards

Type guards are expressions that perform a runtime check whose result can be used by the type system to narrow the scope of the checked argument.

The `typeof` operator is a type guard; in the previous example, it has been used to narrow down the scope of the `user` argument.

There are other expressions that TypeScript treats as type guards, such as `instanceof`, `!==`  and  `in`; the documentation has the [complete list](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards)

To handle situations where the type system is not able to infer the specific type in the current scope, it is possible to define custom type guards through a predicate (a typed function returning a boolean):

```typescript
// Define a type guard for the user
function isUser(u: unknown): u is User {
    if (u && typeof u === 'object')
        return 'username' in u && 'currentToken' in u;
    return false;
}

function getUserAddress(user: User | string) {
	if (isUser(user))
    return fetchFromEmail(user);
	return fetchFromId(user.id);
}
```

User defined type guards are completely under the developer's control, and TypeScript has no way to verify their correctness.

A very common and legit use case for custom type guards is when validating external data against a JSON Schema through an external library, such as [Ajv](https://github.com/ajv-validator/ajv). This usually happens in web applications where the request body is typed as `unknown` (or `any`, depending on the framework you're using), and we want to type-check it before moving on with its processing:

```typescript
import Ajv from "ajv";
const ajv = new Ajv();

const validate = ajv.compile({
	type: 'object',
	properties: {
		username: { type: 'string' },
		currentToken: { type: 'string' }
	},
});

function validateUser(data: unknown): data is User {
	return validate(data);
}
```

This mechanism relies upon the developer's discipline of keeping the JSON Schema definition in sync with the type. In fact, if we modify the type but not the JSON Schema, we would get TypeScript narrowing a type to something that it's not.

 We'll see in a different section an alternative to keep these up to date automatically.

### Discriminated unions

Unions with a common literal field are called discriminated unions. When working with these, TypeScript is able to provide an implicit type guard, avoiding the burden of writing a custom one:

```typescript
type Member = {
	type: 'member',
	currentProject: string
};

type Admin = {
	type: 'admin',
	projects: string[]
};

type User = Member | Admin;

function getFirstProject(u: User) {
	if (u.type === 'member')
		return u.currentProject;
	return u.projects[0];
}
```

You can see in the `getFirstProject` function TypeScript can narrow the scope of the argument without having to write any predicate. Trying to access the `projects` array in the first branch or `currentProjects`in the second branch would result in a type error.

## Runtime Validations

We have briefly explained how, in case of custom-type guards, it is up to the developer to test and make sure that the returned result is correct.

In case of bugs in the predicate, the type system will have inaccurate information. Consider the following code snippet:

```typescript
function validateUser(data: unknown): data is User {
	return true;
}
```

The following predicate will always return true — effectively leading the type checker narrowing a type on something that it is not:

```typescript
const invalidUser = undefined;

if (validateUser(invalidUser)) {
	// The previous statement always returns true
	console.log(invalidUser.name); // Runtime error
}
```

TypeScript has a set of libraries that can help us to keep the runtime validation in sync with the associated type automatically, providing an implicit type guard we do not have to manage. A notable one is [runtypes](https://github.com/pelotom/runtypes), but in this article, we're going to take a look at [io-ts](https://github.com/gcanti/io-ts).

Essentially the deal is to define the shape of a type using io-ts included primitives; that defines a decoder we can use in our application to validate data we do not trust:

Once we have installed the required dependency

```bash
npm i io-ts
```

We can try the following code:

```typescript
import * as D from 'io-ts/Decoder';
import * as E from 'io-ts/Either';
import { pipe } from 'fp-ts/function';

// Define a decoder representing an user
const UserDecoder = D.type({
	id: D.number,
	username: D.string,
	name: D.string
	email: D.string
});

// Use the decoder on data we do not trust
pipe(
	UserDecoder.decode(data),
	E.fold(
		error => console.log(D.draw(error)),
		decodedData => {
			// decodedData's type is User
			console.log(decodedData.username)
		}
	)
);

```

### TypeScript Configuration

The transpiler's behavior can be configured through a `tsconfig.json` file that indicates the root of a project.

In particular, the file contains a series of key values controlling 3 main parts:

1. The project structure, such as what files to include and exclude from the transpiling process, what are the dependencies of the various TypeScript projects, and how these projects can refer through each other through aliases.
2. Type checker behavior, such as whether to check or not for `null`and `undefined` in the codebase, preserve the `const enums`, and so on
3. Runtime transpilation process.

#### TSConfig presets

TypeScript's transpiler can produce down to ES3 code and supports multiple module definitions (CommonJS, SystemJS).

The combination of the two is dependent on the runtime environment that you're using. For instance,  if you're targeting Node 10, you can comfortably transpile to `ES2015` and use `CommonJS` as for the module resolution strategy.

In case you're using a newer Node runtime, such as 14 or 15, then you can target `ESNext` or `ES2020`  and even dare to use the `ESNext` module strategy.

Finally, if you're targeting the browser and you're not using a module bundler such as `wepack` or `parcel`, you might want to use `UMD`.

Fortunately, the TypeScript team provides good presets that you can import in your own `tsconfig` file that handles most of these parameters for you. Using them is relatively straightforward:

```json
{
	"extends": "@tsconfig/node12/tsconfig.json",
	"include": ["src"]
}
```

#### Notable configuration options

* `declaration`: controls whether TypeScript should produce declaration files (`.d.ts`) with the transpilation. If your project is a library, it's good to enable this so that other developers using your code can benefit from the type checking. If the project is a deployable  artifact, such as a web application, you can set this to false
* `noEmitOnError`: controls whether TypeScript should abort the transpilation in case there is a type error. If set to false, the type erasure and the JavaScript production will continue anyway. Generally speaking, `true` is the value to use
* `removeComments`: true,
* `suppressImplicitAnyIndexErrors`: true,
* `strict`: controls a family of additional type checks. Unless there are some good reasons (such as legacy libraries that haven't been correctly migrated/typed), disabling this is not a good idea.
* `noEmitHelpers`: When necessary, TypeScript will emit functions and helpers to polyfills newer features that are not available in older standards, such as ES3 and ES5. If set to false, these helpers will be put at the beginning of your code; otherwise, they will be omitted, and you can install the `tslib` dependency separately.

## Conclusions

Unlike most of the other introduction articles on TypeScript, hopefully, this one gave you a different perspective on the capabilities that are often ignored in TypeScript.

While not perfect, TypeScript's type system is pretty powerful and, for the people interested in using it to the extreme — I highly recommend taking a look at [fp-ts](https://github.com/gcanti/fp-ts) and [io-ts](https://github.com/gcanti/io-ts).