TutorialsOct 30, 20257 min read

Enforce type safety with TypeScript checks before deployments

John Abraham

DevOps engineer

TypeScript introduces the benefits of static typing to JavaScript, allowing developers to identify bugs at an earlier stage. However, relying solely on developers to run type checks locally isn’t enough. Without tsc being called, a person can just leave the invalid code and it may pass to production.

This tutorial will show you how to set up CircleCI to automatically run the TypeScript type checks on each push. Add tsc --noEmit into your CI pipeline, and you will prevent and inhibit type errors at the source.

Prerequisites

  • A GitHub account
  • A CircleCI account connected to your GitHub
  • Git should be installed
  • Basic knowledge of TypeScript and how type annotations work
  • Familiarity with package.json and running npm scripts
  • A general understanding of CI/CD workflows and how CircleCI uses YAML config files
  • Basic knowledge of Git and GitHub

Why enforce type checks before deployment?

When developing programs using JavaScript, a large number of bugs are not detected until run time, or even by the users. TypeScript is aimed at assisting the developer to reveal such bugs sooner by introducing type restrictions at development time. Yet, it is not enough to write in TypeScript. When your team does not run tsc regularly, it is easy to have this code go through the code review and be merged into production.

You should enable type checks in your CI/CD workflow to provide an all-important layer of automation to your development. It will also make sure that type checking occurs each and every time code is being pushed rather than only occurring when a developer thinks to run the compiler locally.

Ensure code reliability

With type checks enforced in CI:

  • Malicious code which violates the functioning of contracts or misuses data structures will not be merged.
  • You avoid obvious errors such as feeding the wrong data type into a function, omitting a check on undefined, or improperly using a third-party API.

Enable safe collaboration

Relying on tsc to be run by every person working in a collaborative environment is not scalable. The laxity of one developer may cause runtime bugs to everyone. When you bake checks into your CI, every member of the team enjoys a mutual safety net. It establishes a stable reference level of code quality and eases the onboarding process of new developers.

Avoid last-minute surprises

By not enforcing CI type, you may find problems after deployment or even worse when the users start using your app. These types of failures in production are usually embarrassing and expensive. Trying to identify issues at a low level by using automated type checks moves error checks back in time when it is cheaper and less disruptive to fix.

Invest in low-cost and high-impact coding

It is ridiculously easy to add a type-checking step to your CircleCI pipeline. A single line of setup tsc --noEmit can save hours of debugging at a later date. It is a minor investment but with big returns in stability, confidence, and developer trust.

Create a simple TypeScript project

To make things easier and keep this tutorial focused on enforcing type safety, we’ve prepared a GitHub repository you can clone and follow along with. The base project structure is ready for you, so you don’t have to worry about setting up files from scratch.

The repository contains two branches:

  • main includes everything from the CircleCI config and a sample type error in index.ts so you can immediately test the type check in action.
  • starter-branch is a clean version with only the basic TypeScript setup. This is the branch you will use to build things step-by-step as you follow the tutorial.

To get started, clone the repo:

git clone https://github.com/CIRCLECI-GWP/circleci-ts-check-demo.git
cd circleci-ts-check-demo
git checkout starter-branch

After checking out the starter-branch, your project will look like this:

circleci-ts-check-demo/
├── src/
│   └── index.ts
├── tsconfig.json
├── package.json

Here’s what each file/folder does:

  • src/index.ts is your main TypeScript file. You’ll write and test your code here.
  • tsconfig.json configures the TypeScript compiler. It enables strict type checking and tells TypeScript where to look for source files.

  • package.json defines your project’s dependencies and scripts, including the type-check command CircleCI will run.

Here’s the package.json file:

{
  "name": "circleci-ts-check-demo",
  "version": "1.0.0",
  "main": "src/index.ts",
  "scripts": {
    "type-check": "tsc --noEmit"
  },
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}

The type-check script runs tsc --noEmit, which checks for type errors without generating compiled JavaScript files. typescript is listed under devDependencies because it’s only needed for development and CI checks, not for production use.

