TypeScript - DavidBK/shampoo GitHub Wiki

Typescript

Estimation time: 1-2 Days


TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.

This is a basic introduction to Typescript.

Learning objectives:

At the end of this learning path, you'll be able to:

  • Run TS project
  • Write an efficient type-safe code in TypeScript

Send me back home

[[TOC]]


Learning note: all links in this path are reading tutorials. You can read them but you can watch a youtube crash course. if you find useful links, please share them with me.

Here is some examples links:

Get Started with typescript

You can start "playing" with typescript online in sandbox.

In order to run it locally we need to install typescript and then compile our code into executable javascript.

Install

TypeScript is available as a package on the npm registry.

You can install it as dev-dependency in your project using:

npm install typescript --save-dev

if you want typescript support in node: imports and global node variables you need to install @types/node

npm install @types/node --save-dev

Side Note: Sometimes You will need types in the declaration file. If so install the types as dependencies. You can read this for more information.

TSConfig

Typescript compiler needs instructions to compile. We can specify the compiler options in config file called tsconfig.

You can initialize the compiler options using tsc cli --init flag:

npx tsc --init

This will create a basic tsconfig.json file.

My recommended tsconfig.json for modern node applications is:

compilerOptions:

  • "module": "NodeNext" - Tells TS that this project use ES Modules (And the module resolution is NodeNext)

  • target": "ESNext" - Tells TS that it's okay to output JavaScript syntax with new features instead of embedding a polyfill.

  • "strict": true - Tells TS to enables a wide range of type checking behavior that results in stronger guarantees of program correctness.

  • "noUncheckedIndexedAccess": true - Tells TS to add undefined to any declared via index signatures field in the type.

  • skipLibCheck": true - Tells TS to skip type checking of all declaration files (*.d.ts). This can save time during compilation and solve incompatibly issues between libraries.

Here is a recommended tsconfig.json file:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "NodeNext",
    "outDir": "./dist",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"],
}

You can run this script in your terminal to create the file:

curl https://gitlab.com/davidbk6/shampoo/-/raw/14055316e7a305253eef65ed430a169e1141d70a/tsconfig.json -o tsconfig.json

Here is the full TSConfig Reference

Basic Compile

You can use the tsc cli using npx:

npx tsc

Or you can create a npm script:

In your package.json:

  "scripts": {
    "build": "tsc"
  },

And then:

npm run build

Test it! create .ts file and run the compile .js.

TypeScript Execute - tsx (Optional)

