Skip to content

TypeScript Projects

The TypeScript project generator can be used to create a modern TypeScript library or application configured with best practices such as ECMAScript Modules (ESM), TypeScript project references, Vitest for running tests and Biome for linting and formatting.

You can generate a new TypeScript project in two ways:

  1. Install the Nx Console VSCode Plugin if you haven't already
  2. Open the Nx Console in VSCode
  3. Click Generate (UI) in the "Common Nx Commands" section
  4. Search for @aws/nx-plugin - ts#project
  5. Fill in the required parameters
    • Click Generate
    ParameterTypeDefaultDescription
    name Requiredstring-TypeScript project name
    directory stringpackagesParent directory where the library is placed.
    subDirectory string-The sub directory the lib is placed in. By default this is the library name.
    preferInstallDependencies booleantrueWhether to prefer installing dependencies after the generator runs. Set to false to defer installing when batching multiple generators (an install still runs if needed so subsequent generators can compute the Nx project graph); install once at the end.

    The generator will create the following project structure in the <directory>/<name> directory:

    • Directorysrc TypeScript source code
      • index.ts
    • package.json Project manifest defining the project’s package name and dependencies
    • project.json Project configuration and build targets
    • tsconfig.json Base TypeScript configuration for this project (extends workspace root tsconfig.base.json)
    • tsconfig.lib.json TypeScript configuration for your library (your runtime or packaged source)
    • tsconfig.spec.json TypeScript configuration for your tests
    • vitest.config.mts Configuration for Vitest

    You will also notice some changes to the following files in your workspace root:

    • nx.json Nx configuration is updated to configure the @nx/js/typescript plugin for your project
    • tsconfig.base.json a TypeScript alias is set up for your project so that it can be imported by other projects in your workspace
    • tsconfig.json a TypeScript project reference is added for your project

    Add your TypeScript code in the src directory.

    Since your TypeScript project is an ES Module, be sure to write your import statements with the correct ESM syntax, explicitly referencing the file extension:

    index.ts
    import { sayHello } from './hello.js';

    The entry point for your TypeScript project is src/index.ts. You can add exports here for anything you’d like other projects to be able to import:

    src/index.ts
    export { sayHello } from './hello.js';
    export * from './algorithms/index.js';

    Importing your Library Code in Other Projects

    Section titled “Importing your Library Code in Other Projects”

    TypeScript aliases for your project are configured in your workspace tsconfig.base.json, which allows you to reference your TypeScript project from other TypeScript projects:

    packages/my-other-project/src/index.ts
    import { sayHello } from '@my-scope/my-library';

    When you add an import statement for a new project in your workspace for the first time, you will likely see an error in your IDE similar to the below:

    Import error
    Terminal window
    File '/path/to/my/workspace/packages/my-library/src/index.ts' is not under 'rootDir' '/path/to/my/workspace/packages/my-consumer'. 'rootDir' is expected to contain all source files.
    File is ECMAScript module because '/path/to/my/workspace/package.json' has field "type" with value "module" ts(6059)
    File '/path/to/my/workspace/packages/my-library/src/index.ts' is not listed within the file list of project '/path/to/my/workspace/packages/my-consumer/tsconfig.lib.json'. Projects must list all files or use an 'include' pattern.
    File is ECMAScript module because '/path/to/my/workspace/package.json' has field "type" with value "module" ts(6307)

    This is because a project reference has not yet been set up, and the imported project has not yet been declared as a workspace dependency in your project’s package.json.

    Your workspace is configured with sync generators which manage both for you, so you don’t need to configure them by hand. Simply run the following command and Nx will add the required configuration:

    Terminal window
    pnpm nx sync
    Terminal window
    NX The workspace is out of sync
    [@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on, contain stale project references, or have duplicate project references.
    [@aws/nx-plugin:ts#sync]: Local project dependencies are out of sync. The following workspace dependencies will be declared:
    packages/my-other-project/package.json:
    - @my-scope/my-library: workspace:*
    Syncing the workspace...
    The workspace was synced successfully!

    Nx’s TypeScript sync generator adds the project reference, and the plugin’s ts#sync generator declares the imported project in your project’s package.json (workspace:* where the package manager supports the workspace protocol, * on npm and yarn classic), so the package manager links the local project on install. After this, the error in your IDE should be gone and you are ready to use your library.

    If you add custom compilerOptions.paths entries in a project’s tsconfig.json, TypeScript stops inheriting the workspace aliases defined in tsconfig.base.json. The hidden ts#sync generator runs before the compile target (configured in nx.json) to copy any missing base aliases into tsconfig.json, tsconfig.lib.json and tsconfig.app.json files that already declare paths. To opt out, remove @aws/nx-plugin:ts#sync from targetDefaults.compile.syncGenerators in nx.json.

    Each TypeScript project declares its third-party runtime dependencies in its own package.json. Build and test tooling shared across projects is defined in the root package.json.

    To add a runtime dependency to a project, install it into that project:

    Terminal window
    pnpm add some-npm-package --filter my-library

    You can also run your package manager’s plain add/install command from within the project’s directory.

    To add a shared dev tool, install it at the workspace root:

    Terminal window
    pnpm add -Dw some-dev-tool

    For package managers which support catalogs (pnpm, yarn and bun), generators record each version in the catalog and reference it with the catalog: protocol (eg "zod": "catalog:"), so the catalog is the single source of truth for versions regardless of which project declares the dependency. This helps you to follow a single version policy, avoiding type conflicts and multiple copies of packages in deployment bundles.

    When you add a new dependency yourself, keep it in the catalog too:

    The workspace sets catalogMode: strict, so pnpm add records the version in the catalog and references it automatically — no extra steps.

    Terminal window
    pnpm add some-npm-package --filter my-library

    Catalogs are enabled by default. To turn them off, create your workspace with --catalog false:

    Terminal window
    pnpm create @aws/nx-workspace my-project --catalog false

    Or set it in aws-nx-plugin.config.mts at any time:

    aws-nx-plugin.config.mts
    export default {
    packageManager: {
    catalogs: false,
    },
    } satisfies AwsNxPluginConfig;

    With catalogs disabled, generators write dependency versions directly into each project’s package.json, and keeping versions aligned across projects is your responsibility.

    If you prefer declaring every dependency in the root package.json only (no per-project dependency declarations), turn off the lint rule in the workspace biome.json:

    biome.json
    {
    "linter": {
    "rules": {
    "correctness": {
    "noUndeclaredDependencies": "off"
    }
    }
    }
    }

    Projects resolve root-installed packages at build time, so everything still builds — you give up per-project manifests as an accurate record of each project’s dependencies (which matters if you later publish a project or split the repo).

    When you use your TypeScript project as runtime code (for example as the handler for an AWS Lambda function), it’s recommended that you use a tool such as Rolldown to bundle your project, since this can tree-shake to ensure that only the dependencies your project actually references are included.

    You can achieve this by adding a target such as the following to your project.json file:

    {
    ...
    "targets": {
    ...
    "bundle": {
    "cache": true,
    "executor": "nx:run-commands",
    "outputs": ["{workspaceRoot}/dist/packages/my-library/bundle"],
    "options": {
    "command": "rolldown -c rolldown.config.ts"
    }
    },
    },
    }

    And adding the rolldown.config.ts file as follows:

    rolldown.config.ts
    import { defineConfig } from 'rolldown';
    export default defineConfig([
    {
    input: 'src/index.ts',
    output: {
    file: '../../dist/packages/my-library/bundle/index.js',
    format: 'cjs',
    inlineDynamicImports: true,
    },
    },
    ]);

    Your TypeScript project is configured with a build target (defined in project.json), which you can run via:

    Terminal window
    pnpm nx build <project-name>

    Where <project-name> is the fully qualified name of your project.

    The build target will compile, lint and test your project.

    Build output can be found in the root dist folder in your workspace, inside a directory for your package and target, for example dist/packages/<my-library>/tsc

    To build all of the projects in your workspace, run:

    Terminal window
    pnpm nx run-many --target build

    Or use the shorthand command:

    Terminal window
    pnpm build

    Vitest is configured for testing your project.

    Tests should be written in .spec.ts or .test.ts files, co-located in your project’s src folder.

    For example:

    • Directorysrc
      • hello.ts Library source code
      • hello.spec.ts Tests for hello.ts

    Vitest provides Jest-like syntax for defining tests, with utilities such as describe, it, test and expect.

    hello.spec.ts
    import { sayHello } from './hello.js';
    describe('sayHello', () => {
    it('should greet the caller', () => {
    expect(sayHello('Darth Vader')).toBe('Hello, Darth Vader!');
    });
    });

    For more details about how to write tests, and features such as mocking dependencies, refer to the Vitest documentation

    Tests will run as part of the build target for your project, but you can also run them separately by running the test target:

    Terminal window
    pnpm nx test <project-name>

    You can run an individual test or suite of tests using Vitest’s -t flag. Pass it after a -- separator so Nx forwards it to Vitest rather than consuming it as its own --target option:

    Terminal window
    pnpm nx test <project-name> -- -t 'sayHello'

    TypeScript projects use Biome for linting and formatting. Biome is configured in the workspace root biome.json file — changes to this apply to all TypeScript projects in your workspace and ensure consistency.

    To invoke the linter to check your project, you can run the lint target.

    Terminal window
    pnpm nx lint <project-name>

    The majority of linting or formatting issues can be fixed automatically by running with the --configuration=fix argument.

    Terminal window
    pnpm nx lint <project-name> --configuration=fix

    Similarly if you would like to fix all lint issues in all packages in your workspace, you can run:

    Terminal window
    pnpm nx run-many --target lint --all --configuration=fix

    To avoid linting issues slowing you down during development (particularly if you have non auto-fixable issues in your project), you can run a build with the skip-lint configuration:

    Terminal window
    pnpm nx run-many --target build --configuration=skip-lint

    This skips the lint target entirely during build.