Add TypeScript to a JavaScript Project

Learn how to add TypeScript to an existing JavaScript project without rewriting everything. Install TypeScript, create a tsconfig, and start getting type safety file by file.

6 min read

Adding TypeScript to a JavaScript project means installing the TypeScript compiler, creating a configuration file, and letting your existing JavaScript files coexist with new TypeScript files. You do not need to rewrite the entire codebase at once. Most teams add TypeScript to a JavaScript project gradually, converting one file at a time while the rest of the app keeps working.

This guide walks through the setup for a Node.js project. The same principles apply for browser projects, React apps, or any other JavaScript environment.

Install TypeScript

TypeScript ships as a regular npm package, so adding it works the same way as installing any other tool. Install it as a development dependency, since the compiler is only needed while you build the project, not when it runs in production.

bashbash
npm install --save-dev typescript

This command downloads TypeScript into the project and gives you the compiler command, called tsc. Confirm the install worked by asking the compiler for its version number instead of guessing.

bashbash
npx tsc --version

If this prints a version number, the compiler is installed and ready to use. A Node.js project also needs type declarations for Node's built-in APIs, since TypeScript does not know about them by default. Install the companion package for Node types next.

bashbash
npm install --save-dev @types/node

This package teaches TypeScript what globals like the process object, console, and Buffer look like, along with the rest of the Node standard library. Without it, the compiler treats those names as unknown and reports errors. Skip this step for browser-only projects; DOM types are already built into TypeScript.

Create the tsconfig.json

A configuration file named tsconfig.json tells TypeScript where your source files live and how strict the checks should be. Generate a starting version at the project root using the compiler's built-in scaffold command.

bashbash
npx tsc --init

The default output from this command includes dozens of commented-out options, which is more than a JavaScript project needs on day one. Replace it with a smaller configuration that stays lenient while you get the first files compiling.

jsonjson
{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "allowJs": true,
    "strict": false
  },
  "include": ["src/**/*"]
}

The option that matters most here is allowJs, set to true. It tells the compiler to process plain JavaScript files alongside the new TypeScript ones, so nothing in your existing codebase gets ignored or left behind during the migration.

Strict checking is turned off on purpose at this stage, since you are introducing TypeScript into code that was never written with types in mind. Start lenient, get the project compiling, then tighten the rules once the basics are in place. For details on strict mode, see Strict Mode in TypeScript.

Organize Your Directories

TypeScript expects separate input and output directories so it never overwrites your original source files when it compiles. A typical structure separates the two clearly, with source code in one folder and generated output in another.

texttext
project/
  src/          # your .js and .ts source files
  dist/         # compiled JavaScript output
  tsconfig.json

If your JavaScript currently sits in the project root, move it into a folder named src first. This keeps the files you edit separate from the files the compiler generates, which avoids accidentally editing build output by mistake.

The output directory setting tells TypeScript to write compiled files into the dist folder, and the root directory setting tells it that src is where source files live. Together they make the generated output mirror the structure of your source tree.

Rename Your First File

Pick one JavaScript file to convert first. A utility file with few dependencies is a good starting point, since a mistake there is unlikely to ripple through the rest of the app. Rename its extension from .js to .ts so the compiler starts checking it.

bashbash
mv src/utils.js src/utils.ts

With the file renamed, run the compiler to see how it holds up against TypeScript's checks for the first time.

bashbash
npx tsc

Seeing errors after this first run is normal, since the file was never type-checked before. Three kinds of error show up often during an initial conversion:

  • A missing module error, when an import path resolves incorrectly.
  • An implicit any error, when a function parameter has no declared type.
  • A missing property error, when TypeScript inferred an empty object type for a variable.

Do not try to fix every error at once. Focus only on the file you just renamed, and for each error decide whether to add a type annotation now or temporarily relax the setting that is complaining.

Fix Common First Errors

Implicit any errors are the most common first hurdle. When a function parameter has no type, TypeScript infers it as any and, with stricter settings, flags it. Adding a short type annotation to the parameter clears the error immediately.

typescripttypescript
function formatName(name: string) {
  return name.toUpperCase();
}

If annotating every parameter right away is too much work for a large file, turn off the noImplicitAny check in the configuration file temporarily and come back to it later.

jsonjson
{
  "compilerOptions": {
    "noImplicitAny": false
  }
}

A second common error appears when you import a package and TypeScript cannot find type information for it. Install the matching types package for that library to resolve it.

