Tech stack
·11 min read

What is TypeScript and Why You Should Use It For Your Next Project

If you’ve been a part of the JavaScript (JS) community and ecosystem at any point in the last few years then it’s likely you would have heard of TypeScript (TS). But, what is TypeScript? Why is there a lot of hype surrounding it in the community? And, what can it offer you as a developer and the projects you work on? These are all questions we’re going to cover in this post so without further ado let’s get started on our dive into TypeScript.

What is TypeScript

Firstly, let’s cover the basics, what is TypeScript? TypeScript is a superset of JavaScript that was developed and open-sourced by Microsoft in 2012, it’s primary feature is that it adds optional static typing to JavaScript, an otherwise quite loose and forgiving language.

Microsoft did this to help address the challenges and issues with building large-scale production applications in JavaScript as without this functionality it’s easy for bugs, errors, and issues to creep into the code and cause various issues for users. By using TypeScript, we’re able to catch a lot of these problems in the code before they enter production.

Outside of the static typing that TypeScript brings to JavaScript, it also brings some nice to have features and tooling like better code organization and enhanced error-checking capabilities that help make developing in TypeScript a much more pleasant experience for developers.

Why use Typescript in your next project

Now, we know more about the history of TypeScript, where it came from and the motives behind it; let’s take a deeper dive into some of the ways it can help you on your next project.

Enhanced code quality and maintainability

Thanks to the static typing that TypeScript adds, it makes it easier to understand the code that has been written. Now, instead of looking at a function and wondering what is being passed into it, you can quickly see what types the function accepts as an argument as well as what types it’ll return. This means new developers on a project can quickly get up to speed with the codebase and get familiar with the areas they’re working on.

Improved productivity

Because TypeScript understands the potential methods that can be called on any given data type, it can give us more intelligent autocompletion suggestions to help make us more efficient.

For example, if you open the autocomplete field in your IDE for a TypeScript project, the methods and options it suggests will be scoped to the data type you called it on. For example, call it on an array, and you’ll see things like .map(), .filter(), .sort(), and so on. But call it on a string, and you’ll see things like .toUpperCase(), .toLowerCase(), etc. Now, there’s no more wondering what you can and cannot call at any point in time which is especially helpful when working with SDKs, for example, as all of the methods you can call will be shown in the autocomplete.

Another nice perk of TypeScript is that you can navigate through the type definitions of a project inside your IDE, so if you wonder what the data structure returned by a function or an SDK method call is, you can inspect it in your IDE directly. This might seem a small addition, but it stops you from having to open a web browser and visit an external web page, taking you out of your IDE and current line of thinking in your code.

Error detection

Another benefit of the typings is that it gives you the ability to catch errors and issues as they happen in the code when you type it instead of waiting for when you run the application. This means we can avoid a lot of future bugs and issues for small things like calling the wrong method on a variable type, allowing us to develop faster.

For example, say you call an array method like .map() on a variable with the type of string[] | string | undefined; this means that the variable could be an array of strings, a single string, or it could be undefined. In this case, TypeScript will give you an error in your IDE to let you know that it could be undefined or a single string and that calling .map() on those values will produce an error, so you should handle the other non-array types before calling .map() on the variable.

Improved collaboration

TypeScript is great for solo projects, but it’s even better for projects that have multiple developers; this is because the code is self-documenting thanks to the types the code implicitly or explicitly receives. This means teams of developers can easily navigate an entire project and pick up where a previous developer left off relatively easily by just looking at and following the types and code.

Another reason TypeScript is great for collaboration on a codebase is that it can be paired with linting tools like ESLint to enforce coding standards and practices that every developer on the project needs to follow. This could be smaller things like no floating promises but also larger issues like the use of any types.

When to use TypeScript

Now we’re a bit more familiar with what TypeScript offers and how those offerings can help us, let’s talk about when we should use TypeScript. I’ll start by saying this question is quite an opinionated one; some TypeScript developers like myself believe that TypeScript has evolved to the point where it can be used for any project regardless of size. There are tools out there that help scaffold projects out in seconds with zero input on your part besides running the command making it painless to get up and running.

