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
Error | Fix |
---|---|
Cannot find module '#utils/logger' | Ensure alias is defined in both tsconfig.json and package.json |
Must use '.js' extension | Always use .js in imports when using ESM |
Works in dev, breaks in prod | Make 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.