bashbash
npm install --save-dev @types/express

Many popular packages already bundle their own type definitions, so this step is not always necessary. For packages without types, see Type Untyped Packages in TypeScript.

A third error shows up when JavaScript code adds properties to an object dynamically, a pattern that is common in loosely typed code but conflicts with how TypeScript infers object shapes.

typescripttypescript
const config = {};
config.port = 3000;

TypeScript reports that the port property does not exist on the inferred empty object type. Declaring the object's shape upfront, instead of letting TypeScript infer it from an empty literal, fixes the error.

typescripttypescript
const config: { port: number } = { port: 3000 };

Add an npm Script

Typing the full compiler command every time gets tedious, so add a build script to package.json that wraps it. This also makes the build step consistent for anyone else working on the project.

jsonjson
{
  "scripts": {
    "build": "tsc",
    "build:watch": "tsc --watch"
  }
}

Running npm run build now compiles the project the same way npx tsc did earlier. The watch variant recompiles automatically whenever a file changes, which saves time while you are actively converting files.

At this point, the project has TypeScript installed, a working configuration, and at least one converted file compiling alongside the remaining JavaScript files.

Tighten Settings Gradually

Once the initial files compile without errors, start enabling stricter checks one at a time instead of turning them all on together. Introduce them in the order shown below, moving to the next row only after the errors from the previous one are fixed.

SettingWhat it doesWhen to enable
Flag untyped parametersReports function parameters without a declared typeAfter you have added basic types to most functions
Enforce null checksTreats null and undefined as separate, trackable typesAfter you have handled nullable values in your code
Enable full strict modeTurns on every strict check at onceWhen you are ready for full type safety

Each setting surfaces a new batch of errors on its own, so enabling them together makes it hard to tell which change caused which error. Fix the errors from one setting before turning on the next.

For more on individual compiler options, see Important tsconfig Options Explained.

What About Build Tools

A bundler needs its own way of handling the new file type, either through a loader that understands it or a separate compile step that runs before bundling.

With Webpack, install a loader that lets the bundler compile files directly using your existing configuration file.

bashbash
npm install --save-dev ts-loader

Then point Webpack at your entry file, add both extensions to module resolution, and tell it to run that loader on any file matching the TypeScript extension.

javascriptjavascript
module.exports = {
  entry: "./src/index.ts",
  resolve: {
    extensions: [".ts", ".js"],
  },
  module: {
    rules: [
      { test: /\.ts$/, use: "ts-loader", exclude: /node_modules/ },
    ],
  },
};

Vite takes a different approach: it transpiles source files on the fly for fast rebuilds but does not check types as part of that process. Run the compiler separately, with its no-emit flag, to catch type errors during development or in continuous integration.

When to Convert More Files

There is no deadline for finishing the migration. Keep the lenient setting from earlier enabled for as long as you need, and rename files only when you are ready to add types to them. Many teams keep it enabled permanently so legacy JavaScript and newer TypeScript code continue to coexist.

Each file you convert gives you better editor support and catches more bugs at compile time. The pace is entirely up to you and your team.

Rune AI

Rune AI

Key Insights

  • Install TypeScript as a dev dependency with npm install --save-dev typescript.
  • Create a tsconfig.json with allowJs: true to accept both .js and .ts files.
  • Rename one .js file to .ts at a time and fix compiler errors as they appear.
  • Enable stricter checks like noImplicitAny only after the initial conversion.
  • TypeScript compiles to plain JavaScript that works everywhere your old JS did.
RunePowered by Rune AI

Frequently Asked Questions

Do I have to rename all my .js files to .ts right away?

No. When you enable allowJs in tsconfig, TypeScript accepts .js files alongside .ts files. You can migrate one file at a time.

Will adding TypeScript break my existing JavaScript code?

With the default lenient settings, most JavaScript projects compile without errors. Start with allowJs and strict mode off, then tighten settings gradually as you add types.

Can I use TypeScript with Webpack or Vite?

Yes. Most modern bundlers have first-class TypeScript support. Install the relevant loader or plugin, and configure it to process .ts files in addition to .js files.

Conclusion

Adding TypeScript to a JavaScript project is a gradual process. Install the compiler, create a tsconfig with allowJs enabled, and start renaming files from .js to .ts one at a time. Tighten compiler options as your team gets comfortable. You control the pace.