Skip to main content

TypeScript Types

TypeScript extends JavaScript by adding a robust type system that helps you catch errors early during development rather than at runtime. This is especially valuable when working with Next.js applications, as it helps create more maintainable and reliable code.

Introduction to TypeScript Types

TypeScript is a superset of JavaScript that adds static typing to the language. While JavaScript is dynamically typed (meaning variables can change types at runtime), TypeScript allows you to define specific types for your variables, function parameters, return values, and more.

The benefits of TypeScript's type system include:

  • Error detection during development: Catch type-related errors before running your code
  • Improved IDE support: Get better autocomplete, hints, and documentation
  • Self-documenting code: Types serve as built-in documentation
  • Safer refactoring: Make changes with confidence, knowing the type system will catch incompatibilities

Basic Types

Let's start with the fundamental types in TypeScript:

Primitive Types

typescript
// Number
let age: number = 25;
let price: number = 99.99;

// String
let firstName: string = "John";
let greeting: string = `Hello, ${firstName}!`;

// Boolean
let isActive: boolean = true;
let hasPermission: boolean = false;

// Undefined & Null
let u: undefined = undefined;
let n: null = null;

Arrays

Arrays can be typed in two ways:

typescript
// Using square bracket notation
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];

// Using generic Array type
let scores: Array<number> = [85, 92, 78];
let fruits: Array<string> = ["Apple", "Banana", "Orange"];

Objects and Interfaces

For objects, TypeScript uses interfaces or type aliases to define their shape:

typescript
// Using interface
interface User {
id: number;
name: string;
email: string;
isAdmin?: boolean; // Optional property (note the ?)
}

const newUser: User = {
id: 1,
name: "John Doe",
email: "[email protected]"
// isAdmin is optional, so we can omit it
};

// Using type alias
type Product = {
id: number;
title: string;
price: number;
inStock: boolean;
};

const laptop: Product = {
id: 101,
title: "MacBook Pro",
price: 1999,
inStock: true
};

Union Types

Union types allow a variable to have multiple types:

typescript
// A variable that can be either a string or a number
let id: string | number;

id = "abc123"; // Valid
id = 456; // Valid
// id = true; // Error: Type 'boolean' is not assignable to type 'string | number'

// Function that accepts different types
function printId(id: string | number) {
console.log(`ID: ${id}`);
}

printId("A12345");
printId(42);

Type Aliases

Type aliases create custom names for types:

typescript
type UserID = string | number;

function getUserById(id: UserID) {
// Implementation...
}

// Using the alias
const userId1: UserID = 123;
const userId2: UserID = "user_abc";

Function Types

TypeScript allows you to specify parameter types and return types for functions:

typescript
// Basic function type annotation
function add(a: number, b: number): number {
return a + b;
}

// Arrow function with types
const multiply = (x: number, y: number): number => x * y;

// Function with optional parameter
function greet(name: string, greeting?: string): string {
return greeting ? `${greeting}, ${name}!` : `Hello, ${name}!`;
}

console.log(greet("Alice")); // "Hello, Alice!"
console.log(greet("Bob", "Welcome")); // "Welcome, Bob!"

// Function with default parameter
function calculateTax(amount: number, taxRate: number = 0.1): number {
return amount * taxRate;
}

Advanced Types

Type Assertions

Sometimes you know more about a value's type than TypeScript does. Type assertions let you tell the compiler "trust me, I know what I'm doing":

typescript
// Scenario: We know this will be an HTMLInputElement
const emailInput = document.getElementById("email") as HTMLInputElement;

// Now we can access input-specific properties
emailInput.value = "[email protected]";

// Alternative syntax (less common, doesn't work in JSX)
const passwordInput = <HTMLInputElement>document.getElementById("password");

Intersection Types

Intersection types combine multiple types into one:

typescript
type Employee = {
id: number;
name: string;
department: string;
};

type Manager = {
subordinates: number;
meetings: boolean;
};

// Combines both types
type ManagerEmployee = Employee & Manager;

const seniorManager: ManagerEmployee = {
id: 101,
name: "Jane Smith",
department: "Engineering",
subordinates: 12,
meetings: true
};

Literal Types

You can specify exact values that a variable can have:

typescript
// Only these specific string values are allowed
type Direction = "North" | "South" | "East" | "West";

function move(direction: Direction) {
console.log(`Moving ${direction}`);
}

move("North"); // Valid
// move("Up"); // Error: Argument of type '"Up"' is not assignable to parameter of type 'Direction'

