Skip to content

Move from JavaScript to TypeScript

You will convert a small JavaScript data model to TypeScript, use the type checker to find an invalid value before execution, and add a runtime check for data that enters the program from outside.

Level 3.1 uses TypeScript in the React client, the Express server, and the Capacitor application. TypeScript lets you reuse the JavaScript model from Level 2 while making important data relationships visible to your editor, your team, and the type checker.

TypeScript does not replace testing or runtime validation. This lesson separates those responsibilities before the project depends on them.

What you will practice

  • Explain which JavaScript rules TypeScript keeps and which static checks TypeScript adds.
  • Use inference, annotations, object types, arrays, and literal unions to describe application data.
  • Read a type error as evidence about a mismatch between code and the declared model.
  • Explain why TypeScript types are not available when the emitted JavaScript runs.
  • Treat external data as unknown and narrow it with runtime checks before use.
  • Compare the role of TypeScript types with PHP type declarations without treating either stack as the only valid choice.
  • New: Static type checking, type inference, type annotations, object types, literal unions, unknown, narrowing, type predicates, type erasure, and the separate roles of type checking and runtime validation.
  • Reused: JavaScript values, objects, arrays, functions, parameters, return values, conditionals, typeof, strict equality, template literals, JSON, browser Console output, and deliberate tests.

Starting point

Before you start

  • The completed Level 2 learning goals, including JavaScript functions, arrays, objects, conditions, interface state, testing, and Git.
  • A current browser with access to the TypeScript Playground and browser developer tools.
  • No local project or package installation is required for this lesson. The next lesson creates the course project.
Current state
You can write and run JavaScript, but the current code does not state or check the exact shape of a work item before execution.
First action
Open the TypeScript Playground, select the TS Config panel, enable strict mode, and return to the editor panel.
First checkpoint
The Playground editor accepts a JavaScript-style program and shows its emitted JavaScript in the JS output panel.
Help trigger
Use the nearest recovery note or ask for help if strict mode is not enabled, a type error remains after the matching correction, or the Run output differs from the stated result after you reload the Playground.

You have completed the lesson when:

  • you can identify JavaScript syntax, TypeScript-only syntax, type-check time, and runtime in the lesson examples;
  • a WorkItem type limits status to planned, active, or done;
  • a typed function accepts one WorkItem and returns a summary string;
  • changing a status to an unsupported value produces a type error before the program runs;
  • the emitted JavaScript contains the executable behavior but no WorkItem type or type annotations;
  • a value from JSON enters the trusted code as unknown;
  • runtime checks reject one invalid object and accept one valid object; and
  • you can explain why successful type checking does not prove that an API response, form value, file, or stored value is valid.

Separate JavaScript, TypeScript, and the runtime

Section titled “Separate JavaScript, TypeScript, and the runtime”

TypeScript is a language and toolchain built on JavaScript. It keeps JavaScript expressions, statements, functions, objects, arrays, modules, and runtime behavior. It adds syntax that describes expected types and a checker that analyzes those expectations before execution.

Use four separate terms:

Term What it does Example in this level
JavaScript Defines the runtime language and behavior Objects, arrays, functions, fetch, and event handlers
TypeScript source Adds type syntax to JavaScript source status: WorkStatus and item: WorkItem
Type checker Reports mismatches without needing to execute each path Reports that "blocked" is not a valid WorkStatus
Runtime Executes the emitted JavaScript The browser runs the React client; Node runs the Express server

The browser and Node do not execute TypeScript type aliases. The toolchain checks the .ts or .tsx source and produces JavaScript for a runtime.

Open the TypeScript Playground. The Playground keeps the source, type feedback, emitted JavaScript, and runtime output in one browser page.

Replace the editor content with this code:

TypeScript Playground — JavaScript syntax
const title = "Create accessible navigation";
const completed = false;
const workStatus = completed ? "done" : "planned";
console.log(`${title}: ${workStatus}`);

