Skip to main content

TypeScript Interfaces

In modern web development with Next.js, TypeScript has become an essential tool for building robust and maintainable applications. One of TypeScript's most powerful features is interfaces, which help you define the structure of objects and create clear contracts for your code.

What Are TypeScript Interfaces?

Interfaces in TypeScript serve as a blueprint for objects, defining the properties and methods that an object should have. Think of interfaces as contracts or agreements about the shape of data in your application.

Basic Syntax

An interface is declared using the interface keyword followed by the name of the interface (usually starting with an uppercase letter) and a set of property and method definitions.

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

Using Interfaces for Objects

Let's see how we can use interfaces to define the structure of objects in our Next.js applications.

Example: Creating a User Object

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

// Using the interface
const newUser: User = {
id: 1,
name: "John Doe",
email: "[email protected]",
isActive: true
};

// TypeScript will show an error if we miss a property
const invalidUser: User = {
id: 2,
name: "Alice",
// Error: Property 'email' is missing
isActive: false
};

Optional Properties

Sometimes you might want to make certain properties optional. You can do this by adding a question mark (?) after the property name.

typescript
interface BlogPost {
title: string;
content: string;
publishedDate: Date;
author: string;
tags?: string[]; // Optional property
featuredImage?: string; // Optional property
}

const post: BlogPost = {
title: "Getting Started with TypeScript",
content: "TypeScript is a powerful superset of JavaScript...",
publishedDate: new Date(),
author: "Jane Smith"
// tags and featuredImage are optional, so we can omit them
};

Readonly Properties

To prevent modification of certain properties after an object is created, you can use the readonly modifier.

typescript
interface Config {
readonly apiKey: string;
readonly baseUrl: string;
timeout: number; // This can be modified
}

const appConfig: Config = {
apiKey: "abc123xyz",
baseUrl: "https://api.example.com",
timeout: 3000
};

// This is allowed
appConfig.timeout = 5000;

// Error: Cannot assign to 'apiKey' because it is a read-only property
appConfig.apiKey = "new-key";

Extending Interfaces

Interfaces can inherit properties from other interfaces using the extends keyword, promoting code reuse.

typescript
interface Person {
name: string;
age: number;
}

interface Employee extends Person {
employeeId: string;
department: string;
salary: number;
}

const developer: Employee = {
name: "Sarah Johnson",
age: 28,
employeeId: "EMP001",
department: "Engineering",
salary: 85000
};

Implementing Interfaces in Classes

In Next.js applications, you might have classes that need to conform to a specific structure. Interfaces can enforce that structure.

typescript
interface Vehicle {
brand: string;
speed: number;
start(): void;
stop(): void;
}

class Car implements Vehicle {
brand: string;
speed: number;

constructor(brand: string) {
this.brand = brand;
this.speed = 0;
}

start(): void {
console.log(`${this.brand} car starting...`);
this.speed = 10;
}

stop(): void {
console.log(`${this.brand} car stopping...`);
this.speed = 0;
}
}

const myCar = new Car("Toyota");
myCar.start();
console.log(`Current speed: ${myCar.speed}mph`);
myCar.stop();

Output:

Toyota car starting...
Current speed: 10mph
Toyota car stopping...

Function Types with Interfaces

Interfaces can define the shape of functions, specifying parameter types and return values.

typescript
interface MathFunction {
(x: number, y: number): number;
}

const add: MathFunction = (a, b) => a + b;
const multiply: MathFunction = (a, b) => a * b;

console.log(add(5, 3)); // Output: 8
console.log(multiply(5, 3)); // Output: 15

Interface vs. Type Alias

TypeScript offers both interfaces and type aliases, which can seem similar. Here's a quick comparison:

typescript
// Interface
interface User {
name: string;
email: string;
}

// Type alias
type UserType = {
name: string;
email: string;
};

Key differences:

  • Interfaces can be extended with the extends keyword, while types use intersection (&)
  • Interfaces can be reopened and added to, types cannot
  • Interfaces are often preferred for public APIs as they are more extensible

Practical Example: Creating a Next.js API Response Type

Here's a practical example of using interfaces in a Next.js application to type API responses:

typescript
// Define the structure of a product from our API
interface Product {
id: string;
name: string;
price: number;
description: string;
imageUrl: string;
inStock: boolean;
}

// Define the API response structure
interface ProductApiResponse {
success: boolean;
data: Product[];
total: number;
page: number;
pageSize: number;
}

// In a Next.js page or component
async function fetchProducts(): Promise<ProductApiResponse> {
const response = await fetch('/api/products');
const data: ProductApiResponse = await response.json();
return data;
}

// Using the typed response
async function ProductList() {
const { data: products } = await fetchProducts();

return (
<div>
<h1>Product List</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
{!product.inStock && <span> (Out of stock)</span>}
</li>
))}
</ul>
</div>
);
}

Index Signatures

When you need to create an object with dynamic keys, you can use index signatures:

typescript
interface Dictionary {
[key: string]: string;
}

const colors: Dictionary = {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF"
};

// You can add any string key
colors.purple = "#800080";

Hybrid Types

Interfaces can describe objects that act as both functions and objects with properties:

typescript
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function createCounter(): Counter {
const counter = ((start: number) => {
return `Counter started at ${start}`;
}) as Counter;

counter.interval = 123;
counter.reset = function() {
console.log('Counter reset');
};

return counter;
}

const c = createCounter();
console.log(c(10)); // Output: Counter started at 10
console.log(c.interval); // Output: 123
c.reset(); // Output: Counter reset

Summary

TypeScript interfaces are a powerful tool for defining object shapes and contracts in your Next.js applications. They help you:

  • Define clear structures for objects and functions
  • Create contracts that classes must implement
  • Allow for code completion and error checking in your editor
  • Document your code in a way that TypeScript can verify

By using interfaces effectively, you can write more maintainable, self-documenting code with fewer runtime errors.

Additional Resources

Exercises

  1. Create an interface for a blog post with properties for title, content, author, and publication date.
  2. Extend that interface to create a FeaturedBlogPost interface with additional properties.
  3. Create a class that implements an interface of your choice.
  4. Define an interface for a function that takes two strings and returns a boolean.
  5. Create an interface with optional properties and readonly properties, then try to use it in a practical example.


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