But, on the flip side of this coin are the developers who think TypeScript can be overkill for quick prototype projects where you’re just fleshing out an idea and don’t want to get bogged down in typing issues and errors. In truth, there isn’t a right or wrong answer. Whether you fall into the TypeScript all-the-time camp or the only in-production camp, it’s a matter of opinion and one you’ll need to experiment to find where you stand.

TypeScript syntax examples

With our bases and foundational knowledge of TypeScript covered, let’s dive into some actual code examples of TypeScript. Of course, we can’t cover every possible situation here but we’ll cover three of the core things you’ll be using in TypeScript; these are variables, functions, and objects.

Before jumping into the examples though it’s important to understand implicit and explicit types as they’ll come up a lot during your TypeScript journey.

  • Implicit is where TypeScript automatically works out the type based on the values being assigned to it.
  • Explicit is where we manually assign a type specifically to that variable.

Now, we know the difference, let’s jump into some examples.

Typescript variables

Variables are the foundation of any programming language and with TypeScript it’s no different; depending on the type of variable, the methods you can call with be different so it’s important they’re typed correctly.

// Implicit variables
 const string = 'hello world'; // string
 const number = 4; // number
 const myFunction = () => 'hello world'; // () => string (function returning a string)

// Explicit variables
 const stringOrUndefined: string | undefined = 'hello world'; // string or undefined
 const numberOrNull: number | null = 4; // number or null

TS functions

We briefly touched on functions in our variables example but let’s now take a bit of a closer look at them here. You can type both the arguments being passed in and the values being returned. But, while you can if you want to, you don’t always need to type the return value as it’ll be implicitly worked out for you most of the time. The arguments on the other hand will always need an explicit typing assigned to them.

// Implicit return of a string
function toUpperCase(arg: string) {
    return arg.toUpperCase(); // This will always be a string, as only a string can be provided
}

// Explicit return of a string
function toUpperCaseExplicit(arg: string): string {
    return arg.toUpperCase();
}

// Typing arguments provided as an object
function combineStrings({arg1, arg2}: {arg1: string, arg2: string}): string {
    return `${arg1} ${arg2}`;
}

TypeScript objects

We used an object in our previous example for functions, but let’s dive deeper into them here. Objects are a fundamental part of TypeScript, so it’s important we type them correctly. There are a couple of ways we can go about typing them in TypeScript, and one is the implicit route of using the properties defined on the object (which is more accurate and provides better auto-completion), and the other is using index types.

// Implicit types
const obj = {
	prop1: 'hello',
	prop2: 45,
	prop3: undefined
}

// The above object would have a type of
{
	prop1: string;
	prop2: number;
	prop3: undefined
}

This method of typing is the preferred route as you can now immediately see what properties are available on the object and the data types they’ll return. For example, you could use obj.prop1 and be confident it would be a string type. But, what about if the object is being returned from an API or it’s being programmatically generated so it could have one property or it could have a hundred properties?

You could type out every possible property but that’s both time-consuming and not guaranteed to be accurate so a quicker solution is to use an index type.

// Index types
const obj: {[property: string]: string | number | undefined} = {
	prop1: 'hello',
	prop2: 45,
	prop3: undefined
}

In this example, we’re saying the object will have a series of properties that will be strings, which will have values that are one (or multiple) of string, number, or undefined. This method of typing gives a lot less confidence because now we can’t be sure what type a value is, and you’ll need to handle all the possibilities in your code. But this method of typing is also very helpful if you don’t control the source of the object and can’t find an explicit type or make a type for it.

Stay on Top of New Tools, Frameworks, and More

Research shows that we learn better by doing. Dive into a monthly tutorial with the Optimized Dev Newsletter that helps you decide which new web dev tools are worth adding to your stack.

TypeScript features

