How to configure and resolve path alias with a Typescript Project
Table of Contents
What is a path alias?
Path alias is a way to define an absolute path in your typescript project with a word, path or a character.
a very common syntax you will see is replacing the src
directory with @
The advantage of using a path alias is that you no longer need to use a lot of directory traversals in your imports.
instead of using:
import { userRouter } from '../../routers/userRouter'
you would use:
import { userRouter } from '@/routers/userRouter'
The beauty of this solution is that you no longer need to do long path traversals to import your packages, and it doesn’t matter where your file is, as the import statement will always be the same.
I have also covered how to configure path alias for react and storybook, you can check them out here:
Starter Project
So let us start with a sample mock project. You can go ahead with the starter repository that we have here:
we’re using yarn
for this one, so let us install the dependencies.
yarn
This project does not have any functionality implemented, but it’s structured like how a typical express app will be.
if we go to src/routes/admin/usersRouter.ts
, we will find an example of the situation of directory traversal that I am talking about.
import { /**/ } from '../../controllers/usersController';
and if we go to src/routes/tasksRouter.ts
, we will find a more barable import statement, but the issue is that we are traversing differently due to the location of the file.
import { /**/ } from '../controllers/tasksController';
this can get really annoying during development because our path to the controllers
directory is always relative to where we are. Things can quickly get out of hand the more we expand the project.
How to configure Typescript Path Alias
We can do this by adding the following lines to our tsconfig.json
file.
"compilerOptions": { "baseUrl": ".", "paths": { "@/*": [ "./src/*" ] }, /*...*/ }
make sure that you are adding the baseUrl
and paths
lines inside the compilerOptions
property.
what our configuration here is doing is that we are defining the src
dirctory to have an alias of @/
What that’s going to do for us is that we can now refer to the src
directory at any directory depth in our project by using @/
. meaning, instead of the previous import statements, we can now use:
import { /**/ } from '@/controllers/usersController';
The linter will not be giving out any errors if we import our modules that way, and vscode will be able to figure out what @/
is referring to.
so we’ll change usersRouter.ts
to this:
import express from 'express'; import { getUsers } from '@/controllers/usersController'; export const usersRouter = express.Router(); usersRouter.get('/', getUsers);
and tasksRouter
to this:
import express from 'express'; import { createTask, deleteTask, getSingleTask, getTasks, updateTask, } from '@/controllers/tasksController'; export const tasksRouter = express.Router(); tasksRouter.get('/', getTasks); tasksRouter.get('/:taskId', getSingleTask); tasksRouter.get('/', createTask); tasksRouter.get('/:taskId', updateTask); tasksRouter.get('/:taskId', deleteTask);
Now if we attempt to build our project, Typescript won’t be giving us any errors.
we can build the project with this command:
yarn build # this runs -> tsc
This will generate our compiled project in javascript, and it will be in the dist
folder.
if we attempt to run the compiled code with this command:
yarn start # this runs -> node dist/index.js
then we’ll get this error:
internal/modules/cjs/loader.js:892 throw err; ^ Error: Cannot find module '@/controllers/usersController' ... code: 'MODULE_NOT_FOUND', ...
This is happening because the tsconfig.json
we have only applies to our typescript code. Node is not aware of this configuration when it comes to the compiled Javascript code. So we will need to configure it so it cna understand the path alias that we configured.
Note about the path alias resolution options
for each of the provided options, they assume that you are continuining from the “How to configure Typescript Path Alias” section, and they do not build on top of each other.
How to resolve Javascript path alias with module-alias
There are various ways to do this, but the easiest thing we can do is by using a package called module-alias. This solution is specific to resolving the path aliases found in the compiled Javascript code
Install the module-alias packge
yarn add module-alias
Configure package.json
We will need a property to our package.json, called _moduleAliases
{ ... "_moduleAliases": { "@": "dist" }, "scripts": { "build": "tsc", "start": "node dist/index.js" }, ... }
in there, we will let i know what our alias configuration is, but we want it to know to resolve it for the javascript compiled code.
this means that we will have to point to dist
rather than src
, as our javascript code is going to import the modules from there.
also notice that we did not add the trailing /
after the @
in our configuration.
Import the module in index.ts
finally we’ll import the module itself, this will be in our typescript app’s entry file, typically /src/index.ts
.
We will add this line at the top of the file, before any import.
import 'module-alias/register'; // 👈 add this one import express from 'express'; import { valdiateAdminRole } from './middlewares/validateAdminRole'; import { validateToken } from './middlewares/validateToken'; import { usersRouter } from './routes/admin/usersRouter'; import { tasksRouter } from './routes/tasksRouter'; const app = express(); app.use('/api/tasks', validateToken, tasksRouter); app.use('/api/admin/users', validateToken, valdiateAdminRole, usersRouter); app.listen(5000, () => { console.log('server started at port 5000!'); });
Build the project again
yarn build # this runs -> tsc
Start the Javascript Project
yarn start # this runs -> node dist/index.js
The project will start without issues here!
How to resolve a Typescript path alias with ts-node
With ts-node, the same steps we did for node configuration that are in the previous section will work as well.
Tough there is another way to do it, which is through the package tsconfig-paths.
Once again, if we attempt to run the app with ts-node
, we will get this error:
$ ts-node src/index.ts Error: Cannot find module '@/controllers/usersController' Require stack:
so to configure the resolution part, we will:
Install the tsconfig-paths module
yarn add -D ts-node tsconfig-paths
run ts-node with tsconfig-paths
if you have ts-node
installed globally:
ts-node -r tsconfig-paths/register src/index.ts
if you have ts-node
installed for just this project:
yarn ts-node -r tsconfig-paths/register src/index.ts
so the important part is we register the resolver package through the argument -r tsconfig-paths/register
for neat-ness sake, we can add this as a script in our package.json file like so:
"version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "dev": "ts-node -r tsconfig-paths/register src/index.ts", "build": "tsc", "start": "node dist/index.js" }, ...
and then we can just run our code like so:
yarn dev
How to resolve path alias with ts-node and Nodemon
This approach might be the most comfortable one to have during development. It resolves the path alias, runs the code, and reloads it when the code is updated.
Install the packages
yarn add -D nodemon ts-node tsconfig-paths
configure nodemon
At the root directory of our repository, we’ll create a nodemon.json
file.
in that file, we’ll add the following:
{ "ignore": ["**/*.test.ts", "**/*.spec.ts", ".git", "node_modules"], "watch": ["src"], "exec": "node -r tsconfig-paths/register -r ts-node/register ./src/index.ts", "ext": "ts, js" }
ignore
is set to ignore the files we do not want to watch. this would be the unit testing files, git and node moduels.
watch
mark our src
directory as the one to watch for any of its changes. If a change is detected there, then it will re-run the exec
command.
exec
contains the command that we want to run whenever a file that we are tracking in watch
changes.
ext
filters the type of files we want to watch for.
add a script for development
in package.json
, we’ll simply add this line under scripts
:
.... "license": "MIT", "scripts": { "dev": "nodemon", .... }, ....
run the code
yarn dev
and from this point on, our code runs while it understands the path alias, and it reloads when we update our code!
Great job, this was exactly what I was looking for with using the `module-alias` part of your blog. Appreciate it!
Glad it helped!
Module alias helped. Thank you!