// Number literals work too
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;

function rollDice(): DiceValue {
return Math.floor(Math.random() * 6) + 1 as DiceValue;
}

TypeScript in Next.js

TypeScript is particularly useful in Next.js applications. Here are some practical examples:

Typing Props in Components

typescript
// Defining prop types for a component
interface UserProfileProps {
user: {
id: number;
name: string;
email: string;
avatar?: string;
};
showEmail: boolean;
}

// Using the types in a Next.js component
const UserProfile: React.FC<UserProfileProps> = ({ user, showEmail }) => {
return (
<div className="user-profile">
<h2>{user.name}</h2>
{user.avatar && <img src={user.avatar} alt={`${user.name}'s avatar`} />}
{showEmail && <p>Email: {user.email}</p>}
</div>
);
};

Typing API Responses and Requests

typescript
// Define the shape of your API response
interface Post {
id: number;
title: string;
content: string;
author: {
name: string;
id: number;
};
createdAt: string;
}

// In your Next.js page
import { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps = async (context) => {
const response = await fetch('https://api.example.com/posts');
const posts: Post[] = await response.json();

return {
props: {
posts
}
};
};

// Then in your component
interface BlogPageProps {
posts: Post[];
}

const BlogPage: React.FC<BlogPageProps> = ({ posts }) => {
return (
<div>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>By {post.author.name}</p>
<div>{post.content}</div>
</article>
))}
</div>
);
};

Utility Types

TypeScript provides several built-in utility types that transform existing types:

Partial

Makes all properties optional:

typescript
interface User {
id: number;
name: string;
email: string;
age: number;
}

// All properties are optional here
function updateUser(userId: number, updates: Partial<User>) {
// Implementation...
}

// Valid call with only a subset of properties
updateUser(1, {
name: "Alice Smith",
age: 32
});

Pick and Omit

Pick creates a new type by picking specific properties, while Omit creates a type by excluding properties:

typescript
interface Product {
id: number;
name: string;
price: number;
category: string;
description: string;
inStock: boolean;
}

// Only these properties are included
type ProductPreview = Pick<Product, 'id' | 'name' | 'price'>;

const preview: ProductPreview = {
id: 1,
name: "Ergonomic Chair",
price: 299
// Other fields would cause an error
};

// These properties are excluded
type ProductWithoutInventory = Omit<Product, 'inStock'>;

const newProduct: ProductWithoutInventory = {
id: 2,
name: "Standing Desk",
price: 499,
category: "Furniture",
description: "Adjustable height desk for better posture"
// No inStock property needed
};

Best Practices for TypeScript with Next.js

  1. Create type definitions for your data structures:

    typescript
    // In a types.ts file
    export interface User {...}
    export interface Post {...}
  2. Use proper typing for API routes:

    typescript
    // pages/api/users.ts
    import type { NextApiRequest, NextApiResponse } from 'next';
    import { User } from '../../types';

    export default function handler(
    req: NextApiRequest,
    res: NextApiResponse<User[] | { error: string }>
    ) {
    // Implementation...
    }
  3. Leverage TypeScript for component props:

    typescript
    interface ButtonProps {
    primary?: boolean;
    size?: 'small' | 'medium' | 'large';
    label: string;
    onClick?: () => void;
    }

    const Button: React.FC<ButtonProps> = ({
    primary = false,
    size = 'medium',
    label,
    onClick
    }) => {
    // Component implementation
    };

Summary

TypeScript's type system provides significant benefits when building Next.js applications:

  • It helps catch errors during development rather than at runtime
  • It improves code readability and maintainability
  • It enables better IDE support with autocompletion and inline documentation
  • It makes refactoring safer and easier

The key concepts covered in this guide include:

  • Basic types (primitive types, arrays, objects)
  • Union and intersection types
  • Type aliases and interfaces
  • Function typing
  • Advanced type features (assertions, generics, utility types)
  • Practical applications in Next.js components and API routes

Additional Resources

Practice Exercises

  1. Create a TypeScript interface for a blog post with properties for title, content, author, and publication date.
  2. Write a function that filters an array of blog posts by author, with proper typing.
  3. Create a Next.js component that displays a list of products with appropriate TypeScript interfaces.
  4. Define utility types for a user authentication system in a Next.js application.
  5. Create a type-safe API route handler for a form submission in Next.js.

With these foundations, you're well equipped to leverage TypeScript's powerful type system in your Next.js projects!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)