JavaScript and TypeScript course Β· Module 6: TypeScript Basics

TypeScript configuration and tools

5 min read
In this lesson7

In Jurassic Park, even the best genetic system is useless without proper calibration of the laboratory equipment. Similarly, TypeScript without proper configuration won't reach its full potential. The tsconfig.json file is your control panel - it defines how the TypeScript compiler interprets and transforms your code.

tsconfig.json - command center

Every TypeScript project starts with a tsconfig.json file in the root directory. This is a JSON file defining compiler options, which files should be compiled, and how the output should look:

1{
2  "compilerOptions": {
3    "target": "ES2020",
4    "module": "ESNext",
5    "lib": ["ES2020", "DOM", "DOM.Iterable"],
6    "outDir": "./dist",
7    "rootDir": "./src",
8    "strict": true,
9    "esModuleInterop": true,
10    "skipLibCheck": true,
11    "forceConsistentCasingInFileNames": true
12  },
13  "include": ["src/**/*"],
14  "exclude": ["node_modules", "dist"]
15}

Key compiler options

Target - output JavaScript version:

  • ES5 - compatibility with older browsers
  • ES2020 - modern environments (nullish coalescing, optional chaining)
  • ESNext - latest features

Module - module system:

  • CommonJS - Node.js (require/module.exports)
  • ESNext - ES modules (import/export)
  • NodeNext - Node.js with ESM support

Lib - which APIs are available:

  • DOM - browser API (document, window)
  • ES2020 - methods like Promise.allSettled, BigInt
  • WebWorker - Web Workers API

Strict Mode - maximum safety

The strict: true flag enables all rigorous checks at once. It's like turning on full security in Jurassic Park - no dinosaur will escape the enclosure:

1// strict: true enables ALL of the following:
2{
3  "strictNullChecks": true,       // null/undefined must be handled
4  "strictFunctionTypes": true,    // strict typing of function parameters
5  "strictBindCallApply": true,    // strict typing of bind/call/apply
6  "strictPropertyInitialization": true, // properties must be initialized
7  "noImplicitAny": true,          // implicit any is forbidden
8  "noImplicitThis": true,         // this must have a type
9  "alwaysStrict": true,           // "use strict" in every file
10  "useUnknownInCatchVariables": true // catch(e) is unknown, not any
11}

Example difference with strictNullChecks:

1// WITHOUT strictNullChecks - runtime error!
2function getDinoName(id: string): string {
3  const dino = dinosaurs.find(d => d.id === id);
4  return dino.name; // Runtime error: Cannot read property 'name' of undefined
5}
6
7// WITH strictNullChecks - TypeScript enforces null handling
8function getDinoName(id: string): string | undefined {
9  const dino = dinosaurs.find(d => d.id === id);
10  return dino?.name; // Safe - returns undefined if not found
11}

Path Aliases - navigation shortcuts

In large projects, imports can become unreadable. Path aliases allow you to create shortcuts:

1{
2  "compilerOptions": {
3    "baseUrl": ".",
4    "paths": {
5      "@models/*": ["src/models/*"],
6      "@utils/*": ["src/utils/*"],
7      "@services/*": ["src/services/*"],
8      "@config": ["src/config/index.ts"]
9    }
10  }
11}

Instead of:

1import { Dinosaur } from '../../../models/dinosaur';
2import { formatDate } from '../../utils/helpers';

You write:

1import { Dinosaur } from '@models/dinosaur';
2import { formatDate } from '@utils/helpers';

Declaration Files (.d.ts)

Declaration files describe types for JavaScript code. They have the .d.ts extension and contain no implementation - only type signatures:

1// dinosaur.d.ts - type declaration
2declare interface IDinosaur {
3  id: string;
4  name: string;
5  species: string;
6  dangerLevel: 1 | 2 | 3 | 4 | 5;
7}
8
9declare function findDinosaur(id: string): IDinosaur | null;
10declare const PARK_NAME: string;
11
12// Module declaration for a JS library without types
13declare module 'dino-tracker' {
14  export function trackPosition(id: string): [number, number];
15  export function getStatus(id: string): 'active' | 'sleeping' | 'escaped';
16}

Three types of declaration files:

  1. Automatic - TypeScript generates them with the declaration: true flag
  2. Manual - you write them yourself for JS code without types
  3. DefinitelyTyped - community types for popular libraries

