Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Modules: import, export and import type

Learn how modules work in TypeScript: exporting and importing types and values, import type and type-only exports, verbatimModuleSyntax, file extensions in imports with nodenext, path aliases, and organising types across a project.

Advanced5 min readUpdated
On this page

TypeScript uses the same ES module syntax as JavaScript (Modules): export and import. The difference is that you can also export and import types, and TypeScript has a few extra rules about how imports are written.

Exporting types and values#

models.ts (TypeScript)
export interface Course {
  id: string;
  title: string;
  lessons: number;
}

export type Level = "beginner" | "intermediate" | "advanced";

export function describe(course: Course, level: Level): string {
  return `${course.title} (${level}, ${course.lessons} lessons)`;
}
main.ts (TypeScript)
import { describe, type Course, type Level } from "./models.js";

const ts: Course = { id: "ts", title: "TypeScript", lessons: 39 };
const level: Level = "intermediate";
console.log(describe(ts, level));   // TypeScript (intermediate, 39 lessons)

Types and interfaces are exported and imported just like functions. At runtime, the type exports simply disappear.

import type#

Writing type before an imported name (or import type { ... } for the whole statement) marks it as type-only:

TypeScript
import type { Course, Level } from "./models.js";      // the whole import is types
import { describe, type Course } from "./models.js";   // mixed: one value, one type

Type-only imports are guaranteed to be removed from the compiled JavaScript. This matters because tools that compile files one at a time, or that only strip types (esbuild, SWC, Node.js type stripping), cannot always tell whether an import is a type. If a type import were left in the output, the program could fail at runtime looking for an export that does not exist.

The verbatimModuleSyntax compiler option makes this explicit: imports without type are always kept, and the compiler reports an error when you import a type without marking it. It is recommended for modern projects (tsconfig.json).

File extensions in imports#

With "module": "nodenext" (used for Node.js projects), relative imports must include the extension of the output file, which is .js, even though the source file is .ts:

TypeScript
import { describe } from "./models.js";   // correct: models.ts compiles to models.js
import { describe } from "./models";      // error under nodenext

This looks odd at first, but it matches exactly what Node.js will load at runtime. Projects using a bundler (Vite, webpack, Next.js) usually set "moduleResolution": "bundler", which allows extensionless imports because the bundler resolves them.

Importing packages#

Packages installed with npm are imported by name. Many include their own type definitions; for others, types come from separate @types/... packages (Declaration Files):

TypeScript
import { readFile } from "node:fs/promises";   // types from @types/node
import path from "node:path";

console.log(typeof readFile, path.extname("lesson.ts"));
Output
function .ts

Path aliases#

Long relative paths like ../../../components/Button.js are hard to read. The paths compiler option defines aliases:

tsconfig.json (JSON)
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
TypeScript
import { Button } from "@/components/Button";

paths only tells TypeScript how to resolve the alias; your bundler or runtime must be configured to resolve it the same way. Frameworks such as Next.js set up the @/* alias for you. (This website uses exactly this alias.)

Organising types#

  • Keep types next to the code that uses them when only one module needs them.
  • Put types shared across the app in a dedicated module, such as src/types/ or models.ts.
  • Derive types from a single source where possible (ReturnType, typeof, schema validators) instead of writing the same shape twice.
  • Export types that callers need: if a function's parameter type is not exported, users of your module cannot name it.

Practice#

Practice exercise

Split this code into shapes.ts (the Shape type and the area function, both exported) and main.ts (which imports them, using a type-only import for Shape):

TypeScript
type Shape = { kind: "circle"; r: number } | { kind: "square"; side: number };
function area(s: Shape): number { return s.kind === "circle" ? Math.PI * s.r ** 2 : s.side ** 2; }
const shapes: Shape[] = [{ kind: "square", side: 3 }];
console.log(shapes.map(area));
Show solution
shapes.ts (TypeScript)
export type Shape = { kind: "circle"; r: number } | { kind: "square"; side: number };

export function area(s: Shape): number {
  return s.kind === "circle" ? Math.PI * s.r ** 2 : s.side ** 2;
}
main.ts (TypeScript)
import { area, type Shape } from "./shapes.js";

const shapes: Shape[] = [{ kind: "square", side: 3 }];
console.log(shapes.map(area));   // [ 9 ]

Key takeaway#

Finished this lesson?