This source uses JavaScript syntax only. TypeScript still infers useful types from the values:

  • title has the string-literal type "Create accessible navigation";
  • completed has the boolean-literal type false; and
  • workStatus has the union type "done" | "planned" because the conditional expression can produce either string.

Move the pointer over each identifier in the Playground. The editor displays the inferred type. Inference means that TypeScript calculates a type from the source without a written annotation at that location.

Select Run. The Logs panel must show:

Expected Playground log
Create accessible navigation: planned

Open the Playground’s JS output panel. The exact output can differ with the selected compilation target, but it contains the same executable values, condition, and Console call.

The output does not yet prove that types disappear because the source contains no TypeScript-only syntax. You will add that syntax next.

  • If the Logs panel does not appear, select Run after you replace the editor content.
  • If the result says done, confirm that completed is the boolean value false, without quotation marks.
  • If the editor already contains unrelated errors, select all editor content before you paste the lesson code.
  • If the pointer does not show type information, place the text cursor on the identifier and wait for the editor information to appear.

Checkpoint: JavaScript behavior runs inside the TypeScript workflow

What now works
The Playground accepts the JavaScript-style source, reports inferred types in the editor, emits JavaScript, and logs Create accessible navigation: planned.
What remains
Make the work-item model explicit and use the checker to reject an unsupported state.
Next action
Replace the Playground source with the WorkStatus and WorkItem model in the next section.
If it does not work
Restore the complete first code block, select Run, and resolve the first visible error before you inspect later output.

A type model records relationships that the application depends on. The model should describe product rules, not every temporary value in the file.

The continuing course project will manage work items. Each item has:

  • a numeric identifier;
  • a title;
  • one supported status; and
  • a set of labels.

Replace the Playground content with this code:

TypeScript Playground — typed work-item model
type WorkStatus = "planned" | "active" | "done";
type WorkItem = {
id: number;
title: string;
status: WorkStatus;
labels: string[];
};
function createSummary(item: WorkItem): string {
return `${item.title}: ${item.status}`;
}
const firstItem: WorkItem = {
id: 1,
title: "Create accessible navigation",
status: "planned",
labels: ["frontend", "accessibility"],
};
console.log(createSummary(firstItem));

Select Run. The Logs panel must still show:

Expected Playground log
Create accessible navigation: planned

The program has the same visible result, but the source now states its data contract.

Syntax Meaning
type WorkStatus = ... Gives a reusable name to the permitted status values
"planned" | "active" | "done" A union of three exact string values
type WorkItem = { ... } Describes the required properties of a work-item object
id: number Requires the id property to contain a number
labels: string[] Requires an array whose items are strings
item: WorkItem Requires the function argument to match WorkItem
): string States that the function returns a string
firstItem: WorkItem Checks the object literal against the named model

The WorkStatus union prevents arbitrary strings from becoming stored status values. This is more precise than the broad type string because the product currently supports three states.

The WorkItem object type requires every listed property. TypeScript also checks each property value where the object is created and used.

Use inference where the value already gives enough information

Section titled “Use inference where the value already gives enough information”

Do not add an annotation to every declaration. In this line, TypeScript knows that the function call returns a string:

Inference from a return type
const summary = createSummary(firstItem);

Writing const summary: string would repeat information that the checker already has. Add an annotation when it expresses a useful boundary, contract, or intended model.

Useful annotations in the complete example include:

  • the item parameter, because callers must know what the function accepts;
  • the function return type, because it states the function contract; and
  • firstItem: WorkItem, because the object must satisfy the shared application model.

Change only this property:

Deliberate type error
status: "blocked",

The editor must report a message similar to:

Expected type-checker result
Type '"blocked"' is not assignable to type 'WorkStatus'.

This message contains three useful facts:

  1. The source currently supplies the exact value "blocked".
  2. The receiving property expects WorkStatus.
  3. The declared WorkStatus model does not include that value.

