DEV Community

Lucas M Dev
Lucas M Dev

Posted on

TypeScript in 10 Minutes: From JavaScript to Type Safety

You know JavaScript. TypeScript is just JavaScript with types. Here's everything you need to start.

Why bother with TypeScript?

// JavaScript — no errors until runtime
function greet(user) {
  return `Hello, ${user.nme}`; // typo! "nme" instead of "name"
}

greet({ name: "Alice" }); // Returns "Hello, undefined" — silent bug!
Enter fullscreen mode Exit fullscreen mode
// TypeScript — catches errors at compile time
function greet(user: { name: string }): string {
  return `Hello, ${user.nme}`; // Error: Property 'nme' does not exist
}
Enter fullscreen mode Exit fullscreen mode

TypeScript catches bugs before you run the code. That's the whole deal.

Setup in 30 seconds

npm install -D typescript tsx @types/node

# tsconfig.json
npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Or use TypeScript directly with Node:

npx tsx my-file.ts
Enter fullscreen mode Exit fullscreen mode

The 8 types you'll use 90% of the time

// 1. Primitives
let name: string = "Alice";
let age: number = 28;
let active: boolean = true;

// 2. Arrays
let tags: string[] = ["ts", "js"];
let scores: number[] = [95, 87, 92];
let mixed: (string | number)[] = ["a", 1, "b", 2];

// 3. Objects (inline)
let user: { name: string; age: number } = { name: "Alice", age: 28 };

// 4. Functions
function add(a: number, b: number): number {
  return a + b;
}

const greet = (name: string): string => `Hello, ${name}!`;

// 5. Union types
let id: string | number = "abc123";
id = 42; // also valid

type Status = "pending" | "active" | "inactive";
let userStatus: Status = "active";

// 6. Optional properties (?)
type User = {
  name: string;
  email: string;
  phone?: string; // optional
};

// 7. Any (avoid this — it disables type checking)
let data: any = fetchData();

// 8. Unknown (safer than any)
let response: unknown = fetch("/api");
if (typeof response === "string") {
  console.log(response.toUpperCase()); // OK — narrowed to string
}
Enter fullscreen mode Exit fullscreen mode

Interfaces vs Types

// Interface — for objects (recommended for public APIs)
interface User {
  id: number;
  name: string;
  email: string;
}

// Extends
interface AdminUser extends User {
  permissions: string[];
}

// Type alias — more flexible, works for primitives, unions, etc.
type ID = string | number;
type Nullable<T> = T | null;
type UserOrAdmin = User | AdminUser;

// Both work for objects — use either, be consistent
Enter fullscreen mode Exit fullscreen mode

Generics: Write once, use with any type

// Without generics — duplicated code
function getFirstString(arr: string[]): string | undefined {
  return arr[0];
}
function getFirstNumber(arr: number[]): number | undefined {
  return arr[0];
}

// With generics — one function, any type
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

getFirst(["a", "b", "c"]); // string | undefined
getFirst([1, 2, 3]);        // number | undefined
getFirst([{ id: 1 }]);      // { id: number } | undefined
Enter fullscreen mode Exit fullscreen mode

Common utility types

type User = {
  id: number;
  name: string;
  email: string;
  password: string;
};

// Partial — all properties optional
type UpdateUser = Partial<User>;
// { id?: number; name?: string; email?: string; password?: string }

// Required — all properties required
type FullUser = Required<UpdateUser>;

// Pick — select some properties
type PublicUser = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string }

// Omit — exclude some properties
type SafeUser = Omit<User, "password">;
// { id: number; name: string; email: string }

// Record — key-value map
type UserMap = Record<string, User>;
const users: UserMap = {
  "alice": { id: 1, name: "Alice", email: "alice@example.com", password: "..." },
};

// ReturnType
function fetchUser() { return { id: 1, name: "Alice" }; }
type FetchedUser = ReturnType<typeof fetchUser>;
// { id: number; name: string }
Enter fullscreen mode Exit fullscreen mode

Type narrowing

type Response = { success: true; data: User } | { success: false; error: string };

function handleResponse(res: Response) {
  if (res.success) {
    // TypeScript knows this is { success: true; data: User }
    console.log(res.data.name);
  } else {
    // TypeScript knows this is { success: false; error: string }
    console.log(res.error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Quick wins in your existing JS code

// Add return types to functions
function calculateTotal(items: CartItem[]): number {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}

// Type your API responses
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

async function fetchUsers(): Promise<ApiResponse<User[]>> {
  const res = await fetch("/api/users");
  return res.json();
}

// Type React props
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: "primary" | "secondary" | "danger";
  disabled?: boolean;
}

function Button({ label, onClick, variant = "primary", disabled = false }: ButtonProps) {
  return <button onClick={onClick} className={variant} disabled={disabled}>{label}</button>;
}
Enter fullscreen mode Exit fullscreen mode

TypeScript cheat sheet

What Syntax
String let x: string
Number let x: number
Boolean let x: boolean
Array let x: string[]
Object let x: { key: type }
Union `let x: string \
Optional {% raw %}property?: type
Function (arg: type): returnType => {}
Generic function f<T>(x: T): T
Interface interface Name { ... }
Type alias type Name = ...
Partial Partial<Type>
Pick `Pick<Type, 'key1' \
Omit {% raw %}Omit<Type, 'key'>
Record Record<string, Type>

Next steps

  1. Start migrating one file at a time — rename .js to .ts
  2. Fix the type errors (let TypeScript guide you)
  3. Add types to function parameters first
  4. Gradually add stricter tsconfig settings

The any type is a crutch — use it to get started, but aim to remove it over time.


Questions? Drop them in the comments — happy to help!

Top comments (0)