With the basics of converting JavaScript syntax to TypeScript syntax covered, let’s move on to some of the more TypeScript-specific features and the syntax for those.

TypeScript type guards

One of the most compelling reasons to use TypeScript is the ability for TypeScript to warn you if you are attempting to do something that the type of variable doesn’t allow. This often leads to times when you must guard your statements to allow them to be performed with certainty. For example, if you have a variable that is either a string or an array of strings, you can’t just call .toUpperCase() on the variable because what if it’s an array? It’ll error.

So, let’s look at an example of how to do this safely in TypeScript.

function uppercase(value: string | string[]) {
	if (Array.isArray(value)) {
		return value.map(str => str.toUpperCase());
	}

	return value.toUpperCase();
}

uppercase('hello world'); // HELLO WORLD
uppercase(['hello','world']); // ['HELLO', 'WORLD']

In this example, we create a function that takes in a value that can be either a string or an array of strings; then inside the function, we handle both possibilities by using Array.isArray() to check if the value passed in is an array or not.

Some other ways you can type guard is using things like [instanceof], [typeof], or type predicates.

TypeScript interfaces

Interfaces are another way we can type objects and their values but instead of doing them inline, we can define them independently and then reuse them in multiple places. Let’s take a look at these by adapting our earlier example of object typing.

// Inline typing
const obj: {prop1: string; prop2: number; prop3: undefined} = {
	prop1: 'hello',
	prop2: 45,
	prop3: undefined
}

// Using an interface
interface ObjType {
	prop1: string; 
	prop2: number; 
	prop3: undefined;
}

const obj: ObjType = {
	prop1: 'hello',
	prop2: 45,
	prop3: undefined
}

const obj2: ObjType = {
	prop1: 'world',
	prop2: 90,
	prop3: undefined
}

As you can see by using an interface we’re able to cut down on the amount of typing we need to write and can reuse these types across multiple objects to save us having to type things multiple times. You can learn more about interfaces and the differences between them and types here.

Unions and Intersection Types

As an extension of interfaces, what happens if you want to join two of them together to create one larger type or if you want to specify that a value can be one of two types? Well, this is where type unions and intersections come into play.

Unions

By using a type union, we can specify that a value can be one of the multiple types, and it’s up to us to use a type guard to narrow that value down to the correct type and handle all of the possibilities. We’ve used type unions already in this tutorial, earlier when we talked about if a value can be either a string[] | string | undefined, this is a union and is denoted by the |. You can also combine these with interfaces and do something like this.

interface ObjOne {
	prop1: string; 
	prop2: number; 
	prop3: undefined;
}

interface ObjTwo {
	prop1: string; 
	prop2: number; 
	prop3: string[];
}

type ObjUnion = ObjOne | ObjTwo 

Now, if you used ObjUnion as the object type, you would need to narrow it down when using that object to determine which one of the interfaces in the union is the correct one to use for that object.

Intersections

If unions are what happens when we want to say this value can be this type OR that type, intersections are if we want to say this object is this type AND that type. We can use intersections to combine multiple types into one and they look like this.

interface ObjOne {
	prop1: string; 
	prop2: number; 
}

interface ObjTwo {
	prop3: string[];
}

type ObjOneAndTwo = ObjOne & ObjTwo;

/* 
ObjOneAndTwo is now...

{
	prop1: string; 
	prop2: number; 
	prop3: string[];
}

*/

TypeScript enums

Enums are one of the few features in TypeScript that aren’t a type-level extension of JavaScript, enums allow us to define a set of named constraints and use them throughout our code to ensure consistency. Let’s look at an example to help show this.

enum Direction {
	North = "NORTH",
	East = "EAST",
	South = "SOUTH",
	West = "WEST",
}

// ❗️ Notice we're using the enum as the type of the argument
function logDirection(direction: Direction) {
	console.log(direction)
}

logDirection(Direction.North); // ✅ This works correctly as we use the enum
logDirection('NORTH'); // ❌ This errors as we're not using the enum

