Modules in TypeScript: Import and Export
Modules: Import and Export
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
We've been using import/export consistently since chapter 7, without explaining them in detail. Time to catch up – including the finer points specific to TypeScript's type system.
Named exports: the pattern used so far
// Book.ts:
export interface Book { /* ... */ }
export type BookStatus = 'available' | 'borrowed' | 'reserved';
// index.ts:
import { Book, BookStatus } from './models/Book.js';"Named", because EVERY export has its OWN name, and on import EXACTLY that name (in curly braces) must be used – a file can have ANY number of named exports, EXACTLY the pattern we've used throughout.
Default exports: ONE main export per file
// Library.ts:
export default class Library { /* ... */ }
// index.ts:
import Library from './services/Library.js'; // NO curly braces,
// name freely chosen at import timeAchtung: Our project DELIBERATELY used export class Library (named export) instead of export default class Library – named exports are today's recommended practice, for a concrete reason: with named exports, the imported name MUST match the exported one (editor renames stay CONSISTENT across the whole project); with default exports, EVERY importing file can choose a DIFFERENT name – that easily leads to confusion in large projects.
Type-only imports: compile time only
import type { Book } from './models/Book.js'; // explicitly ONLY a type, no runtime import
import { Book } from './models/Book.js'; // works EXACTLY the same for interfaces,
// but less EXPLICITLY documentedimport type makes it EXPLICIT that ONLY type information is being imported, which disappears ENTIRELY at compile time (see chapter 1) – for an interface like Book, that's already the case anyway (interfaces NEVER exist at runtime), but import type makes this INTENT immediately clear to readers of the code, EXACTLY as we already saw in "React for Professionals" for RootState/AppDispatch.
Barrel exports: a central collecting export
As the number of models GROWS, it becomes tedious to import EVERY file individually – a "barrel" (collecting file) bundles multiple exports:
export * from './Book.js';
export * from './Author.js';
export * from './Audiobook.js';
export * from './Medium.js';
export * from './Genre.js';
export * from './HasIsbn.js';// Instead of FOUR separate imports:
import { Book } from './models/Book.js';
import { Author } from './models/Author.js';
import { Genre } from './models/Genre.js';
// ONE bundled import:
import { Book, Author, Genre } from './models/index.js';Achtung: Barrel exports are convenient, but have a known downside in larger projects: ALL files re-exported by the barrel potentially get LOADED as soon as ANYTHING is imported from the barrel – in very large projects, this can needlessly inflate compile time/bundle size. For our manageable library project, that's not an issue, but an important point for later, larger projects.
A trap: circular imports
// A.ts imports from B.ts, B.ts imports from A.ts -
// sometimes works "by accident", but easily breaks on refactors
// and is generally a sign of poorly separated responsibilitiesOur model hierarchy (Medium → Book/Audiobook, Author independent) is DELIBERATELY built as a pure "tree" with no cycles – Author.ts NEVER imports from Book.ts, even though Book.ts imports Author. This ONE-WAY dependency direction avoids circular imports from the ground up.
Tipp: Rule of thumb: export (named) for EVERYTHING in this tutorial, import type for pure type imports (makes intent clear), barrel exports ONLY when genuinely needed, avoid circular imports upfront through deliberate, ONE-WAY dependency direction.