tsup path alias beats ts-node nodemon express typescript
| |

Why Tsup Beats Nodemon and Ts-Node for Path Alias in TypeScript


To answer the title’s questions, it’s because no extra configuration is required for tsup! I have previously talked about how we can get a typescript projected started with tsup, typescript and express. I have also mentioned that we did not configure tsconfig.json, so let us tackle this part with path aliases!

Note that for simplicity sake, you’d find parts where am telling you to just add lines. in those cases, you should just add those lines rather than replacing the entire file.

Setting up the Project

Let’s start by setting up a new simple typescript project project using tsup, express, and typescript. We’ll also create a tsconfig.json file to configure path aliases.

Initialize Node Project

Create a new directory for your project and navigate into it:

mkdir my-project
cd my-project

Initialize a new node project. I use Yarn, but you can do the same for npm or pnpm:

yarn init -y

Since we’re at it, let’s add in our dev and build scripts in the package.json file:

"scripts": {
	"dev": "tsup src/index.ts --watch --onSuccess 'node dist/index.js'",
  "build": "tsup src/index.ts"

Install Dependencies

Install the required dependencies:

yarn add express
yarn add -D typescript tsup express @types/express

Configure tsconfig.json

So for this step, we won’t overcomplicate the situation and we’ll just initialize with the default tsconfig.json, and we can do that with this command:

yarn tsc --init

and it will generate a relatively large json file that has all sorts of commented configurations. the cleaned up version will look as such:

  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true

there are some of you that may wonder why I used yarn tsc init instead of tsc init, and I have a future blog post that details why you should avoid using global npm CLI commands.

Initialize Express Server

Create a new TypeScript file called index.ts in a src directory:

mkdir src
touch src/index.ts

Add the following code to src/index.ts:

import express from 'express';

const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello World!');

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);

finally, we can try out to run our code with:

yarn dev

navigating to http://localhost:3000, you should be able to see a page with Hello, World! in it.

How to Configure Path Aliases with Tsup

Here’s the fun part, we just need to configure our tsconfig.json for it, and tsup will read from that file to understand our path aliases when bundling, which is awesome!

so we’ll configure our tsconfig.json by adding baseUrl and paths properties in the compilerOptions section as such:

"compilerOptions": {
  "baseUrl": ".",
  "paths": {
    "@/*": ["src/*"]

So what is Path Alias exactly?

In the example tsconfig.json above, we define @/* as a path alias for src/*. This means that any import statement that starts with @/ will be resolved to src/.

Regardless of in which file we want to import another file from, it will always be the same:

import { User } from '@/models/user';

as oppose to the messy ../../ directory traversal that we have without path alias configured:

import { User } from '../../models/user';

Demonstrating Path Alias

Let’s create a simple example to demonstrate this. We’ll create a User model in a file called user.ts in the src/models directory, and then import it into index.ts.

Create a new file called user.ts in the src/models directory. If you are running those commands, then make sure you are at the project’s root:

mkdir src/models
touch src/models/user.ts

Add the following code to src/models/user.ts:

export const User = (name: string, email: string) => {
  return { name, email };

Import the User model into index.ts using a path alias:

import express from "express";
import { User } from "@/models/user";

const app = express();
const port = 3000;

const user = User("Alice", "alice@example.com");

app.get("/", (req, res) => {
  res.send("Hello World!");

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);

Now when you run the project with yarn dev , and we should see the User object logged to the console.

yarn dev


{ name: 'Alice', email: 'alice@example.com' }
Server listening at http://localhost:3000

And there we have it! super simple to set up.

Since tsup bundles everything into a single javascript file, then it also flattens the import statements to be from the same bundled file. What happens in this case is that as a byproduct, our bundled index.js file will not have any import statements with the @/ path alias. This means that we don’t need to do any module resolution like how we did before when we used nodemon and ts-node.

This is a wonderful thing, and has previously bugged me for the longest time when I used ts-node.

Sing up to get an email notification when new content is published

Subscription Form

One Comment

Leave a Reply

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

Similar Posts