As you can see in this example, we use the enum we defined as the type of the argument in the function which means only the values of that enum will be accepted into the function, any other values (even if they match the enum) will throw an error. This is why enums are great for consistency across a codebase as they force developers to use the same values and prevent them from passing in different ones that might cause an error.

TypeScript generics

Finally, the last feature of TypeScript we’re going to look at in this post, is generics. Generics are a more advanced feature of TypeScript but they are also one of the most helpful features of the language. Generics allow us to pass in a type dynamically to a function which means we can take a function that would otherwise have a single purpose and make it more reusable.

A great example of using Generics is functions that fetch data from an API. This is because TypeScript can’t infer the type of the data being returned from an API request. So, instead, it will default to something like unknown or any which isn’t particularly helpful for us developers, and to some extent, it negates the benefits of using TypeScript in the first place. But, this is where generics come in, we can convert a fetcher function to accept a generic and tell TypeScript that the returned value from the fetch function is the type we passed as the generic.

I realize that is a lot to take in and might be a bit confusing so let’s look at an example for it.

interface ObjOne {
	prop1: string; 
	prop2: number; 
}


// Defining our function with a generic type (T)
async function fetchData<T>() {
	const response = await fetch('API_URL');
	// 👇 Telling TS that the response data is the type of T (our generic)
	const data = await response.json() as T;

	return data;
}

await fetchData<string>(); // The returned data would be a string
await fetchData<number>(); // The returned data would be a number
await fetchData<ObjOne>(); // The returned data would have a type of ObjOne

TypeScript tooling

The TypeScript ecosystem is constantly expanding and new tools are always being developed and released for the community to use but to get you started on your journey into TypeScript and its tooling here are four of the most popular and well-known tools available.

  • tsc: A TypeScript compiler to convert TypeScript into JavaScript for browsers and Node.js to execute.
  • ts-node: If you don’t want to go the tsc route and would rather just run your TypeScript code directly then you can use ts-node, instead of normal Node.js to execute your code.
  • ESLint: ESLint is an incredibly popular tool for linting JavaScript code but it also supports TypeScript code (via the typeScript-eslint project) with its own set of TypeScript-specific rules. So, if you have a project that uses both JavaScript and TypeScript, this tool is perfect.
  • ts-jest: Want to add some Jest tests to your TypeScript code? ts-jest is the best option for that.

Getting started with TypeScript

There are many ways to get started with TypeScript and it depends on the type of developer you are. If you want to jump into building some projects and exploring some TypeScript code directly then here are a few project ideas for you to get started with.

But, if you would rather explore other available projects then there are plenty to pick from on places like GitHub. Or, if you don’t want to look at a project directly and instead want to explore TypeScript further you can look up online communities on websites like Reddit and Twitter.

Closing thoughts

And, that’s it, we’ve reached the end of this guide to TypeScript, we’ve looked at everything from the history of the language, to what it offers us as developers as well as some syntax examples. I hope I’ve persuaded you to give TypeScript a shot in your next project if you haven’t used it before. And, if you have used it before, then perhaps dive a little bit deeper into the functionality and features it offers us.

FAQs

Why use JavaScript vs TypeScript?

TypeScript vs. JavaScript is a common query among developers. TypeScript offers many benefits over JavaScript, but the primary one is the static typings it adds to the language. This addition gives us more certainty over the code we write and helps reduce the number of bugs and issues that are created.

Article written by

Coner Murphy

Fullstack web developer, freelancer, content creator, and indie hacker. Building SaaS products to profitability and creating content about tech & SaaS.

More posts
Coner Murphy profile picture.

Join the discussion

Hit your website goals

Websites success stories from the Prismic Community

How Arcadia is Telling a Consistent Brand Story

Read Case Study

How Evri Cut their Time to Ship

Read Case Study

How Pallyy Grew Daily Visitors from 500 to 10,000

Read Case Study