The error does not decide the product requirement. You must choose one of two valid actions:

  • If blocked is a real application state, add it to WorkStatus and decide how every status-dependent feature handles it.
  • If blocked is a mistaken value, restore one of the supported states.

For this lesson, blocked is not part of the product model. Restore status: "planned" and confirm that the error disappears.

Perform these experiments one at a time. Restore the valid code after each experiment.

Change Expected checker evidence Reason
Remove labels A required property is missing WorkItem requires all four properties
Change id to "1" A string is not assignable to number Quotation marks create a string value
Put false in labels A boolean is not assignable to string Every array item must be a string
Write item.name in the function name does not exist on WorkItem The model defines title, not name

Do not make all four changes at the same time. One change keeps each message connected to one cause.

Checkpoint: The static model rejects unsupported source values

What now works
WorkItem describes the required object shape, WorkStatus limits the status values, the typed function returns the expected summary, and each deliberate mismatch produces relevant checker evidence.
What remains
Inspect which type information reaches the runtime and validate data that the checker cannot know in advance.
Next action
Restore the valid typed example, then inspect the JS output panel.
If it does not work
Compare the source with the complete typed example and correct the first type error from top to bottom. Later errors can be consequences of the first mismatch.

Confirm that types do not exist at runtime

Section titled “Confirm that types do not exist at runtime”

With the valid typed example restored, open the JS output panel again.

The emitted output has this general form:

Representative emitted JavaScript
"use strict";
function createSummary(item) {
return `${item.title}: ${item.status}`;
}
const firstItem = {
id: 1,
title: "Create accessible navigation",
status: "planned",
labels: ["frontend", "accessibility"],
};
console.log(createSummary(firstItem));

The selected compilation target can change details such as declaration syntax. The important result is stable:

  • WorkStatus is absent;
  • WorkItem is absent;
  • the parameter annotation is absent;
  • the return annotation is absent; and
  • the JavaScript values, function, property access, and Console call remain.

Type erasure means that the TypeScript-only type information is removed from the emitted JavaScript. The runtime cannot ask whether an object satisfies the erased WorkItem alias.

TypeScript also preserves JavaScript runtime behavior. For example, JavaScript produces Infinity for division by zero. A number annotation does not change that behavior or add a runtime exception.

Static checks can find some mistakes before execution

Section titled “Static checks can find some mistakes before execution”

The checker can analyze source relationships such as:

  • a misspelled property that is not in an object type;
  • a string passed where a function expects a number;
  • a missing required property;
  • a function path that can return the wrong type; or
  • an unsupported member of a literal union.

The checker cannot prove facts that only exist outside the analyzed source. Examples include:

  • whether an API is available;
  • whether a server returns the documented JSON;
  • whether a form value follows a business rule;
  • whether stored data came from an older application version;
  • whether a user has permission to perform an action; or
  • whether the interface works with keyboard, touch, zoom, and assistive technology.

Those facts require runtime validation, error handling, automated tests, manual tests, or server-side authorization, depending on the boundary.

Data from a request, form, file, storage system, or third-party library can disagree with your TypeScript model. Treat unverified external data as unknown until runtime code checks the properties that the application needs.

unknown means that a value exists but trusted code does not yet know enough about its type. Unlike any, unknown prevents direct property access until the code narrows the possible type.

Add this line below the WorkItem type:

Unverified external value
const externalValue: unknown = JSON.parse('{"id":1,"title":"Create navigation"}');

Then try to add this line:

Deliberate unknown-value error
console.log(externalValue.title);

The checker reports that externalValue has the type unknown. The JSON text contains a title, but the trusted program has not checked that claim. It has also not checked status or labels, which are required by WorkItem.

Remove the deliberate console.log line before you continue.

Write runtime checks that narrow the value

Section titled “Write runtime checks that narrow the value”

Replace the complete Playground content with this example:

