You’ve probably heard of TypeScript — the language created and maintained by Microsoft that’s had a huge impact on the Web, with many prominent projects embracing and migrating their code to TypeScript. TypeScript is a typed superset of JavaScript. In other words, it adds types to JavaScript — and hence the name. But why would you want these types? What benefits do they bring? And do you need to rewrite your entire codebase to take advantage of them? Those questions, and more, will be answered in this TypeScript tutorial for beginners.
We assume a basic knowledge of JavaScript and its tooling, but zero prior knowledge of TypeScript is required to follow along.
Some Erroneous JavaScript Code
Table of Contents
To start with, let’s look at some fairly standard plain JavaScript code that you might come across in any given codebase. It fetches some images from the Pexels API and inserts them to the DOM.
However, this code has a few typos in it that are going to cause problems. See if you can spot them:
const PEXELS_API_KEY = '...'; async function fetchImages(searchTerm, perPage) { const result = await fetch(`https://api.pexels.com/v1/search?query=${searchTerm}&per_page=${perPage}`, { headers: { Authorization: PEXELS_API_KEY, } }); const data = await result.json(); const imagesContainer = document.qerySelector('#images-container'); for (const photo of data.photos) { const img = document.createElement('image'); img.src = photo.src.medium; imagesContainer.append(img); } } fetchImages('dogs', 5); fetchImages(5, 'cats'); fetchImages('puppies');
Can you spot the issues in the above example? Of course, if you ran this code in a browser you’d immediately get errors, but by taking advantage of TypeScript we can get the errors quicker by having TypeScript spot those issues in our editor.
Shortening this feedback loop is valuable — and it gets more valuable as the size of your project grows. It’s easy to spot errors in these 30 lines of code, but what if you’re working in a codebase with thousands of lines? Would you spot any potential issues easily then?
Note: there’s no need to obtain an API key from Pexels to follow along with this TypeScript tutorial. However, if you’d like to run the code, an API key is entirely free: you just need to sign up for an account and then generate one.
Running TypeScript from the Editor
Once upon a time, TypeScript required that all files be written as .ts
files. But these days, the onboarding ramp is smoother. You don’t need a TypeScript file to write TypeScript code: instead, we can run TypeScript on any JavaScript file we fancy!
If you’re a VS Code user (don’t panic if you aren’t — we’ll get to you!), this will work out the box with no extra requirements. We can enable TypeScript’s checking by adding this to the very top of our JavaScript file (it’s important that it’s the first line):
// @ts-check
You should then get some squiggly red errors in your editor that highlight our mistakes, as pictured below.
You should also see a cross in the bottom left-hand corner with a two by it. Clicking on this will reveal the problems that have been spotted.
And just because you’re not on VS Code doesn’t mean you can’t get the same experience with TypeScript highlighting errors. Most editors these days support the Language Server Protocol (commonly referred to as LSP), which is what VS Code uses to power its TypeScript integration.
It’s well worth searching online to find your editor and the recommended plugins to have it set up.
Installing and Running TypeScript Locally
If you’re not on VS Code, or you’d like a general solution, you can also run TypeScript on the command line. In this section, I’ll show you how.
First, let’s generate a new project. This step assumes you have Node and npm installed upon your machine:
mkdir typescript-demo cd typescript demo npm init -y
Next, add TypeScript to your project:
npm install --save-dev typescript
Note: you could install TypeScript globally on your machine, but I like to install it per-project. That way, I ensure I have control over exactly which version of TypeScript each project uses. This is useful if you have a project you’ve not touched for a while; you can keep using an older TS version on that project, whilst having a newer project using a newer version.
Once it’s installed, you can run the TypeScript compiler (tsc
) to get the same errors (don’t worry about these extra flags, as we’ll talk more about them shortly):
npx tsc index.js --allowJs --noEmit --target es2015 index.js:13:36 - error TS2551: Property 'qerySelector' does not exist on type 'Document'. Did you mean 'querySelector'? 13 const imagesContainer = document.qerySelector('#images-container'); ~~~~~~~~~~~~ node_modules/typescript/lib/lib.dom.d.ts:11261:5 11261 querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'querySelector' is declared here. index.js:16:9 - error TS2339: Property 'src' does not exist on type 'HTMLElement'. 16 img.src = photo.src.medium; ~~~ Found 2 errors.
You can see that TypeScript on the command line highlights the same JavaScript code errors that VS Code highlighted in the screenshot above.
Fixing the Errors in Our JavaScript Code
Now that we have TypeScript up and running, let’s look at how we can understand and then rectify the errors that TypeScript is flagging.
Let’s take a look at our first error.
Property qerySelector
does not exist on type Document
index.js:13:36 - error TS2551: Property 'qerySelector' does not exist on type 'Document'. Did you mean 'querySelector'? 13 const imagesContainer = document.qerySelector('#images-container'); node_modules/typescript/lib/lib.dom.d.ts:11261:5 11261 querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'querySelector' is declared here.
This can look quite overwhelming if you’re not used to reading TypeScript errors, so don’t panic if it looks a bit odd! TypeScript has spotted that, on line 13
, we’ve called a method document.qerySelector
. We meant document.querySelector
but made a mistake when typing. We would have found this out when we tried to run our code in the browser, but TypeScript is able to make us aware of it sooner.
The next part where it highlights lib.dom.d.ts
and the querySelector<K...>
function is diving into more advanced TypeScript code, so don’t worry about that yet, but at a high level it’s TypeScript showing us that it understands that there’s a method called querySelector
, and it suspects we might have wanted that.
Let’s now zoom in on the last part of the error message above:
index.js:13:36 - error TS2551: Property 'qerySelector' does not exist on type 'Document'. Did you mean 'querySelector'?
Specifically, I want to look at the text did not exist on type 'Document'
. In TypeScript (and broadly in every typed language), items have what’s called a type
.
In TypeScript, numbers like 1
or 2.5
have the type number
, strings like "hello world"
have the type string
, and an instance of an HTML Element has the type HTMLElement
. This is what enables TypeScript’s compiler to check that our code is sound. Once it knows the type of something, it knows what functions you can call that take that something, or what methods exist on it.
Note: if you’d like to learn more about data types, please consult “Introduction to Data Types: Static, Dynamic, Strong & Weak”.
In our code, TypeScript has seen that we’ve referred to document
. This is a global variable in the browser, and TypeScript knows that and knows that it has the type of Document
. This type documents (if you pardon the pun!) all of the methods we can call. This is why TypeScript knows that querySelector
is a method, and that the misspelled qerySelector
is not.
We’ll see more of these types as we go through further TypeScript tutorials, but this is where all of TypeScript’s power comes from. Soon we’ll define our own types, meaning really we can extend the type system to have knowledge about all of our code and what we can and can’t do with any particular object in our codebase.
Now let’s turn our attention to our next error, which is slightly less clear.
Property src
does not exist on type HTMLElement
index.js:16:9 - error TS2339: Property 'src' does not exist on type 'HTMLElement'. 16 img.src = photo.src.medium;
This is one of those errors where sometimes you have to look slightly above the error to find the problem. We know that an HTML image element does have a src
attribute, so why doesn’t TypeScript?
const img = document.createElement('image'); img.src = photo.src.medium;
The mistake here is on the first line: when you create a new image element, you have to call document.createElement('img')
(because the HTML tag is <img>
, not <image>
). Once we do that, the error goes away, because TypeScript knows that, when you call document.createElement('img')
, you get back an element that has a src
property. And this is all down to the types.
When you call document.createElement('div')
, the object returned is of the type HTMLDivElement
. When you call document.createElement('img')
, the object returned is of type HTMLImageElement
. HTMLImageElement
has a src
property declared on it, so TypeScript knows you can call img.src
. But HTMLDivElement
doesn’t, so TypeScript will error.
In the case of document.createElement('image')
, because TypeScript doesn’t know about any HTML element with the tag image
, it will return an object of type HTMLElement
(a generic HTML element, not specific to one tag), which also lacks the src
property.
Once we fix those two mistakes and re-run TypeScript, you’ll see we get back nothing, which shows that there were no errors. If you’ve configured your editor to show errors, hopefully there are now none showing.
How to Configure TypeScript
It’s a bit of a pain to have to add // @ts-check
to each file, and when we run the command in the terminal having to add those extra flags. TypeScript lets you instead enable it on a JavaScript project by creating a jsconfig.json
file.
Create jsconfig.json
in the root directory of our project and place this inside it:
{ "compilerOptions": { "checkJs": true, "noEmit": true, "target": "es2015" }, "include": ["*.js"] }
This configures the TypeScript compiler (and your editor’s TS integration) to:
- Check JavaScript files (the
checkJs
option). - Assume we’re building in an ES2015 environment (the
target
option). Defaulting to ES2015 means we can use things like promises without TypeScript giving us errors. - Not output any compiled files (the
noEmit
option). When you’re writing TypeScript code in TypeScript source files, you need the compiler to generate JavaScript code for you to run in the browser. As we’re writing JavaScript code that’s running in the browser, we don’t need the compiler to generate any files for us. - Finally,
include: ["*.js"]
instructs TypeScript to look at any JavaScript file in the root directory.
Now that we have this file, you can update your command-line instruction to this:
npx tsc -p jsconfig.json
This will run the compiler with our configuration file (the -p
here is short for “project”), so you no longer need to pass all those flags through when running TypeScript.
Continue reading A Step by Step TypeScript Tutorial for Beginners on SitePoint.