Overview
Add TypeScript checking to JavaScript gradually with allowJs, checkJs, and JSDoc.
TS in JS Projects helps you describe intent in code so mistakes are caught earlier. Treat types as documentation that the compiler can verify, not as noise added after the fact.
Core Ideas
- Use TS in JS Projects to make data shapes and function contracts explicit.
- Prefer precise types over broad ones, especially at module boundaries.
- Narrow unknown input before using it.
- Let compiler errors guide better modeling instead of disabling checks.
Step by Step
- Write the JavaScript shape that TS in JS Projects describes.
- Add the narrowest useful type annotation.
- Use the value in a function so the compiler can verify the contract.
- Intentionally break the shape once, then read the compiler error.
Beginner Explanation
TS in JS Projects connects TypeScript to real project environments such as Node.js, React, JavaScript migration, community type packages, and modern TypeScript updates.
Project TypeScript is more than syntax. It includes package types, module settings, framework conventions, build tools, editor feedback, and team rules.
Beginners should adopt TypeScript gradually and keep the compiler strict enough to help without blocking learning.
Before You Start
- Before practicing TS in JS Projects, write the JavaScript value or function you want to describe.
- Identify the boundary: function parameter, return value, object shape, API response, component prop, or configuration option.
- Turn on strict compiler settings when possible so weak assumptions become visible.
- Change one type at a time and read the compiler message before fixing it.
- Remember that TypeScript checks code before runtime; runtime validation is still needed for untrusted external data.
Key TypeScript Concepts
- @types packages add types for JavaScript libraries.
- React components need typed props, events, refs, state, and children.
- Node.js projects need module settings that match the runtime.
- Migration works best when checking is introduced gradually and strictness increases over time.
Plain-English Glossary
- Type annotation: a written type such as string, number, Lesson, or Promise<User>.
- Inference: TypeScript deciding a type from the value or usage.
- Union: a type that allows one of several choices.
- Narrowing: checking a value so TypeScript can treat it as a more specific type.
- Generic: a reusable type parameter such as T that preserves information.
- Interface: an extendable object contract.
- Type alias: a name for any type expression, including unions and object shapes.
- tsconfig: the project file that controls compiler behavior.
What You Will Learn
- Explain what kind of mistake TS in JS Projects helps TypeScript catch.
- Write a small typed example and predict whether the compiler should accept or reject it.
- Read at least one compiler error and identify the expected type and actual type.
- Refactor the example so the type describes the real data more clearly.
Where You Use This in Real Projects
You use TS in JS Projects in forms, API clients, React components, Node.js services, configuration files, data models, reusable utilities, validation layers, tests, and shared packages.
TypeScript is most valuable at boundaries where values move between files, functions, packages, APIs, users, databases, and UI components.
A practical team uses TypeScript to reduce accidental misuse, improve editor autocomplete, and make refactoring less risky.
Compiler and Tooling Notes
- Use strict mode for new code when possible.
- Do not turn a compiler error into any unless you understand what safety is being removed.
- Remember that TypeScript types disappear at runtime after compilation.
- Validate JSON, form data, URL params, and external API responses at runtime before trusting them.
- When a type becomes hard to read, split it into named aliases and test it with small examples.
Beginner Mental Model
Think of TS in JS Projects as TypeScript meeting the real world: packages, frameworks, build tools, JavaScript files, and team conventions.
The type system works best when project configuration and library types match how the code actually runs.
Common Compiler Errors and Fixes
- Type mismatch: the value used in TS in JS Projects does not match the expected type. Fix the value or correct the type model.
- Missing property: an object does not include a required field. Add the field, make it optional only if it is truly optional, or use a different type.
- Possibly null or undefined: check the value before using it, use optional chaining, or provide a fallback.
- Unsafe any: replace any with a specific type or unknown plus a narrowing check.
- No matching overload or call signature: compare the arguments you passed with the function type and adjust the call.
Real-World Pattern
// Shared between React, Node.js, and API clients.
export type LessonDto = {
slug: string;
title: string;
updatedAt: string;
};
export function assertLessonDto(value: unknown): value is LessonDto {
return typeof value === 'object' && value !== null && 'slug' in value && 'title' in value;
}
- This TS in JS Projects pattern uses a small type at a real boundary instead of adding types everywhere at random.
- The example keeps runtime code readable while giving the compiler enough information to catch mistakes.
- You can reuse the same pattern in API clients, form handlers, components, services, tests, and shared utilities.
Code Example
// Node.js or React projects often share typed data contracts.
export type LessonDto = {
slug: string;
title: string;
tags: string[];
};
export function createLessonUrl(lesson: LessonDto): string {
return `/learn/typescript/${lesson.slug}`;
}
Another Example
// React-style props
type LessonCardProps = {
title: string;
completed?: boolean;
onStart: (slug: string) => void;
};
function startLesson(props: LessonCardProps, slug: string) {
props.onStart(slug);
}
More Practice Examples
Example 1: Model lesson data
type Level = 'beginner' | 'intermediate' | 'advanced';
interface Lesson {
slug: string;
title: string;
level: Level;
minutes: number;
}
const lesson: Lesson = {
slug: 'ts-practice',
title: 'TypeScript Practice',
level: 'beginner',
minutes: 30
};
- The Level union prevents accidental values such as easy or expert.
- The Lesson interface names the object shape once so it can be reused.
- The compiler checks missing properties and wrong value types before runtime.
Example 2: Narrow unknown input
function formatTitle(value: unknown): string {
if (typeof value === 'string' && value.trim() !== '') {
return value.trim();
}
return 'Untitled lesson';
}
console.log(formatTitle(' TS Functions '));
- unknown forces the function to check the value before using string methods.
- The return type tells callers they will always receive a string.
- This pattern is useful for form input, JSON, and third-party data.
Example 3: Reuse a generic response type
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: string };
function unwrap<T>(result: ApiResult<T>): T {
if (!result.ok) throw new Error(result.error);
return result.data;
}
- The generic T preserves the data type through the helper.
- The union makes success and failure explicit.
- The if statement narrows result before data is returned.
Beginner Practice Examples
Practice: Type component-like props
type ButtonProps = {
label: string;
disabled?: boolean;
onClick(): void;
};
function renderButton(props: ButtonProps): string {
return props.disabled ? `${props.label} disabled` : props.label;
}
- The optional disabled field can be omitted.
- The callback has no parameters and no useful return value.
- This mirrors the way React and design-system components are typed.
Practice: Add a safe formatter
function minutesLabel(minutes: number | null | undefined): string {
if (minutes == null) {
return 'Time not set';
}
return `${minutes} minutes`;
}
console.log(minutesLabel(35));
- The parameter allows number, null, or undefined.
- The null check handles both null and undefined.
- After the check, TypeScript knows minutes is a number.
Practice: Type a small API result
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: string };
const result: Result<string[]> = {
ok: true,
data: ['HTML', 'CSS', 'TypeScript']
};
- The generic result can work with any data type.
- The ok field acts as the discriminant for narrowing.
- Failure results must include error instead of data.
Compiler Error Practice
Error 1: Wrong value type
Broken version
type Lesson = {
title: string;
minutes: number;
};
const lesson: Lesson = {
title: 'TS Basics',
minutes: '30'
};
Fixed version
type Lesson = {
title: string;
minutes: number;
};
const lesson: Lesson = {
title: 'TS Basics',
minutes: 30
};
- The broken version uses a string where the type requires a number.
- The fixed version changes the value instead of weakening the type.
- This is the kind of mistake TS in JS Projects should catch before runtime.
Error 2: Possibly undefined value
Broken version
const lessons = ['HTML', 'CSS'];
const first = lessons[2];
console.log(first.toUpperCase());
Fixed version
const lessons = ['HTML', 'CSS'];
const first = lessons[2];
if (first) {
console.log(first.toUpperCase());
} else {
console.log('No lesson found');
}
- The array lookup may not find an item.
- The fixed version checks the value before using a string method.
- This habit helps with arrays, maps, query selectors, and API results.
Error 3: Unsafe unknown value
Broken version
function printTitle(value: unknown) {
console.log(value.trim());
}
Fixed version
function printTitle(value: unknown) {
if (typeof value === 'string') {
console.log(value.trim());
return;
}
console.log('Untitled');
}
- unknown cannot be used like a string until it is narrowed.
- The typeof check proves the value is a string.
- Use this pattern whenever data comes from outside your typed code.
Example Explained
- The TS in JS Projects example starts with a real JavaScript value, function, object, or project setting.
- The type describes the smallest useful contract for that value.
- The compiler can then compare how the value is created, passed, and used.
- When the example narrows a value, TypeScript allows safer access only after the check.
- The best examples keep runtime behavior and type behavior easy to explain separately.
How to Read This Example
- Find the value, function, class, object, or configuration option being typed.
- Read the type from left to right and name what values are allowed.
- Look for optional properties, unions, generics, or narrowing checks.
- Change one property or argument to the wrong type and predict the compiler error.
- Fix the TS in JS Projects example by improving the type model, not by hiding the error with any.
Checklist
- Let the compiler guide the design instead of silencing errors.
- Prefer unknown over any when input is not trusted yet.
- Keep shared types close to the data they describe.
Common Mistakes
- Using any to silence errors instead of modeling the data.
- Typing only the easy parts and leaving unsafe boundaries unchecked.
- Duplicating types instead of deriving them with utility types or generics.
Do and Don't
- Do: use TS in JS Projects to describe real data and real function contracts.
- Do: prefer unknown over any when data has not been checked yet.
- Do: let inference help with local variables when the value is obvious.
- Don't: add complicated types that make the code harder to use than the bug they prevent.
- Don't: use type assertions to silence errors before proving the value is safe.
Practice Challenge
Type the TS in JS Projects example, intentionally break one value, and read the compiler message before fixing it.
Try These Changes
- Add one missing property to the example and read the compiler error.
- Change one string literal to an invalid value and explain why TypeScript rejects it.
- Replace any with unknown, then add a narrowing check.
- Extract an inline object type into a named interface or type alias.
- Write one function that accepts the type and returns a formatted display string.
Step-by-Step Project Path
- Create one small file named after TS in JS Projects and write a plain JavaScript version first.
- Add the first type at the boundary: function parameter, object shape, API result, component props, or config value.
- Intentionally break one value so you can read the compiler error in context.
- Fix the value or improve the type without using any as a shortcut.
- Add one runtime check if the value could come from a user, API, URL, file, or third-party package.
- Write a short comment for yourself explaining which bug the type prevents.
Quick Check
- Question: Do TypeScript types exist at runtime? Answer: No, they are checked before the JavaScript runs.
- Question: Why is unknown safer than any? Answer: unknown requires narrowing before you use the value.
- Question: What should you type first? Answer: Boundaries such as parameters, return values, API data, and props.
- Question: What is inference? Answer: TypeScript deciding a type from the value or context.
- Question: How should you fix most TS in JS Projects errors? Answer: Adjust the value or model the type more accurately.
Debugging Checks
- Read the first compiler error carefully before changing multiple files.
- Identify the expected type and the actual type in the error message.
- Hover values in the editor to inspect inferred types.
- Check tsconfig strictness, module settings, lib settings, and included files when errors seem surprising.
- Use small temporary variables to make complicated inferred types easier to inspect.
Mini Project
Build an integration note for TS in JS Projects: show how TypeScript fits with Node.js, React, package types, tooling, or gradual migration.
Mastery Check
- You can explain what error TS in JS Projects catches before runtime.
- You can narrow unknown data before using it.
- You can reuse the type safely in another function or module.