tsx (TypeScript eXecute - do not confuse with React's TSX files which stand for TypeScript XML) is a TypeScript runtime that allows you to execute TypeScript files directly.

This tool is useful for running TypeScript files without the need to compile them to JavaScript first.

You can use it without any setup using npx:

npx tsx <your-file>.ts

You can read more in the tsx repo.

ts-node (Optional)

ts-node is a TypeScript execution engine and REPL for Node.js. It "Just In Time" (JIT) transforms TypeScript into JavaScript, enabling you to directly execute TypeScript on Node.js without precompiling.

Install it as a devDependency:

npm i -D ts-node

And then run your file:

npx node-ts <your-file>.ts

You can add it to your npm scripts:

"dev": "ts-node <your-file>.ts"

If you like to use esm (and you should) you can use it as a loader to your file:

"dev": "node --loader ts-node/esm <your-file>.ts"

If you use ts-node I recommend to add the configuration to the tsconfig file as "ts-node".

 "ts-node": {
   "swc": true,
 }

You can read more in the ts-node docs.

Other JS runtime (Advanced)

Some modern JavaScript engines runtime come with built-in support for TypeScript:

  • Deno is a modern runtime for JavaScript and TypeScript that uses V8 and is built in Rust. Deno has "First-class" support for TS and its "Understand" (Using JIT) Typescript so you execute your .ts using deno.

  • Bun is a modern JavaScript runtime like Node or Deno. Bun uses the JavaScriptCore engine and was written in ZIG. Bun is designed as a drop-in replacement for your current JavaScript & TypeScript apps or scripts so you can run .ts files directly.

TypeScript - The basics

You should be familiar with the following concepts:

You can read them all in the TypeScript Handbook. Especially the everyday-types

  • string, number, and boolean
  • Array
  • Tuple
  • Functions
  • Object types
  • Type Aliases (type)
  • Interfaces
  • any and unknown
  • Union Types (|)
  • Intersection Types (&)
  • Function type expression
  • Parameter Destructuring in functions
  • Passing Type Arguments (<Type>)
  • Type Assertions (as, !)
  • typeof type operator
  • keyof type operator
  • null, undefined, void, and never
  • Narrowing and Type guards
  • Global utility Types (e.g Omit, Record, Promise, etc.)

Beginners Typescript Workshops

Here is a great tutorial for all the basics. Do it yourself 😄

Questions - Basic Typescript

  1. Take the following code and paste it in new test.js

    const echo = (arg) => arg;
    
    const greet = echo("Hello From World");
    const veryGreet = greet.map((ch) => ch + "!");
    console.log(veryGreet);

    Run it using node test.js and see the error.

    • What is the problem?
    • How typescript can help me?
    • Create test.ts file and fix the code so it will not compile.
  2. What is the problem? Fix it.

    const productPrices = {
      apple: 1.2,
      banana: 0.5,
      orange: 0.8,
    };
    
    const getPrice = (productName: string) => productPrices[productName];

    If you can't see the problem paste the code to TS Playground and see the error.

Working With TS - My Recommendations

  • Write a little TS as you can! TS is very "smart" and most of the time inferred types will be more "specific". Also this will help your code readability.
    • Don't declare types in variables
    • Prefer to not declare return types of simple functions
  • Don't use enums, prefer union types or as const objects.
  • Don't use Function, Object, String, Number, and Boolean (Capital letters) and object - they are not doing what you think they are doing.
  • Be careful with .json() (in fetch response) and JSON.parse() as they both return any. You can cast them to unknown (or use ts-reset to do it for you)
  • I Prefer type aliases over interfaces for "everyday" types. If you using Object Oriented Programming you can use interfaces but keep in mind the merge behavior of interfaces.

Runtime checks with TypeScript

TypeScript is lived in "compile-time", which make its great for checking variables at the type level (developer "linting"). However, during "runtime", JavaScript is the only language that is executed. Therefore, it is necessary to validate user input against schema validation in JavaScript.

In many cases there is a strong corelation between types and runtime schemas, and duplicating type declarations in both schema and types can be cumbersome.

Zod

Zod is a TypeScript-first schema declaration and validation library.

The goal is to eliminate duplicate type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It also allows for easy composition of simpler types into complex data structures.

If you wish to use Zod with Fastify app, you can use fastify-type-provider-zod library.

Zod tutorial (optional)

You can do this tutorial which is a set of zod exercises for you to work through.

TypeBox (optional)

TypeBox is a JSON Schema Type Builder with Static Type Resolution for TypeScript.

With typebox you define your schema within your code and use them directly as types or schemas as you need them (like zod).

TypeBox is great for working with Fastify in TS because the validation in Fastify uses JSON Schema. Moreover, defining schemas for your Fastify can increase their throughput. To use it in your Fastify you will need fastify type-provider.

For more information you can read in the Fastify typebox doc.

TypeScript - More Topics (optional)

  • Generics
  • Literal Types and as const
  • Indexed Access Types (such as T["key"], T[string] etc.)
  • Index Signatures (such as [index: string]: number)
  • extends keyword

Questions - More Topics

  1. Fix the code so it will Error only in errVeryStr

    const echo = (arg) => arg;
    
    const greet = echo("Hello From Space");
    const greetArr = echo(greet.split(""));
    
    // @ts-expect-error
    const errVeryGreet = greet.map((ch) => ch + "!");
    const veryGreet = greetArr.map((ch) => ch + "!").join("");
    
    console.log(veryGreet);
  2. Fix the code so addFullName function will get any object that satisfies UserShape:

    type UserShape = {
      firstName: string;
      lastName: string;
    };
    
    const addFullName = (user: UserShape) => {
      return {
        ...user,
        fullName: `${user.firstName} ${user.lastName}`,
      };
    };
    
    const actorUser = {
      firstName: "John",
      lastName: "Malkovich",
      hobbies: ["acting", "directing"],
    };
    
    const soldierUser = {
      firstName: "John",
      lastName: "Rambo",
      rank: "Sergeant",
    };
    
    const notUser = {
      first_name: "I am not a user",
      lastName: "I told you I am not a user",
    };
    
    const actor = addFullName(actorUser);
    const soldier = addFullName(soldierUser);
    // @ts-expect-error
    const doNot = addFullName(notUser);
    
    console.log(actor.hobbies); // Fix the ts error
    console.log(soldier.rank); // Fix the ts error
    
    // @ts-expect-error
    console.log(actor.rank);
  3. What is the problem with this code? Can you fix it?

    const satellitesCompanies = {
      skysat: "Planet",
      worldview: "Maxar",
      sentinel: "Sentinel",
    };
    
    type SatellitesCompanies = typeof satellitesCompanies;
    
    type SkysatCompany = SatellitesCompanies["skysat"];
    type WorldviewCompany = SatellitesCompanies["worldview"];
    type SentinelCompany = SatellitesCompanies["sentinel"];
    
    declare function freeOrder(company: SentinelCompany): void;
    declare function payedOrder(company: SkysatCompany | WorldviewCompany): void;
    
    freeOrder("Sentinel");
    
    // @ts-expect-error
    freeOrder("Planet");
    
    // @ts-expect-error
    payedOrder("Sentinel");
  4. Change the SatelliteName type so it will error on the last line:

    const satellitesNames = [
      "skysat",
      "skywalker",
      "skyscraper",
      "skyrim",
      "skype",
    ];
    
    type SatelliteName = string;
    
    declare function isSatelliteGood(satName: SatelliteName): boolean;
    
    console.log(isSatelliteGood("skywalker"));
    
    // @ts-expect-error
    console.log(isSatelliteGood("skySat"));
  5. Change the SatellitesCompaniesNames type so it will error on the last line:

    const satellitesCompanies = {
      skysat: "Planet",
      worldview: "Maxar",
      sentinel: "Sentinel",
    };
    
    type SatellitesCompaniesNames = string;
    
    declare function isCompanyGood(company: SatellitesCompaniesNames): boolean;
    
    console.log(isCompanyGood("Planet"));
    
    // @ts-expect-error
    console.log(isCompanyGood("planet"));

TypeScript - Advanced (optional)

Note: This section is advanced, you can ask your mentor what to learn from this section.

  • Enums
  • type predicates (is)
  • Recursive Types
  • Discriminated unions
  • Exhaustiveness checking
  • Function Overloads
  • infer keyword
  • satisfies operator
  • Conditional Types
  • Distributive Conditional Types (T extends T vs [T] extends [T])
  • Mapped Types (in)
  • Template Literal Types
  • using operator
  • Decorators

Questions - Advanced

  1. Fix this code so it will error

    type Route = { path: string; children?: Routes };
    type Routes = Record<string, Route>;
    
    const routes: Routes = {
      AUTH: {
        path: "/auth",
        children: {
          LOGIN: {
            path: "/login",
          },
        },
      },
      HOME: {
        path: "/",
      },
      // @ts-expect-error
      mistake: true,
    };
    
    // @ts-expect-error
    const { path } = routes.NotExist;
    • Create a type "function" which generate tuple types. For Example:

      type TupleStr = Tuple<string, 3>;
      // TupleStr = [string, string, string]
      
      type Tuple2 = Tuple<number, 2>;
      // Tuple2 = [number, number]
    • Fix the type of this group function:

      /**
       * Groups elements of an array into subarrays of a specified size.
       * If the array length is not a multiple of size, the array is truncated to fit.
       */
      const group = <T>(arr: T[], size = 2) =>
        Array.from({ length: arr.length / size }, (_, i) =>
          arr.slice(i * size, i * size + size)
        );
      
      const arr = [1, 2, 3, 4, 5, 6];
      const res = group(arr); // Fix it to be [number, number][]
      const res2 = group(arr, 3); // Fix it to be [number, number, number][]
    • Fix the Tuple implementation so it will work with distribute over union types:

      const num = Math.random() > 0.5 ? 2 : 3;
      const res3 = group(arr, num); // const res3: ([number, number] | [number, number, number])[]
  2. Fix the types of this curry helper function:

    type Fn = (...args: any[]) => any;
    
    const curry = (fn: Fn) => {
      const curried = (...args: unknown[]) =>
        args.length >= fn.length
          ? fn(...args)
          : (...args2: unknown[]) => curried(...args.concat(args2));
    
      return curried;
    };
    
    const sum = curry((a: number, b: number) => a + b);
    
    const add2 = sum(2); // any. Fix it!
    
    const test = curry((a: string, b: number, c: boolean) => true); // (...args: unknown[]) => any. Fix it!
    
    const trueFn = curry(() => true); // Fix it!

Project

Talk with your mentor about the project.

Worth knowing (optional)

These concepts are worth mentioning but don't learn them now.

  • Declaration Files
  • Mixins
  • Module Resolution

Further Reading (optional)

Tools

⚠️ **GitHub.com Fallback** ⚠️