We’ve all been there. You’re working on a growing project, and suddenly your import statements look like a game of directory leapfrog: import { UserService } from '../../../../services/user.service'. It’s fragile, ugly, and a nightmare to refactor.

TypeScript provides a built-in way to solve this via paths in tsconfig.json. However, there is a massive catch: TypeScript does not rewrite these paths during compilation. When you run tsc, your beautiful @services/user alias remains exactly like that in the generated JavaScript, which causes Node.js to crash because it has no idea what @services means.

That’s where tsc-alias comes in. In this guide, I’ll show you exactly how to use tsc-alias in TypeScript to bridge this gap and keep your production code as clean as your source code.

Prerequisites

Step 1: Configure Path Aliases in tsconfig.json

Before we can use tsc-alias, we need to tell TypeScript which aliases we want to use. Open your tsconfig.json and locate the compilerOptions section.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@models/*": ["src/models/*"],
      "@services/*": ["src/services/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

Note: The baseUrl is required when using paths. I usually set it to "." to ensure paths are resolved relative to the project root.

Step 2: Install tsc-alias

Since we only need this tool during the build process, we install it as a development dependency.

npm install --save-dev tsc-alias

Step 3: Update Your Build Script

This is the crucial part. Instead of just running tsc, we need to run tsc-alias immediately after the compilation is finished. This tool scans your output folder (usually dist or build) and replaces the aliases with actual relative paths that Node.js understands.

Update your package.json scripts as follows:

{
  "scripts": {
    "build": "tsc && tsc-alias"
  }
}

Now, whenever you run npm run build, the process happens in two stages: First, TypeScript compiles the code; second, tsc-alias cleans up the paths. As shown in the image below, the final JavaScript output will no longer contain the @ symbols, but will instead use corrected relative paths.

Comparison of compiled JavaScript before and after running tsc-alias
Comparison of compiled JavaScript before and after running tsc-alias

Step 4: Testing the Implementation

To verify it’s working, create a file in src/services/api.ts and import something using an alias in src/index.ts:

// src/index.ts
import { ApiClient } from '@services/api';

const client = new ApiClient();
console.log('Connected!');

Run your build command: npm run build. Check your dist/index.js file. You should see that @services/api has been converted back to something like ./services/api.

Pro Tips for Better Project Structure

Troubleshooting Common Issues

Issue: “Module not found” during runtime

If you’re still seeing errors, double-check that your outDir in tsconfig.json matches where you expect the files to be. tsc-alias looks for the compiled files; if it can’t find them, it won’t replace anything.

Issue: tsc-alias isn’t updating files

Ensure you are running tsc before tsc-alias. If you use a build tool like Webpack or Vite, you might not need tsc-alias because those tools handle path resolution during bundling. This tool is primarily for projects compiled directly to JS for Node.js execution.

What’s Next?

Now that you’ve cleaned up your imports, you might want to look at other ways to optimize your development workflow. If you’re still early in your journey, I highly recommend my migrate from javascript to typescript guide to ensure your project is set up for long-term success.

Ready to scale your app? Start implementing these patterns today and stop the relative path madness!