TypeScript Playground — validate unknown work-item data
type WorkStatus = "planned" | "active" | "done";
type WorkItem = {
id: number;
title: string;
status: WorkStatus;
labels: string[];
};
function isWorkStatus(value: unknown): value is WorkStatus {
return value === "planned" || value === "active" || value === "done";
}
function isWorkItem(value: unknown): value is WorkItem {
if (typeof value !== "object" || value === null) {
return false;
}
return (
"id" in value &&
typeof value.id === "number" &&
"title" in value &&
typeof value.title === "string" &&
"status" in value &&
isWorkStatus(value.status) &&
"labels" in value &&
Array.isArray(value.labels) &&
value.labels.every((label) => typeof label === "string")
);
}
function createSummary(item: WorkItem): string {
return `${item.title}: ${item.status}`;
}
const jsonText = `{
"id": 1,
"title": "Create accessible navigation",
"status": "planned",
"labels": ["frontend", "accessibility"]
}`;
const parsedValue: unknown = JSON.parse(jsonText);
if (isWorkItem(parsedValue)) {
console.log(createSummary(parsedValue));
} else {
console.error("The work-item data is invalid.");
}

Select Run. The Logs panel must show the valid summary.

isWorkItem is a regular JavaScript function with a TypeScript return annotation.

The function performs these runtime checks in order:

  1. The value must be an object and must not be null.
  2. The object must contain id, and its value must be a number.
  3. The object must contain title, and its value must be a string.
  4. The object must contain status, and isWorkStatus must accept its exact value.
  5. The object must contain labels, the value must be an array, and every array member must be a string.

The return annotation value is WorkItem is a type predicate. It tells TypeScript what a true result means for the type of the parameter. Inside the true branch, the checker narrows parsedValue from unknown to WorkItem.

The runtime executes the comparisons, property checks, Array.isArray, and every call. The emitted JavaScript removes the type predicate, but the function still returns true or false.

This validator checks the current structural contract only. It does not require a positive integer ID or a non-empty title. Add those business rules when the product contract requires them, and test them separately.

Change only the JSON status value from "planned" to "blocked". Select Run again.

The Logs panel must now show:

Expected invalid-data result
The work-item data is invalid.

No type error appears inside the JSON string because source-code strings can contain any text. The runtime check rejects the parsed value after the program reads that text.

Restore "planned", then perform these tests one at a time:

JSON change Expected runtime result
Remove labels Invalid-data message
Change id to "1" Invalid-data message
Put false in labels Invalid-data message
Restore the complete valid JSON Valid summary

The final state must use the complete valid JSON and log the valid summary.

  • If TypeScript reports an error on a property check, confirm that the object test and value === null test appear before the property checks.
  • If invalid status passes, confirm that isWorkStatus compares the value with all three permitted strings and no other value.
  • If a boolean label passes, confirm that every compares each label’s typeof result with "string".
  • If every value fails, log each boolean expression separately or return after one property check at a time. Find the first false result instead of changing all checks together.
  • If the Playground reports a JSON parse error, restore the complete jsonText block. JSON requires double quotation marks around property names and string values.

Checkpoint: Unknown data becomes trusted only after runtime checks

What now works
The program keeps parsed JSON as unknown, accepts the complete valid work item, rejects each tested mismatch, and only calls createSummary in the narrowed true branch.
What remains
Compare the selected stack with PHP, complete the self-check, and preserve the static-versus-runtime distinction for later API work.
Next action
Restore the valid JSON, run it once, and then read the stack comparison.
If it does not work
Restore the complete validation example and test the valid case first. Change one JSON value at a time only after the baseline passes.

Compare TypeScript and PHP without ranking them

Section titled “Compare TypeScript and PHP without ranking them”

PHP is a valid choice for server-side websites and APIs. Many web-development programs teach PHP because it can render HTML, process requests, use databases, and run on widely available hosting.

This level uses TypeScript for a different course constraint: you already learned JavaScript in Level 2. The selected stack lets you apply the same language model in React, Node, Express, and Capacitor before you add another server-side language.

