
TypeScriptのinterfaceとは?オブジェクト型の定義と使い方を解説
TypeScriptのinterfaceを使ったオブジェクト型の定義方法を解説。typeとの違いやネスト構造、実務で使える設計パターンを初心者向けに紹介します。
全8回シリーズ — 目次
1 TypeScriptとは
2 環境構築
3 基本の型
4 関数と型
5 インターフェイス
6 クラス
7 実践的な型
8 便利機能まとめ
🎯 この記事のゴール
interface とtype を使ってデータ構造を型で表現できるようになること。LaravelやREST APIのレスポンスを型定義する実践例まで解説します。 実行ファイルを用意しよう 実行ファイルを用意しよう
今回用のファイルを作成します。Bunの場合はinterfaces.ts 、Node.jsの場合はsrc/interfaces.ts です。
Bunの場合
touch interfaces.ts
bun --watch run interfaces.ts Node.js + npm の場合
touch src/interfaces.ts
npx tsc --watch # 別ターミナルで起動
node dist/interfaces.js interface とは何か
第3回でオブジェクトの型をインラインで書く方法を学びました。プロパティが増えると冗長になってしまいます。
// プロパティが増えるほど読みにくくなる
const user: { id: number; name: string; email: string; age: number } = {
id: 1, name: "Alice", email: "alice@example.com", age: 28,
};interface を使うと型に名前を付けて再利用できます。読みやすさが格段に上がり、複数の変数や関数で同じ型を共有できます。
// interface で型に名前をつける
interface User {
id: number;
name: string;
email: string;
age: number;
isAdmin: boolean;
}
// 変数に使う
const alice: User = {
id: 1, name: "Alice", email: "alice@example.com", age: 28, isAdmin: false,
};
// 関数の引数にも使える
function printUser(user: User): void {
console.log(`[${user.id}] ${user.name} <${user.email}>`);
}
printUser(alice); // → [1] Alice <alice@example.com>
// 定義にないプロパティはエラー
const bob: User = {
id: 2, name: "Bob", email: "bob@example.com", age: 32, isAdmin: true,
phone: "090-0000-0000", // エラー: 'phone' は User に存在しない
}; Bunの場合
bun run interfaces.ts
#[1] Alice <alice@example.com> Node.js + npmの場合
npx tsc && node dist/interfaces.js
#[1] Alice <alice@example.com> interface の便利機能
オプショナルプロパティと readonly
? で省略可能なプロパティ、readonly で変更不可なプロパティを定義できます。
interface Product {
readonly id: number; // 変更不可
name: string;
price: number;
description?: string; // 省略可能
tags?: string[]; // 省略可能な配列
}
const item: Product = {
id: 101,
name: "TypeScript本",
price: 2800,
// description と tags は省略OK
};
console.log(item.name); // → TypeScript本
console.log(item.description); // → undefined
item.id = 999; // エラー: readonly プロパティには代入不可 extends — インターフェースの継承
extends を使うと、既存の interface を拡張した新しい型を作れます。共通プロパティを親に定義し、差分だけ子に書くのが実務での定石です。
interface BaseEntity {
readonly id: number;
createdAt: string;
updatedAt: string;
}
// BaseEntity のプロパティをすべて引き継いで拡張
interface User extends BaseEntity {
name: string;
email: string;
}
interface Post extends BaseEntity {
title: string;
body: string;
userId: number;
}
const user: User = {
id: 1, createdAt: "2025-01-01", updatedAt: "2025-04-01",
name: "Alice", email: "alice@example.com",
};
const post: Post = {
id: 10, createdAt: "2025-03-01", updatedAt: "2025-03-10",
title: "TypeScript入門", body: "TypeScriptは...", userId: 1,
};
console.log(user.name, user.createdAt); // → Alice 2025-01-01
console.log(post.title, post.userId); // → TypeScript入門 1 Bunの場合
bun run interfaces.ts
#Alice 2025-01-01
#TypeScript入門 1 Node.js + npmの場合
npx tsc && node dist/interfaces.js
#Alice 2025-01-01
#TypeScript入門 1 type との違い — 何を使えばいいのか
type (型エイリアス)もinterface と似た使い方ができます。どちらを使えばよいか迷う方が多いので、違いを整理します。
interface
オブジェクトの「形」を定義することに特化。extends による継承が使えて、同名で宣言を重ねると自動でマージされる。
type
あらゆる型に名前をつけられる。Union型・Intersection型・プリミティブへの別名など、interface では書けない型も表現できる。
| 機能 | interface | type |
|---|---|---|
| オブジェクトの型定義 | OK | OK |
| extends による継承 | OK | NG (& で代替可) |
| 宣言マージ(同名で追加定義) | OK | NG |
Union 型A | B | NG | OK |
| プリミティブへの別名 | NG | OK |
| Mapped Types・Conditional Types | NG | OK |
// interface:オブジェクト形状の定義に使う(推奨)
interface Article {
id: number;
title: string;
body: string;
}
// type:Union型・プリミティブ別名はtypeで書く
type ID = number | string; // Union型
type Status = "draft" | "published" | "archived"; // リテラル Union
type Point = { x: number; y: number }; // typeでもオブジェクトを書ける
// Intersection(&)で型を合成する
type ArticleWithStatus = Article & { status: Status };
const draft: ArticleWithStatus = {
id: 1, title: "下書き", body: "...", status: "draft",
};
console.log(draft.title, draft.status); // → 下書き draft Bunの場合
bun run interfaces.ts
#下書き draft Node.js + npmの場合
npx tsc && node dist/interfaces.js
#下書き draft✨ 実務での使い分け指針
オブジェクトの形状定義にはinterface 、Union型・リテラル型・プリミティブ別名にはtype と使い分けると迷いが減ります。チームで統一さえしていれば、どちらでも大きな問題はありません。 ネスト構造 — 複雑なデータを型で表現する
実際のデータはオブジェクトの中にオブジェクトや配列が入ることがほとんどです。interface は入れ子にできます。
interface Address {
zipCode: string;
city: string;
street: string;
}
interface Company {
name: string;
address: Address; // ← Address を入れ子にする
}
interface Employee {
id: number;
name: string;
company: Company; // ← Company を入れ子にする
skills: string[]; // ← 配列も持てる
}
const emp: Employee = {
id: 1,
name: "田中 太郎",
company: {
name: "サンプル株式会社",
address: {
zipCode: "100-0001",
city: "東京都千代田区",
street: "丸の内 1-1-1",
},
},
skills: ["TypeScript", "React", "Node.js"],
};
console.log(emp.name); // → 田中 太郎
console.log(emp.company.address.city); // → 東京都千代田区
console.log(emp.skills.join(", ")); // → TypeScript, React, Node.js Bunの場合
bun run interfaces.ts
#田中 太郎
#東京都千代田区
#TypeScript, React, Node.js Node.js + npmの場合
npx tsc && node dist/interfaces.js
#田中 太郎
#東京都千代田区
#TypeScript, React, Node.js 実践:APIレスポンスの型設計
LaravelやREST APIでよくある「ページネーション付きの一覧取得レスポンス」を例に、実務で使える型設計を見てみましょう。
まずJSONの構造を確認します。
// GET /api/posts のレスポンス(JSON)
{
"data": [
{
"id": number,
"title": string,
"body": string,
"status": "draft" | "published",
"author": { id: number, name: string },
"publishedAt": string | null
}
],
"meta": {
"total": number,
"perPage": number,
"currentPage": number,
"lastPage": number
}
}このJSONをそのまま TypeScript の型に落とし込みます。
// 再利用する小さな型から定義する
type PostStatus = "draft" | "published";
interface Author {
id: number;
name: string;
}
interface Post {
id: number;
title: string;
body: string;
status: PostStatus;
author: Author;
publishedAt: string | null; // null になり得るプロパティ
}
interface PaginationMeta {
total: number;
perPage: number;
currentPage: number;
lastPage: number;
}
// ページネーション付きレスポンスのラッパー
interface PaginatedResponse<T> {
data: T[];
meta: PaginationMeta;
}
// 実際のAPIレスポンスを模擬したデータ
const response: PaginatedResponse<Post> = {
data: [
{
id: 1,
title: "TypeScript入門",
body: "TypeScriptは...",
status: "published",
author: { id: 10, name: "Alice" },
publishedAt: "2025-04-01",
},
{
id: 2,
title: "Bun入門",
body: "Bunとは...",
status: "draft",
author: { id: 10, name: "Alice" },
publishedAt: null, // 下書きは null
},
],
meta: { total: 24, perPage: 10, currentPage: 1, lastPage: 3 },
};
// データを安全に操作できる
response.data.forEach((post) => {
const published = post.publishedAt ?? "未公開";
console.log(`[${post.status}] ${post.title} by ${post.author.name} (${published})`);
});
console.log(`全${response.meta.total}件 / ${response.meta.lastPage}ページ`); Bunの場合
bun run interfaces.ts
#[published] TypeScript入門 by Alice (2025-04-01)
#[draft] Bun入門 by Alice (未公開)
#全24件 / 3ページ Node.js + npmの場合
npx tsc && node dist/interfaces.js
#[published] TypeScript入門 by Alice (2025-04-01)
#[draft] Bun入門 by Alice (未公開)
#全24件 / 3ページ💡 ジェネリクスの先取り
上の例で使ったPaginatedResponse<T> は ジェネリクス の基本形です。T の部分に任意の型を渡すことで、Post だけでなくUser やProduct のページネーションにも同じラッパー型を使い回せます。詳しくは第8回で扱います。📌 第5回のまとめ
interface でオブジェクトの形を定義し、extends で継承する方法を学びました。 type との使い分けは「オブジェクト形状は interface、Union・リテラルは type」が実務の指針です。APIレスポンスを型で表現できるようになれば、フロントエンド開発の安全性が大きく向上します。次回はクラスと型(OOP編)です。📝 ▶ 次のステップ
次は「 」を解説します