TypeScript is a general-purpose programming language used across various development environments. Over the years since its release, it has gained much support from developers and companies, evidenced by GitHub ranking it as its fourth most used programming language of 2022. TypeScript’s prominence has increased since then.
Additionally, its popularity is reflected in the abundance of job opportunities, with over 20,000 TypeScript-related positions advertised on LinkedIn and major industry players, including Trivago and Stripe, opting to transition their technology stacks to TypeScript.
In this article, we will explore TypeScript from the ground up to understand its benefits, which have prompted its ever-growing usage in the developer community.
#TypeScript vs. JavaScript
TypeScript is not entirely new; it's a superset of JavaScript that tackles limitations within JavaScript itself. As a superset, TypeScript extends JavaScript by adding static typing.
This static typing can be declared in two common ways:
1. Type annotations during declaration
This involves immediately specifying the variables and their type when the variable is first declared. Like so:
let name: string = "Hygraph
Where name
is the variable name and string
is the type.
2. Type aliases
This method involves defining a type separately using the type
keyword and later using it to annotate variables.
type StringType = {name: string;};let name: StringType = { name: "Hygraph" };
More about this later in the article.
Read this article to learn more about the differences and similarities between TypeScript and JavaScript.
Now, let us look at some features that TypeScript offers.
#Top TypeScript features
Static typing
Static typing refers to when variables are explicitly declared with their data types (like integer, string, boolean) at compile time, and the system performs type checking before the program runs.
While the behavior below is allowed in JavaScript, doing the same in TypeScript would cause an error because of TypeScript’s type enforcement.
let name: string = "hygraph";name = 2000
Check out this article about abstract typing.
Optional typing
While TypeScript allows explicit variable declaration, it also supports writing JavaScript-like code without declaring the types; TypeScript will infer the type out of the box. E.g.,
let name = "Hygraph";
In the above, TypeScript will infer that the type name is a string
through the variable initialization value, i.e., “Hygraph.”
TypeScript generics
By parameterizing types and functions, TypeScript generics create reusable components and functions that can work with various types without compromising the type safety:
// Generic function to return the length of an arrayfunction getArrayLength<T>(array: T[]): number {return array.length;}// Using the generic function with different types of arraysconst stringArray: string[] = ["apple", "banana", "orange"];const numberArray: number[] = [1, 2, 3, 4, 5];console.log(getArrayLength(stringArray)); // Output: 3console.log(getArrayLength(numberArray)); // Output: 5
Generics are generally useful for defining custom data types that must work with various data types, such as trees, graphs, and queues.
#Advanced type system
TypeScript's type system goes beyond basic static typing. It also provides features for defining complex types, manipulating types, establishing relationships between variables, enforcing type constraints, and other functionalities, which allows for developing error-free applications.
Let us consider some of the most commonly used advanced types.
1. Union types
Union in TypeScript refers to declaring a variable or function argument holding numerous data types. This can be beneficial when a value has different data types at runtime.
Union types are represented with the |
symbol, which separates the data types. E.g.:
let age = number | string;age = 10;age = "ten";
As seen above, we could specify that the age
variable could possess two data types. This provides flexibility without endangering type safety.
2. Intersection types
Intersections allow the creation of a new type by combining multiple existing types into one. This new type has the properties and functionalities of the combined types.
Intersections are created by using the &
symbol between the types to be combined. E.g.:
interface User {id: number;username: string;email: string;}interface Admin {isAdmin: boolean;}// A new type using intersection typetype UserAndAdmin = User & Admin;
Here, the type User
AndAdmin
combines the properties of both User
and Admin
interfaces to produce a type that requires all the properties from both.
3. Conditional types
Conditional types in TypeScript create types that depend on a state, which allows for the definition of dynamic types that change based on the properties of other types, values, or contexts.
Let us consider a basic example:
// A conditional type to determine if a type is an arraytype IsArray<T> = T extends any[] ? true : false;// Test the conditional type with different typestype Result1 = IsArray<number>; // falsetype Result2 = IsArray<string[]>; // truetype Result3 = IsArray<boolean | []>; // true
Conditional types are defined using the ternary operator (? :
) syntax within the angle brackets (<>
) known as “generic type parameter”- more on this later in the article. They also use the extends
keyword, which checks if a type meets a certain condition and produces a different type based on the result of that condition.
4. Mappedn types
Mapped types in TypeScript allow the creation of new types by transforming the properties of existing types. They do this by iterating over the properties of a source type and applying a transformation to each property to generate a new type. E.g.:
// type representing a usertype User = {id: number;username: string;email: string;};// Mapped type to make all properties optionaltype OptionalUser = { [P in keyof User]?: User[P] };// New type using the mapped typeconst optionalUser: OptionalUser = { id: 1 };// Property 'email' is now optionaloptionalUser.username = "john_doe";// Property 'id' is still required// optionalUser.email = "john@example.com"; // Error: Property 'email' is missing
Mapped types are defined using { [P in keyof Type]: NewType }
syntax, where Type
is the source type, P
is the property key, and NewType
is the transformed type. P
iterates over the property keys, and transformation defines the change.
5. Type aliases
Type aliases allow the creation of custom names (aliases) for existing TypeScript types, including primitives, union types, intersection types, and even more complex types like object literals and function types. Type aliases are defined using the type
keyword followed by the new name, as shown below:
// type alias for a union typetype Result = "success" | "error";// type alias for an object literaltype Point = { x: number; y: number };// type alias for a function typetype Greeting = (name: string) => string;// Using the type aliasesconst status: Result = "success";const origin: Point = { x: 0, y: 0 };const greet: Greeting = (name) => Hello, ${name}!;
The code above shows different use cases of type aliases for different types in TypeScript and their usage after declaration.
#TypeScript in Object-Oriented Programming (OOP)
OOP is a paradigm based on the concept of "objects" interacting to create maintainable and reusable code.
1. TypeScript classes
Classes are templates or blueprints for creating objects i.e., they define the data (properties) and methods (functions).
Here is an example of how a class implementation will look in TypeScript:
class Organization {private name: string;private yearFounded: number;constructor(name: string, yearFounded: number) {this.name = name;this.yearFounded = yearFounded;}public getDetails(): string {return `${this.name} was founded in ${this.yearFounded}.`;}}let organization = new Organization("Hygraph", 2015);console.log(organization.name); // Error: Property 'name' is private and only accessible within class 'Organization'.console.log(organization.getDetails()); // Output: Hygraph was founded in 2015
In the code above, we created a class Organization
with private properties name
and yearFounded
, which can only be accessed and modified within the “Organization” class. Notice how we typed the properties and methods.
2. TypeScript interface
Interfaces describe the shape of objects by listing the properties and methods they should have without providing any implementation details:
// Interface representing form datainterface FormData {firstName: string;lastName: string;email: string;age: number;}// Usagelet formData: FormData = {firstName: "John",lastName: "Doe",email: "john.doe@example.com",age: 30};
In the example above, we defined an interface FormData
representing the structure of form data.
Next, we created an object formData
with properties corresponding to the interface definition.
TypeScript enums
Enumerate types, represented with enum
in TypeScript, are a set of named constants enclosed in curly braces {}
, where each constant has an associated numeric or string value.
A typical TypeScript enum set could look like this:
// enum for days of the weekenum DayOfWeek {Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday}enum HTTPStatusCodes {OK = 200,BadRequest = 400,NotFound = 404,}
The code block above immediately explains what is happening, showcasing the semantic meaning-adding advantage of using TypeScript enums.
#TypeScript and ES6
TypeScript supports many ECMAScript 6 (ES6) features, also known as ECMAScript 2015. Some of these features include:
1) Arrow functions: Unlike traditional function expressions, arrow functions provide a straightforward syntax for defining functions. They offer an implicit return for single-line expressions and automatic binding of this
and can be useful for callback functions or event handlers.
// Arrow functionconst add = (x, y) => x + y;// Traditional function expressionconst add = function(x, y) {return x + y;};
2) Template literals: This allows embedding expressions and multiline strings directly within backticks (``) in the code. This makes creating dynamic strings, including interpolating variables and expressions, easy.
const name: string = "John";const greeting: string = `Hello, ${name}!`;
There are more ES6 features than this article can explore. Visit Ecma Internation to learn more.
#Code organization
Proper code organization in TypeScript or any other language is essential for maintaining clean, maintainable, and scalable projects. However, TypeScript simplifies this by providing out-of-the-box support for properly organizing code, such as:
1. Modules
Modules, introduced in ES6 as part of the language specification, using the import
and export
statements, developers can organize code into reusable components, libraries, or features, ensuring separation of concerns, readability, and reusability.
// math.tsexport function add(x: number, y: number): number {return x + y;}// app.tsimport { add } from "./math";
Here, we created an add
function and exported it using the export
statement for usage across the codebase.
2. Namespace
This helps to logically group related code under a single, global hierarchy, especially for shared utility functions or types across multiple modules in a project. This can be useful in large codebases to help prevent naming conflicts or global scope pollution as the codebase becomes larger.
namespace Geometry {export class Circle {// Circle implementation}export class Rectangle {// Rectangle implementation}}
The code above creates a namespace called Geometry that acts like a container for two related classes, Circle
and Rectangle
. These classes can be used in other parts of the code by importing them from the Geometry namespace, like so:
import { Circle, Rectangle } from './Geometry'; // Import from Geometry namespaceconst myCircle = new Circle(); // Create a Circle objectconst myRectangle = new Rectangle(); // Create a Rectangle object
#Integration with JavaScript libraries
Integrating TypeScript with existing JavaScript libraries offers a way of leveraging TypeScript's benefits of type safety while using the JavaScript library ecosystem.
TypeScript does this in the following ways:
Declaration files: Declaration files are blueprints that describe the shape of the library's API (functions, classes, objects, and types).
TypeScript support: Many popular JavaScript libraries and frameworks also officially support TypeScript by providing TypeScript-specific installation instructions. Some also take it a step further by providing plugins specifically developed for TypeScript development libraries.
#Compilation to JavaScript
Browsers only understand and execute JavaScript code. As a result, to run TypeScript code in the browser, it must first be compiled/transpiled into JavaScript using the TypeScript compiler tsc
, which transforms any TypeScript-specific features into code compatible with JavaScript engines.
The compilation process is like this:
Parsing: The compiler starts by reading the TypeScript code and parsing or transforming it into a data structure called an Abstract Syntax Tree (AST), which represents the code's structure and relationships between elements.
Type checking: During parsing, the compiler performs type checking based on type annotations and inference to ensure the code adheres to the defined types.
Transformation: The compiler might transform the code based on the AST and type-checking results. This could involve removing type annotations as they are not needed in JavaScript.
Code generation: The compiler generates the equivalent JavaScript code based on the transformed AST, which retains the functionality of the TypeScript code.
Let us look at this TypeScript code which defines an Admin
interface and a createAdmin
function that matches the interface properties.
interface Admin {name: string;id: number;}function createAdmin(name: string, id: number): Admin {return { name, id}; // Object literal with type inference}const admin1 = createAdmin("Daniel", 1); // Admin { name: "Daniel", id: 1}const admin2 = createAdmin("Michael", 2); // Admin { name: "Michael", id: 2 }
Here is the same code, when transpiled to JavaScript:
function createAdmin(name, id) {return { name, id } // Object literal with type inference}const admin1 = createAdmin("Daniel", 1) // Admin { name: "Daniel", id: 1}const admin2 = createAdmin("Michael", 2) // Admin { name: "Michael", id: 2 }
Check out TypeScript’s online playground to transpile TypeScript code on the fly.
#Compatibility with JavaScript
JavaScript and TypeScript syntax can coexist in the same project. However, to leverage TypeScript’s capabilities, the TypeScript compiler must be set up.
This compatibility offers several advantages for developers:
Syntax compatibility: JavaScript codebases can be gradually migrated to TypeScript. Changing the file extension from
.js
to.ts
is a starting point; it also requires a TypeScript compiler and atsconfig
file that specifies the compiler options.Type compatibility: TypeScript provides a type system that allows developers to specify variable types. However, TypeScript also supports dynamic typing, allowing developers to opt in or out of type-checking.
Runtime compatibility: Since TypeScript code is transpiled to JavaScript before execution, it is compatible with any JavaScript runtime environment.
#Getting started with TypeScript
The first step in working with TypeScript locally (i.e., offline) is to set up your environment. TypeScript can be set up in three ways, but in this section, we will consider just one: via npm.
Setup
First, you must install Node to run the environment and NPM to manage dependency. Download them from the Node.js official website.
Next, use an IDE like VS Code and initialize a new project with npm
by running npm init -y
, which will create a package.json
for the project.
Within this project, run npm install typescript --save-dev
to install TypeScript as a dependency for development purposes only.
Writing TypeScript code
Now, write your first TypeScript program by creating a file named app.ts
. ts
is the file extension for TypeScript as .js
is the extension for JavaScript files.
Now, add this code that defines an Admin
interface with name
and id
properties into the app.ts
file:
interface Admin {name: string;id: number;}function createAdmin(name: string, id: number): Admin {return { name, id}; // Object literal with type inference}const admin1 = createAdmin("Daniel", 1); // Admin { name: "Daniel", id: 1}const admin2 = createAdmin("Michael", 2); // Admin { name: "Michael", id: 2 }
In the code above, after defining the interface, we also created a function createAdmin,
which creates “Admin” objects.
Compiling TypeScript code
To compile this file using the tsc
TypeScript compiler, run npx tsc
app.ts
in the terminal. This will create a file named app.js
alongside the app.ts
file.
To watch for changes and automatically recompile the TypeScript file, run **npx tsc app.ts**
**--**``**watch**
in your terminal.
You can modify the code and write more TypeScript code following some of the features we discussed earlier.
The best way to learn TypeScript is to practice writing it, experiment with different features and patterns, and build projects. The TypeScript handbook explains the various features TypeScript offers and how you can leverage them. You can also read this article about using TypeScript with Svelte.
#Advantages of TypeScript over JavaScript
Since TypeScript’s release over a decade ago, there has been continuous debate over its advantages over JavaScript.
In this section, we will explore some of these advantages.
Type safety: TypeScript's static type system enforces type annotations on variables, functions, and objects, which helps to achieve early error detection and improved maintainability through interfaces, classes, and modules.
Better developer experience: Developers can now write code more confidently, knowing that the compiler will identify potential type mismatches before the code reaches runtime. This can be especially useful when migrating a JavaScript codebase to TypeScript.
#Potential trade-offs
Let us briefly look at some trade-offs between JavaScript and TypeScript, which can potentially guide us on when to use either and for what projects.
#When to use TypeScript
From the trade-offs explored above, you can deduce that TypeScript may not be appropriate for all projects. This may be true, but why do we leave that decision to you? Let’s help you make a better decision.
When is it appropriate to use TypeScript?
Large-scale applications: TypeScript was developed to address scalability problems of large JavaScript codebases. As a project grows, static typing helps catch errors early and improves code maintainability and collaboration among team members.
Team collaboration: In a team environment with multiple developers contributing to the same codebase, TypeScript can facilitate collaboration by enforcing coding conventions and enabling better communication through self-documenting code.
Long-term projects: Maintenance is the last phase of the software development lifecycle and can take as long as a decade or as short as a year. TypeScript is well-suited for long-term projects where code maintenance, scalability, and future-proofing are essential considerations.
#When not to use TypeScript
Unfortunately, despite its advantages, TypeScript might not suit all use cases. Such as:
- Simple projects: For small projects with minimal complexity, the benefits of static typing and advanced tooling may not outweigh the additional configuration and learning curve associated with TypeScript.
- Legacy codebases: Converting large, existing JavaScript codebases to TypeScript can be daunting. In such cases, the effort required to migrate to TypeScript might not justify the benefits, particularly if the project is nearing its end-of-life or undergoing minimal maintenance.
#TypeScript tooling and ecosystem
TypeScript's ecosystem has a rich toolset of libraries and frameworks, which can be leveraged for a better development experience. Such as:
TypeScript compiler (tsc): The TypeScript compiler (
tsc
) is the primary and official tool for transpiling TypeScript code into JavaScript code.**Integrated Development Environments (IDEs): Many IDEs, such as VS Code, offer excellent support for TypeScript development. These features range from intellisense to code completion and debugging functionalities.
TypeScript community: TypeScript has a large and growing community that allows asking questions, sharing knowledge, and learning from other TypeScript developers.
Build tools: Using TypeScript requires compiling it into JavaScript, so bundling tools that automate the compilation process exist. Examples of these build tools are Webpack, Gulp, etc.
#TypeScript and GraphQL
TypeScript and GraphQL are powerful tools commonly used together in modern web development. GraphQL, a query language for APIs, provides a flexible and efficient approach to data fetching and manipulation. Combined, TypeScript and GraphQL offer numerous benefits for building robust and scalable applications. Such as:
Intuitive type generation: Tools like GraphQL code generator can automatically generate TypeScript types from GraphQL schema definitions, which can significantly simplify development.
Strong typing with GraphQL schema: TypeScript's static typing aligns well with GraphQL's schema-based approach.
In essence, while TypeScript ensures type safety, GraphQL provides flexibility in data fetching. TypeScript and GraphQL work perfectly with a headless content management system (CMS) like Hygraph. Developers can combine all three technologies to create high-performance, maintainable, and rich user interfaces.
Learn more about using GraphQL with TypeScript.
#Future of TypeScript
TypeScript is actively developed and maintained by Microsoft and has widespread industry adoption. Currently, popular frameworks like Next.js advocate for TypeScript as the modern tool for web development through the TypeScript first configuration details. Same as Angular - an open-source framework whose official language is TypeScript.
We can expect to see even more progression and adoption over the years. Check out this page to learn more about the TypeScript roadmap.
Developers, TypeScript’s target audience, have also shared their continuous favoritism toward it. The Stackoverflow survey also corroborates this, showing that TypeScript rose from 34.83% in 2022 to 38.87% in 2023 as a popular language.
However, we also see continuous upgrades to JavaScript, with the latest proposition to add type annotations to JavaScript. But are types all that is needed to make developers quit TypeScript for JavaScript? We guess time will tell.
Join Hygraph's developer community to connect with other TypeScript developers, from beginners to experts. Learn best practices and leverage the language's full potential.
Blog Author
Motunrayo Moronfolu
Technical writer
Motunrayo Moronfolu is a Senior Frontend Engineer and Technical writer passionate about building and writing about great user experiences.