Start using TypeScript

Types do not change runtime behavior

Understand that annotations are erased and cannot validate a network response, form value, or JSON file while the program runs.

Types disappear before your program runs. This means a type can never check a value that arrives while the program is running. I want to make this point early, because it is the source of most TypeScript bugs I see.

Take this line, which you will find in many codebases:

const user = (await response.json()) as User

The as User part is a type assertion. It tells TypeScript “trust me, this is a User”. And TypeScript does trust you. It does not add any code that inspects the response body. The server can still return null, an error object, or a user with half the fields missing. Your program will find out later, with a crash somewhere far from this line.

Types do not convert values either

An assertion cannot change a value’s runtime type. Here is a common attempt:

const port = process.env.PORT as unknown as number
console.log(typeof port)
console.log(port + 1)

Compile it and run it with PORT=3000 node index.js. You get:

string
30001

TypeScript believed port was a number. At runtime it was the string '3000', so + 1 did string concatenation. Environment variables are always strings, and no annotation changes that. The assertion only changed what the checker believed.

Where the boundary is

So where do uncertain values come from? Anything that enters the program from outside:

  • JSON from JSON.parse() or response.json()
  • form fields
  • localStorage and files on disk
  • environment variables
  • anything a network call returns

Treat all of these as uncertain. Check them at the boundary, the moment they enter, before you give them a more specific type.

For a user object that means checking that the value is an object, and that the fields your program needs exist and have the right type. A schema library can do this for you, but plain JavaScript typeof checks work too. We write one of those checks by hand later in the course.

The rule I follow: a type annotation describes what my own code guarantees. It never describes what the outside world sends me. For that I need code that runs.

Try this on your own: log typeof process.env.PORT in a small script and run it. Then compare that result with what an unsafe assertion can make TypeScript claim about the same value.

Lesson completed