DefinitelyTyped (@types/*)

Many JavaScript libraries don't have built-in types. The DefinitelyTyped repository contains community-created types:

1# Installing types for popular libraries
2npm install --save-dev @types/node
3npm install --save-dev @types/express
4npm install --save-dev @types/lodash
5npm install --save-dev @types/jest

TypeScript automatically recognizes @types/* packages - you don't need to import them. If a library has built-in types (like axios or date-fns), you don't need a separate @types package.

The typeRoots option in tsconfig controls where TypeScript loads types from:

1{
2  "compilerOptions": {
3    "typeRoots": ["./node_modules/@types", "./src/types"]
4  }
5}

ESLint with TypeScript

TypeScript has its own type checks, but ESLint with the @typescript-eslint plugin adds stylistic rules and advanced analysis:

1{
2  "parser": "@typescript-eslint/parser",
3  "plugins": ["@typescript-eslint"],
4  "extends": [
5    "eslint:recommended",
6    "plugin:@typescript-eslint/recommended",
7    "plugin:@typescript-eslint/recommended-requiring-type-checking"
8  ],
9  "parserOptions": {
10    "project": "./tsconfig.json"
11  },
12  "rules": {
13    "@typescript-eslint/no-explicit-any": "warn",
14    "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
15    "@typescript-eslint/prefer-nullish-coalescing": "error",
16    "@typescript-eslint/prefer-optional-chain": "error"
17  }
18}

Most important @typescript-eslint rules:

  • no-explicit-any - warns about using any
  • no-unused-vars - detects unused variables (with TypeScript support)
  • prefer-nullish-coalescing - prefer ?? instead of ||
  • prefer-optional-chain - prefer a?.b instead of a && a.b
  • strict-boolean-expressions - forbids implicit boolean conversions

Code quality options

Additional compiler flags to help maintain code quality:

1{
2  "compilerOptions": {
3    "noUnusedLocals": true,        // error on unused variables
4    "noUnusedParameters": true,    // error on unused parameters
5    "noImplicitReturns": true,     // every path must return a value
6    "noFallthroughCasesInSwitch": true, // enforces break in switch
7    "noUncheckedIndexedAccess": true,   // arr[i] is T | undefined
8    "exactOptionalPropertyTypes": true  // undefined !== omitting a field
9  }
10}

The noUncheckedIndexedAccess option is particularly useful - it protects against indexing errors:

1const dinos = ["T-Rex", "Raptor", "Triceratops"];
2
3// WITHOUT noUncheckedIndexedAccess
4const first: string = dinos[0]; // OK, but dinos[100] would also be "string"!
5
6// WITH noUncheckedIndexedAccess
7const first: string | undefined = dinos[0]; // TypeScript enforces checking
8if (first) {
9  console.log(first.toUpperCase()); // Safe
10}
Code for this lesson: index.ts
1// TypeScript configuration - Jurassic Park Laboratory
2
3// === 1. STRICT MODE IN ACTION ===
4
5// strictNullChecks - forces you to handle null/undefined
6interface IDinosaur {
7  id: string;
8  name: string;
9  species: string;
10  dangerLevel: 1 | 2 | 3 | 4 | 5;
11}
12
13const dinosaurs: IDinosaur[] = [
14  { id: "001", name: "Rexy", species: "T-Rex", dangerLevel: 5 },
15  { id: "002", name: "Blue", species: "Velociraptor", dangerLevel: 4 },
16  { id: "003", name: "Bumpy", species: "Ankylosaurus", dangerLevel: 2 },
17];
18
19// Safe lookup that handles undefined
20function findDino(id: string): IDinosaur | undefined {
21  return dinosaurs.find(d => d.id === id);
22}
23
24const dino = findDino("001");
25if (dino) {
26  console.log("Found:", dino.name, "- danger level:", dino.dangerLevel);
27} else {
28  console.log("Dinosaur not found");
29}
30
31// === 2. PATH ALIASES (simulation) ===
32// In a real project:
33// import { Dinosaur } from '@models/dinosaur';
34// import { formatDate } from '@utils/helpers';
35// Instead of: import { Dinosaur } from '../../../models/dinosaur';
36
37// === 3. DECLARATION FILES (.d.ts) ===
38// Simulated type declarations for a JS library
39
40// This is what the dino-tracker.d.ts file would look like:
41// declare module 'dino-tracker' {
42//   export function trackPosition(id: string): [number, number];
43//   export function getStatus(id: string): DinoStatus;
44// }
45
46// We use the types as if the library existed:
47type DinoStatus = "active" | "sleeping" | "escaped";
48
49function getStatus(id: string): DinoStatus {
50  const dino = findDino(id);
51  if (!dino) return "escaped";
52  return dino.dangerLevel >= 4 ? "active" : "sleeping";
53}
54
55console.log("Rexy's status:", getStatus("001"));
56console.log("Blue's status:", getStatus("002"));
57
58// === 4. noUncheckedIndexedAccess ===
59const species = ["T-Rex", "Raptor", "Triceratops"];
60
61// With noUncheckedIndexedAccess: species[0] is string | undefined
62const first = species[0];
63if (first !== undefined) {
64  console.log("First species:", first.toUpperCase());
65}
66
67// === 5. DEFINITELYTYPED ===
68// Installing types:
69// npm install --save-dev @types/node @types/express @types/lodash
70// TypeScript recognizes them automatically
71
72// === 6. ESLINT WITH TYPESCRIPT ===
73// Examples of @typescript-eslint rules:
74
75// prefer-nullish-coalescing: use ?? instead of ||
76const parkName: string | null = null;
77const displayName = parkName ?? "Jurassic Park"; // better than ||
78console.log("Park:", displayName);
79
80// prefer-optional-chain: use ?. instead of &&
81interface ParkConfig {
82  security?: {
83    fences?: {
84      voltage?: number;
85    };
86  };
87}
88
89const config: ParkConfig = { security: { fences: { voltage: 10000 } } };
90const voltage = config.security?.fences?.voltage; // better than config && config.security && ...
91console.log("Fence voltage:", voltage, "V");
92
93// noImplicitReturns: every path must return a value
94function classifyThreat(level: number): string {
95  if (level >= 4) return "HIGH THREAT";
96  if (level >= 2) return "MEDIUM THREAT";
97  return "LOW THREAT"; // required by noImplicitReturns
98}
99
100console.log("Classification (5):", classifyThreat(5));
101console.log("Classification (1):", classifyThreat(1));

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Which of the following flags is NOT enabled by the "strict": true option in tsconfig.json?

  2. 2. What is the role of .d.ts files (declaration files) in TypeScript?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Fix strict mode issues: strictNullChecks, noUncheckedIndexedAccess, noImplicitReturns, and configure path aliases.

  • Code editor

    Write a SecurityEvent interface, implement checkPerimeter and logEvent, and create a DinoSize type with a classifyBySize function.

  • Vertical ordering

    Order tsconfig options from most important (enable first) to advanced:

  • Click in order

    Arrange the path alias configuration elements in tsconfig.json:

  • Vertical ordering

    Arrange the steps for creating a new TypeScript project:

Useful articles