How to Set Up Path Aliases in TypeScript + Node.js (ESM) [2025 Guide]

Leonardo phoenix 10 a clean modern tech blog thumbnail featuri 1 (1)

Are you tired of long, ugly relative imports like: ../../../utils/logger? You’re not alone. In this guide, you’ll learn how to set up path aliases like #utils/logger that work with both TypeScript and Node.js ESM—in both development and production environments.

🎯 Why Use Path Aliases?

  • ✅ Clean and readable import paths
  • ✅ Simplifies project refactoring
  • ✅ Supported in modern Node.js and TypeScript

📁 Example Project Structure

ts-alias-demo/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts
│   └── utils/
│       └── logger.ts

⚙️ Step 1: tsconfig.json Setup

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "baseUrl": "./src",
    "paths": {
      "#utils/*": ["utils/*"]
    },
    "rootDir": "./src",
    "outDir": "./dist",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "resolveJsonModule": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

⚙️ Step 2: package.json Imports Field

{
  "type": "module",
  "imports": {
    "#utils/*": "./src/utils/*"
  }
}

Important: Use a # prefix in import maps and ensure "type": "module" is set in package.json.

🧪 Example Code

logger.ts

export function log(message: string) {
  console.log("[LOG]:", message);
}

index.ts

import { log } from "#utils/logger.js"; //  .js extension required in ESM<!-- wp:code -->
<pre class="wp-block-code"><code lang="javascript" class="language-javascript">export function log(message: string) {
  console.log("[LOG]:", message);
}
</code></pre>
<!-- /wp:code -->

log("Hello from path alias!");

🧪 Scripts

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

🚀 Run It

npm install
npm run dev      # Development (with tsx)
npm run build    # Compiles TypeScript
npm start        # Runs compiled dist/index.js

🐞 Common Errors & Fixes

ErrorFix
Cannot find module '#utils/logger'Ensure alias is defined in both tsconfig.json and package.json
Must use '.js' extensionAlways use .js in imports when using ESM
Works in dev, breaks in prodMake sure Node >= 18 and ESM is configured properly

📦 Tools & References

❓ Frequently Asked Questions (FAQ)

1. What are path aliases in TypeScript?

Path aliases let you simplify module imports like ../../../utils/logger to something cleaner like #utils/logger.


2. How do I enable path aliases in TypeScript?Define baseUrl and paths in tsconfig.json. Example: "paths": { "#utils/*": ["utils/*"] }.
3. Why is Node.js not recognizing my path alias?

Node.js (ESM) needs aliases defined in package.json under the imports field, starting with a # prefix.
4. Do I need to use the .js extension in ESM?

Yes. When using ESM in Node.js, you must include file extensions in imports (e.g., #utils/logger.js).
5. What does “Module not found” error mean with aliases?

It usually means the alias isn’t correctly configured in tsconfig.json or package.json. Check both.
6. Which Node.js version supports import maps?

Node.js 16+ with ESM supports import maps via package.json. For best results, use Node.js 18 or later.
7. Can I use aliases with CommonJS?

No, import maps are only supported with ESM in Node.js. CommonJS doesn’t support imports field.
8. Does this setup work with tsx dev runner?

Yes! tsx supports TypeScript aliases and ESM import maps out of the box. It’s the easiest dev setup.
9. Do I need Babel or Webpack for this?

No. Modern Node.js with ESM and TypeScript handles aliases without needing Babel or Webpack.
10. Can I alias other folders like services or config?

Yes. Just add them to tsconfig.json and package.json. Example: "#services/*": "./src/services/*".
11. Why do aliases work in VSCode but fail at runtime?

VSCode uses tsconfig.json for type resolution, but Node.js needs package.json aliases at runtime.
12. How do I build with aliases for production?

Use tsc to compile your TypeScript, then run node dist/index.js with the correct imports in place.
13. Can I alias .json files?

Yes, if "resolveJsonModule": true is enabled in tsconfig.json, and the path is defined in package.json.
14. Can I use aliasing with test frameworks like Jest?

Yes, but you’ll need to configure Jest’s moduleNameMapper to match your alias paths.
15. What is verbatimModuleSyntax in tsconfig?

It prevents TypeScript from transforming import/export syntax, keeping output compatible with ESM behavior.
16. Should I use baseUrl or rootDirs?

baseUrl is required for paths to work. rootDirs is used for merging virtual directories, not for aliasing.
17. Is it safe to use # in aliases?

Yes. Node.js ESM import maps require aliases to start with #. It’s a future-safe standard.
18. Do aliases work in monorepos?

Yes, but you must define aliases per package and ensure inter-package imports use workspace paths or TS project references.
19. Can I use aliases with ts-node?

Not directly. It’s better to use tsx or a bundler like esbuild for dev. ts-node doesn’t resolve `imports` from package.json.
20. Can I share this setup in a boilerplate repo?

Absolutely! This setup is perfect for boilerplates and scaffolding TypeScript + Node.js projects with ESM and clean imports.

Leave a Reply

Your email address will not be published. Required fields are marked *