Interfaces and Type Aliases

The API Contract That Saved Our Integration

It was Thursday afternoon when our mobile team pinged me on Slack: "The user endpoint is returning different fields randomly. Sometimes we get firstName, sometimes first_name. The app is crashing."

I pulled up our Node.js API code:

// user-service.js
function getUser(id) {
  return db.query('SELECT * FROM users WHERE id = ?', [id])
    .then(user => {
      // Sometimes we do this
      return { firstName: user.first_name, lastName: user.last_name };
      // Other times we forget and return raw DB fields
      return user;
    });
}

No contract. No consistency. Just JavaScript objects that could be anything.

The mobile team had built their models based on the first response they saw. When I refactored the backend, I changed field names without realizing it. Result: 3,000 users stuck on a broken app screen.

I spent that weekend migrating to TypeScript and defining explicit interfaces:

interface User {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  createdAt: Date;
}

interface UserResponse {
  user: User;
  metadata: {
    lastLogin: Date;
    status: 'active' | 'suspended' | 'deleted';
  };
}

function getUser(id: string): Promise<UserResponse> {
  // TypeScript won't let me return the wrong shape
  return db.query('SELECT * FROM users WHERE id = ?', [id])
    .then(row => ({
      user: {
        id: row.id,
        firstName: row.first_name,  // Explicit mapping
        lastName: row.last_name,
        email: row.email,
        createdAt: new Date(row.created_at)
      },
      metadata: {
        lastLogin: new Date(row.last_login),
        status: row.status
      }
    }));
}

The migration took 2 days. We caught 43 different inconsistencies during compilation. No more runtime surprises. The mobile team could finally trust our API contract.

This article covers everything about interfaces and type aliases that I wish I'd learned before that incident.


Object Types

TypeScript's core strength: describing object shapes.

Inline Object Types

Object Type Inference


Interfaces: Defining Contracts

Interfaces describe object shapes that can be implemented and extended.

Basic Interface

Optional Properties

Readonly Properties

Real-world use case from my API project:


Interface Extension

Interfaces can extend other interfaces, building complex types from simpler ones.

Single Extension

Multiple Extension

Production pattern I use everywhere:


Type Aliases

Type aliases create new names for types, including primitives, unions, tuples, and complex types.

Basic Type Alias

Object Type Aliases

Union Type Aliases


Interfaces vs Type Aliases

When I started with TypeScript, this confused me the most. Here's what I learned:

Key Differences

My Decision Framework

Use Interfaces When:

  • Defining object shapes

  • Building public APIs (better error messages)

  • Working with classes

  • Might need declaration merging (libraries)

Use Type Aliases When:

  • Defining union types

  • Defining tuple types

  • Creating utility types

  • Aliasing primitives

  • Complex type manipulations

Real example from my codebase:


Index Signatures

When object property names aren't known ahead of time.

String Index Signature

Number Index Signature

Mixed Index Signatures

Real-world use from my feature flag system:


Nested Interfaces

Complex data structures often need nested types.

Simple Nesting

Deep Nesting

Pattern I use to keep types manageable:


Function Properties in Interfaces

Interfaces can describe object methods.

Method Syntax

Property Syntax

Both are valid, but I prefer method syntax for consistency:


Hybrid Types

Objects that act as both a function and an object.

Honestly? I rarely use this pattern. It's more common in library code.


Real-World Example: API Response Types

Here's how I structure a real REST API with TypeScript:


Practical Patterns from Production

1. Discriminated Unions with Interfaces

2. Builder Pattern

3. Configuration Pattern


Common Mistakes I Made

1. Over-Nesting

Bad:

Good:

2. Using any in Interfaces

Bad:

Good:

3. Not Using Optional Properties

Bad:

Good:


Your Challenge

Build a type-safe task management system:


Key Takeaways

  1. Interfaces define contracts - explicit shape of objects

  2. Optional properties with ? - not all fields required

  3. Readonly properties - prevent modification after creation

  4. Interfaces extend - build complex types from simple ones

  5. Type aliases - unions, tuples, complex types

  6. Index signatures - dynamic property names

  7. Interfaces vs Types - use interfaces for objects, types for everything else

  8. Generic interfaces - reusable type-safe structures


What I Learned

Before TypeScript, my API contracts were implicit. Documentation (when it existed) was always out of sync. The mobile team would discover field changes in production.

After adopting interfaces:

  • 43 bugs caught during compilation

  • Zero field mismatch incidents in 6 months

  • API documentation generated from types

  • Confidence in refactoring - compiler validates everything

The Thursday afternoon incident taught me: types are communication. Interfaces aren't just for the compiler - they're documentation, contracts, and team agreements rolled into one.

When I write:

I'm not just helping TypeScript. I'm telling my team, the mobile developers, future maintainers, and my future self: this is exactly what you'll get.

No surprises. No runtime detective work. Just clear, explicit contracts.

Interfaces made my code boring. And boring code doesn't break at 3 AM.


Next: Functions in TypeScript

In the next article, we'll explore TypeScript's powerful function typing system. You'll learn how function signatures prevented a critical authentication bug in my production app.

Last updated