M Logo
๐Ÿ“ TypeScript from Zero ยท Part 24 of 50

TypeScript Modules: import, export, import type and File Extensions

November 6, 2026

Welcome to episode 24 of TypeScript from Zero, a series for complete beginners (no JavaScript needed). Welcome back to TypeScript from zero! Until now every program lived in one file. Real programs are split into many files, and each file is a module. Today: export and import. We are in the shop project, and we start with a new file called math.ts. Every code block below is the exact code and output from the video, ready to copy.

Setup

We start in the terminal and open the project in VS Code:

bash
cd shop
touch src/math.ts
code .

1. export

The word export makes something visible to other files. Add and PI are named exports. A file can also have one default export, and here that is the function double.

2. import

Now a second file, main.ts. We import the default one without braces, and the named ones inside curly braces. Notice the ending.ts in the path. Node runs the file directly, so it needs the real file name. We run main, and it prints five, the value of PI, and eight.

src/main.ts:

ts
import double, { add, PI } from "./math.ts";
console.log(add(2, 3), PI, double(4));

src/math.ts:

ts
export function add(a: number, b: number) {
  return a + b;
}
export const PI = 3.14159;
export default function double(n: number) {
  return n * 2;
}
text
$ node src/main.ts
5 3.14159 8

3. tsc

Node is happy, but tsc is not. An import path can only end with.ts when allow importing T S extensions is enabled. TS5097. We could turn on that option, but there is a better one for a project that we build later.

text
$ npx tsc --noEmit
src/main.ts:1:33 - error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

1 import double, { add, PI } from "./math.ts";
                                  ~~~~~~~~~~~

Found 1 error in src/main.ts:1

4. Option

We open the T S config file and add rewrite relative import extensions, set to true. It says: .ts paths are fine in my code, and when I build the project, write them as.js in the output. Now tsc is quiet.

tsconfig.json:

ts
{
  // Visit https://aka.ms/tsconfig to read more about this file
  "compilerOptions": {
    // File Layout
    "rootDir": "./src",
    "outDir": "./dist",

    // Environment Settings
    // See also https://aka.ms/tsconfig/module
    "module": "nodenext",
    "target": "esnext",
    "rewriteRelativeImportExtensions": true,
    "types": ["node"],
    // For nodejs:
    // "lib": ["esnext"],
    // "types": ["node"],
    // and npm install -D @types/node

    // Other Outputs
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,

    // Stricter Typechecking Options
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,

    // Style Options
    // "noImplicitReturns": true,
    // "noImplicitOverride": true,
    // "noUnusedLocals": true,
    // "noUnusedParameters": true,
    // "noFallthroughCasesInSwitch": true,
    // "noPropertyAccessFromIndexSignature": true,

    // Recommended Options
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
  }
}
bash
$ npx tsc --noEmit

5. Extension

What if we leave the ending out, like in many older tutorials? Node says: cannot find module. tsc complains as well: relative import paths need explicit file extensions, and it suggests.js. For us the right ending is.ts.

src/main.ts:

ts
import double, { add, PI } from "./math";
console.log(add(2, 3), PI, double(4));
text
$ node src/main.ts
node:internal/modules/esm/resolve:241
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/mah/shop/src/math' imported from /home/mah/shop/src/main.ts
    at finalizeResolution (node:internal/modules/esm/resolve:241:11)
    at moduleResolve (node:internal/modules/esm/resolve:846:10)
    at defaultResolve (node:internal/modules/esm/resolve:973:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
    at #resolve (node:internal/modules/esm/loader:690:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:389:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///home/mah/shop/src/math'
}

Node.js v26.13.0
text
$ npx tsc --noEmit
src/main.ts:1:33 - error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './math.js'?

1 import double, { add, PI } from "./math";
                                  ~~~~~~~~

Found 1 error in src/main.ts:1

6. Fix

We write the ending again, and it works.

src/main.ts:

ts
import double, { add, PI } from "./math.ts";
console.log(add(2, 3), PI, double(4));
text
$ node src/main.ts
5 3.14159 8

7. Types file

Types can be exported too. In a new file types.ts we export an interface called User, and a ready made user.

8. Type import

Back in main, we import both. User is only a type, it disappears when Node strips the types. So at runtime there is nothing to import, and Node stops with a syntax error: the module does not provide an export named User. tsc warns us before that. TS1484: User is a type and must be imported using a type only import.

src/main.ts:

ts
import { defaultUser, User } from "./types.ts";
const u: User = defaultUser;
console.log(u.name);
import double, { add, PI } from "./math.ts";
console.log(add(2, 3), PI, double(4));

src/types.ts:

ts
export interface User {
  name: string;
  age: number;
}
export const defaultUser: User = { name: "Mia", age: 30 };
text
$ node src/main.ts
file:///home/mah/shop/src/main.ts:1
import { defaultUser, User } from "./types.ts";
                      ^^^^
SyntaxError: The requested module './types.ts' does not provide an export named 'User'
    at #asyncInstantiate (node:internal/modules/esm/module_job:463:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:561:5)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:105:5)

Node.js v26.13.0
text
$ npx tsc --noEmit
src/main.ts:1:23 - error TS1484: 'User' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.

1 import { defaultUser, User } from "./types.ts";
                        ~~~~

Found 1 error in src/main.ts:1

9. Fix

We import the value normally, and the type on its own line with the words import type. Now Node knows that this line is only for tsc, and removes it.

src/main.ts:

ts
import { defaultUser } from "./types.ts";
import type { User } from "./types.ts";
const u: User = defaultUser;
console.log(u.name);
import double, { add, PI } from "./math.ts";
console.log(add(2, 3), PI, double(4));
text
$ node src/main.ts
Mia
5 3.14159 8
bash
$ npx tsc --noEmit

Recap

Today: export and default export, import with the file ending, the T S config option for it, the extension error, and import type for types. Next episode: asynchronous code, promises and await.

ยฉ 2026 All Rights Reserved By Mahmudul Alam