Before moving forward, you need to push the cloned project to your own GitHub account so CircleCI can access it later on. To set up the remote and push it run:

git remote set-url origin https://github.com/your-username/circleci-ts-check-demo.git
git push -u origin starter-branch

Replace your-username with your own GitHub username. This will upload the current branch to your GitHub repository. CircleCI will now be able to detect the repository during setup.

Set up CircleCI config

Now that your TypeScript project is ready, you can configure CircleCI to enforce type checks. CircleCI uses a .circleci/config.yml file to define what steps should happen in your CI pipeline. Inside the root of your project, create a folder called .circleci. Inside it, add a file named config.yml. Paste this content into it:

version: 2.1

executors:
  node:
    docker:
      - image: cimg/node:18.20
    working_directory: ~/repo

jobs:
  type-check:
    executor: node
    steps:
      - checkout
      - run: npm install
      - run: npm run type-check

workflows:
  version: 2
  check-and-deploy:
    jobs:
      - type-check

This job installs dependencies and runs tsc --noEmit. If any type of error is found, the build will fail.

Connecting the project to CircleCI

At this point, your TypeScript project and CircleCI configuration are set up, now it is time to hook everything up with CircleCI. This integration guarantees there is no need to manually launch your CI pipeline via a push, pull request, or merge.

Connecting this setup is a one-time procedure, and requires only a few clicks. Once connected, CircleCI will find your .circleci/config.yml file, install the dependencies in your project, and run your type-check job when you push a commit. This is the core of your automated quality gate. This gives you the chance to detect and eradicate type issues before it reaches production.

  1. Visit CircleCI
  2. Sign into your account or Log in with GitHub
  3. After you log in, click on Projects > Create Project
  4. Name the project circleci-ts-check-demo

CircleCI project name screen

Follow the prompts to select your project repo, and finish the setup.

Successful deploy page in CircleCI

Trigger a type error and watch the pipeline fail

With your pipeline connected and your CircleCI workflow in place, it’s time to put it into action. The true value of introducing a TypeScript check to CI is that it stops incorrect code from being merged or deployed. To test that what you have built works correctly, add an error in the project. Then observe how CircleCI finds it and makes the build fail.

This step shows the safety net you have created. Rather than depending on the belief that people do type checks locally, you have an automated solution that does that every single time. And when type checking fails, CircleCI will not continue the pipeline because of damaged code.

To test the src/index.ts file, edit it to include this incorrect code:

// src/index.ts

type User = {
  id: number;
  name: string;
  email: string;
};

function greetUser(user: User): string {
  return `Hello, ${user.name}!`;
}

// Intentional type errors
const userName: number = "CircleCI"; // Type 'string' is not assignable to type 'number'

const fakeUser: User = {
  id: "not-a-number", // Type 'string' is not assignable to type 'number'
  name: "Jane Doe",
  email: 12345,        // Type 'number' is not assignable to type 'string'
};

console.log(greetUser(fakeUser));

This code contains three deliberate type errors:

  1. userName is declared as a number but assigned a string.
  2. id in fakeUser is expected to be a number, but a string is given.
  3. email is expected to be a string, but a number is used.

Once you’ve added this code, save the file and push your changes to GitHub:

git add src/index.ts
git commit -m "Add type errors to test CircleCI pipeline"
git push

After making this change and pushing it, CircleCI will start your type-check job. As the code contains a type violation, the work will fail, and the pipeline will stall.

Type check failure blocking deployment in CircleCI

This is exactly what you need: unsafe code is blocked from progressing further. It is an easy but effective method of safeguarding your project against bugs that would have otherwise remained unnoticed until your code is executed.

Conclusion

It is easy but effective to enforce tsc --noEmit in your CircleCI workflow to make a type-safety check of all your code. It guards your team against bugs that can be completely avoided, particularly when dealing with swift-moving or group work.

The advantages of using TypeScript are multiplied when supported by automation. This arrangement can be used to provide quality assurance, increase confidence, and ensure discipline, without requiring your team to memorize any information.