The two stacks share many concepts:

  • variables contain values;
  • functions receive arguments and return results;
  • conditions select behavior;
  • arrays and objects group data;
  • requests enter server-side code and responses leave it;
  • external input needs runtime validation;
  • dependencies, automated tests, version control, and documentation support delivery; and
  • teams must agree on data contracts and error behavior.

The main type-system difference in this lesson is when the check runs:

Question TypeScript in this course PHP with type declarations
What source runs? The toolchain emits JavaScript for the browser or Node A PHP runtime executes PHP source on the server
When are declared types checked? The TypeScript checker analyzes source before runtime PHP checks declared parameter and return types when the code runs
Do the declared types remain available to the running code? TypeScript aliases and annotations are erased PHP type declarations are part of the running PHP program
Can external input still be invalid? Yes Yes
Does a type declaration replace application validation? No No

This TypeScript function and PHP function state a similar input-and-output contract:

TypeScript — checked before JavaScript runs
function formatCount(count: number): string {
return `${count} items`;
}
PHP — type declarations checked at runtime
<?php
declare(strict_types=1);
function formatCount(int $count): string
{
return "{$count} items";
}

The similar annotations can help you transfer the idea of a function contract. The execution model remains different. PHP also has detailed coercion and strict-typing rules that belong in a PHP course. You do not need those rules to complete this TypeScript lesson.

The stack choice gives this six-week level one continuing path:

  1. TypeScript describes shared application models.
  2. React uses those models in browser components and interface state.
  3. Express uses the same language and related models for server routes.
  4. The React client consumes the Express API through runtime-validated data.
  5. Capacitor packages the web client for a supported mobile target.

This choice reduces the number of unrelated language changes during the module. It does not claim that TypeScript is better than PHP for every project. The transferable learning goal is to understand the request, response, validation, component, state, test, build, and delivery responsibilities well enough to recognize them in another stack.

Use this table when you decide how to verify a result later in the module:

Claim Relevant evidence
A source value matches a declared model Type checking
External JSON has the required runtime shape Runtime validation
A function returns the correct result for selected cases Automated unit tests
A React interaction works for a user Component or browser tests, plus manual checks where needed
A route returns the intended HTTP status and JSON API integration tests
A keyboard user can complete a path Keyboard testing and accessibility inspection
The production code can be emitted and bundled Production build
A user is allowed to perform an action Server-side authentication and authorization, when the product requires them

One tool can support several claims, but no single green check proves every part of the application.

Self-check

Complete these checks against the required result.

  1. Point to one line that uses JavaScript syntax and one line that adds TypeScript-only type syntax.
  2. Explain the difference between a value at runtime and a type that the checker uses before runtime.
  3. Confirm that WorkStatus permits planned, active, and done and rejects blocked in source code.
  4. Remove one required WorkItem property, read the resulting error, and then restore the property.
  5. Inspect the emitted JavaScript and confirm that WorkStatus, WorkItem, parameter annotations, and return annotations are absent.
  6. Run the runtime-validation example with valid JSON and confirm that it logs Create accessible navigation: planned.
  7. Run the same example with an invalid status and confirm that it reports The work-item data is invalid.
  8. Explain why assigning JSON.parse output to unknown creates a safer boundary than allowing any property access.
  9. Explain why an as WorkItem assertion would not validate one runtime property.
  10. State one similarity and one execution-time difference between the TypeScript and PHP examples.
  11. Restore the complete valid example and confirm that the Playground shows no type error before you stop.

The required lesson is complete when the valid Playground program passes the type check, accepts the valid JSON, rejects the tested invalid JSON, and you can explain which evidence comes from static checking and which comes from runtime behavior.

Continue to Set up a React and TypeScript project with Vite to move from the browser Playground into the continuing course repository.

If you stop here, leave this resume note: The static work-item model and runtime validator both pass. Next, create the local Vite project and make its type check and production build explicit.