TypeScript strictly typed - Part 1: configuring a project
Tags: typescript, javascript, productivity, webdev
Published: 2024-06-12 16:50:22

After the [introduction](https://dev.to/cyrilletuzi/typescript-strictly-typed-intro-reliability-and-productivity-32aj) of this [posts series](https://dev.to/cyrilletuzi/typescript-strictly-typed-5fln), we are going to the topic's technical core. First, we will talk about configuration, and why **it is very important to do it at the very beginning of a project**.

We will cover:
- When to enable strict options?
- Frameworks status
- Full configuration (the hard way)
- Automatic configuration (the easy way)

## When to enable strict options?

I cannot insist more on the fact that **strict options must be enabled at the very beginning of any project**. Doing so is an easy and straightforward process: one just has to gradually type correctly when coding.

But enabling strict options in an on-going project is a completely different matter: even if TypeScript default mode is capable of inferring types in the majority of cases, the remaining untyped places are a proportion of the codebase. So the more code, the more places to fix, and it requires a clear understanding of what each code is doing.

It is one of the main recurring big errors I have seen in all the companies I have helped over the last decade as a TypeScript expert. Recovering from it is good but really time consuming and painful.

So **be sure to always check a new project is in strict mode before to start coding**.

## Frameworks status

TypeScript strict mode is enabled automatically when generating a project with the last versions of:

- [TypeScript](https://www.typescriptlang.org): `tsc --init`
- [Angular](https://angular.dev): `npm init @angular@latest`
- [React App](https://create-react-app.dev): `npx create-react-app --template typescript`
- [Next.js](https://nextjs.org): `npx create-next-app@latest`
- [Vue](https://vuejs.org): `npm create vue@latest`, then choosing TypeScript
- [Deno](https://deno.com): by default

Note **it has not always been the case with older versions of these frameworks**.

## Full configuration (the hard way)

We will see in the next parts that the "strict" mode is not enough. Other TypeScript compiler options must be enabled, as well as some lint rules.

A complete configuration would look like this:

For TypeScript, in `tsconfig.json`:
```json
{
  compilerOptions: {
    strict: true,
    exactOptionalPropertyTypes: true,
    noPropertyAccessFromIndexSignature: true,
    noUncheckedIndexedAccess: true
  }
}
```

For [ESLint](https://eslint.org) + [TypeScript ESLint](https://typescript-eslint.io/), with the new flat config `eslint.config.js`:

```js
export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.strictTypeChecked,
  ...tseslint.configs.stylisticTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      "eqeqeq": "error",
      "prefer-arrow-callback": "error",
      "prefer-template": "error",
      "@typescript-eslint/explicit-function-return-type": "error",
      "@typescript-eslint/no-explicit-any": "error",
      "@typescript-eslint/no-non-null-assertion": "error",
      "@typescript-eslint/no-unsafe-argument": "error",
      "@typescript-eslint/no-unsafe-assignment": "error",
      "@typescript-eslint/no-unsafe-call": "error",
      "@typescript-eslint/no-unsafe-member-access": "error",
      "@typescript-eslint/no-unsafe-return": "error",
      "@typescript-eslint/prefer-for-of": "error",
      "@typescript-eslint/prefer-nullish-coalescing": "error",
      "@typescript-eslint/prefer-optional-chain": "error",
      "@typescript-eslint/restrict-plus-operands": ["error", {
        "allowAny": false,
        "allowBoolean": false,
        "allowNullish": false,
        "allowNumberAndString": false,
        "allowRegExp": false,
      }],
      "@typescript-eslint/restrict-template-expressions": "error",
      "@typescript-eslint/strict-boolean-expressions": ["error", {
        "allowNumber": false,
        "allowString": false,
      }],
      "@typescript-eslint/use-unknown-in-catch-callback-variable": "error",
    },
});
```

For ESLint + TypeScript ESLint, with the legacy config in `.eslintrc.json`:

```json
{
  "parserOptions": {
    "project": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/strict-type-checked",
    "plugin:@typescript-eslint/stylistic-type-checked"
  ],
  "rules": {
    "eqeqeq": "error",
    "prefer-arrow-callback": "error",
    "prefer-template": "error",
    "@typescript-eslint/explicit-function-return-type": "error",
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-non-null-assertion": "error",
    "@typescript-eslint/no-unsafe-argument": "error",
    "@typescript-eslint/no-unsafe-assignment": "error",
    "@typescript-eslint/no-unsafe-call": "error",
    "@typescript-eslint/no-unsafe-member-access": "error",
    "@typescript-eslint/no-unsafe-return": "error",
    "@typescript-eslint/prefer-for-of": "error",
    "@typescript-eslint/prefer-nullish-coalescing": "error",
    "@typescript-eslint/prefer-optional-chain": "error",
    "@typescript-eslint/restrict-plus-operands": ["error", {
      "allowAny": false,
      "allowBoolean": false,
      "allowNullish": false,
      "allowNumberAndString": false,
      "allowRegExp": false
    }],
    "@typescript-eslint/restrict-template-expressions": "error",
    "@typescript-eslint/strict-boolean-expressions": ["error", {
      "allowNumber": false,
      "allowString": false
    }],
    "@typescript-eslint/use-unknown-in-catch-callback-variable": "error"
  }
}
```

For [Biome](https://biomejs.dev/linter/), in `biome.json`:

```json
{
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "style": {
        "useForOf": "error"
      }
    }
  }
}
```

Note that Biome is a promising but recent tool and that not all the lint rules we will discuss exist yet.

For [Deno](https://lint.deno.land), in `deno.json`:

```json
{
  "compilerOptions": {
    "exactOptionalPropertyTypes": true,
    "noPropertyAccessFromIndexSignature": true,
    "noUncheckedIndexedAccess": true,
    "useUnknownInCatchVariables": true
  },
  "lint": {
    "rules": {
      "tags": [
        "recommended"
      ],
      "include": [
        "eqeqeq",
        "explicit-function-return-type",
        "no-non-null-assertion"
      ]
    }
  }
}
```

Also note that these configuration examples only include options and rules related to this posts series topic. Other options and rules may be added to enforce other TypeScript good practices not related to strict typing.

For all tools, **be careful if a configuration extends another one**. It could mean that even if a preset like `strict` is enabled, one of the individual option included in the preset is disabled in the parent configuration. So in this case, all options should be enabled individually.

## Automatic configuration (the easy way)

Totally optional, but if one does not want to lose time to remember and configure all these options manually, one can run this command:

```bash
npx typescript-strictly-typed@latest
```

It is [a tool I published](https://github.com/cyrilletuzi/typescript-strictly-typed) to automatically add all the strict options in a TypeScript project.

### Next part

In the [next part](https://dev.to/cyrilletuzi/typescript-strictly-typed-part-2-full-coverage-typing-4cg1) of this posts series, we will explain and solve the first problem of TypeScript default behavior: from partial to full coverage typing.

You want to **contact me**? Instructions are available in the [summary](https://dev.to/cyrilletuzi/typescript-strictly-typed-5fln).