Practice: TypeScript and Node.js for services and tooling
Generated by Claude through GigAI 0.1.11.10 on 2026-10-11.
Main tool: TypeScript · TypeScript is the compiler and type checker that every subtopic of this lesson depends on, so practising with it directly shows both what the types guarantee and what Node.js actually runs after they are erased. · Alternatives: Plain JavaScript with JSDoc types keeps the checker but drops the syntax; Flow is a different static checker with a smaller ecosystem; Babel, swc and esbuild strip types quickly but do not type-check; Bun and Deno run TypeScript files directly but still rely on the TypeScript compiler for full type checking.
Every step links into the tool's own documentation; links were checked on 2026-10-11 · version 1
Setup (one time)
1. Install a current Node.js LTS release, create an empty project folder with a package manifest, and install TypeScript into it as a development dependency.
A project-local compiler keeps the version tied to the project instead of the machine. Every later step runs that local tsc.
Explained in: TypeScript: Documentation - TypeScript Tooling in 5 minutes (www.typescriptlang.org), checked 2026-10-11
Done when: Asking the local tsc for its version prints a version number from inside the project folder.
2. Generate a tsconfig file with the tsc init command and read each option it writes.
The generated file is the modern minimal baseline, and knowing its defaults saves guessing later when you change module or strictness settings.
Explained in: TypeScript: Documentation - TypeScript 5.9 (www.typescriptlang.org), checked 2026-10-11
Done when: A tsconfig file exists in the project root and you can say what each of its enabled options does.
3. Set the type field in the package manifest to module and set the compiler module option to a Node.js mode such as node20 or nodenext.
Node.js decides between ES modules and CommonJS from the manifest and the file extension. The compiler must be told to follow the same rules or imports fail only at runtime.
Explained in: TypeScript: Documentation - TypeScript 4.7 (www.typescriptlang.org), checked 2026-10-11
Done when: Running tsc with no source files yet reports no configuration errors about module or module resolution.
Steps
1. Write a first TypeScript file with one typed function, compile it with tsc, and run the emitted JavaScript file with node.
This is the whole loop in miniature: the compiler checks and emits, and Node.js only ever sees the JavaScript output.
Explained in: TypeScript: Documentation - TypeScript Tooling in 5 minutes (www.typescriptlang.org), checked 2026-10-11
Done when: A JavaScript file appears next to or under the output folder and node prints the expected result from it.
2. Write object types for a customer API payload, using optional properties and union types for fields that can be absent or take several shapes.
Field work is mostly modeling data you do not control. Optional and union members force every caller to handle the cases the payload really has.
Explained in: TypeScript: Documentation - Everyday Types (www.typescriptlang.org), checked 2026-10-11
Done when: Reading an optional field without a check produces a compiler error, and adding the check removes it.
3. Create a discriminated union for three webhook event kinds and write a switch over the discriminant that handles each one.
A shared literal tag lets the compiler narrow each branch to the right shape. It is the standard way to model event and message data.
Explained in: TypeScript: Documentation - TypeScript for Functional Programmers (www.typescriptlang.org), checked 2026-10-11
Done when: Inside each case the editor or compiler accepts only that event kind's fields, and adding a fourth kind surfaces an unhandled case.
4. Write a typed configuration object for your service and check it with the satisfies operator instead of a type annotation.
The satisfies operator validates the object against a type while keeping the precise inferred type of each key, which catches typos without losing detail.
Explained in: TypeScript: Documentation - TypeScript 4.9 (www.typescriptlang.org), checked 2026-10-11
Done when: A misspelled key is rejected by the compiler while a correctly spelled key keeps its specific literal or member type on hover.
5. Compile a file that declares an interface, open the emitted JavaScript, then run it with a JSON input that breaks the interface.
Types are erased at compile time, so nothing in the output checks incoming data. Seeing a wrong-shaped value pass through makes the need for runtime validation concrete.
Explained in: TypeScript: Documentation - TypeScript for the New Programmer (www.typescriptlang.org), checked 2026-10-11
Done when: The emitted file contains no trace of the interface, and the program accepts the malformed input without any error from the type system.
6. Write a validation function that takes an unknown value, checks its fields at runtime, and narrows it to your payload type as a type predicate.
A hand-written guard is the smallest form of runtime validation, and it shows what a schema library such as Zod automates for you.
Explained in: TypeScript: Documentation - TypeScript 5.5 (www.typescriptlang.org), checked 2026-10-11
Done when: The malformed input from the previous step is now rejected at runtime, and code after the guard sees the narrowed type.
7. Write two async functions that return promises, await them together, and log around timers and awaits to observe the order in which callbacks run.
Node.js runs your code on one thread and schedules continuations on the event loop. The compiler only tracks what a promise resolves to, not when it resolves.
Explained in: TypeScript: Documentation - TypeScript 4.5 (www.typescriptlang.org), checked 2026-10-11
Done when: The combined result has the unwrapped element types on hover, and you can explain the printed order of synchronous, promise and timer log lines.
8. Write a small HTTP service on the built-in Node.js http module as an ES module, with a typed request body that passes through your validation function and a typed response.
Every Node.js framework sits on this module, so doing it once by hand shows where untyped bytes enter and where your types begin. Fastify and Express then add routing and schemas on top.
Explained in: TypeScript: Documentation - TypeScript 4.7 (www.typescriptlang.org), checked 2026-10-11
Done when: A local request with a valid body returns a success response and a request with a malformed body returns a client error status.
9. Enable the erasable syntax only option, remove any enum or parameter property it flags, and run a script file directly with a recent Node.js release without compiling first.
Node.js can strip types itself when the file uses only erasable syntax. That gives fast scripts and tooling without a build step.
Explained in: TypeScript: Documentation - TypeScript 5.8 (www.typescriptlang.org), checked 2026-10-11
Done when: The compiler reports no erasable syntax errors and node runs the TypeScript script file directly.
10. Write relative imports with TypeScript file extensions, enable relative import extension rewriting, and compare the import paths in the source with those in the emitted output folder.
The same source then works both when run directly and when compiled, and comparing the two outputs shows exactly what the compiler changes on emit.
Explained in: TypeScript: Documentation - TypeScript 5.7 (www.typescriptlang.org), checked 2026-10-11
Done when: The emitted files import JavaScript extensions while the sources import TypeScript extensions, and both forms run.
11. Package the shared types and validation code as a small library by enabling declaration output with isolated declarations and adding explicit return types where the compiler asks.
Declaration files are what consumers of an SDK see. Isolated declarations keep the public surface explicit and fast to generate.
Explained in: TypeScript: Documentation - TypeScript 5.5 (www.typescriptlang.org), checked 2026-10-11
Done when: The build emits a declaration file for each exported module and reports no isolated declarations errors.
12. Configure a type-check script that runs tsc without emitting, pin the TypeScript version in the manifest, then reproduce the check from a clean install and fix whatever the TypeScript 6.0 defaults surface.
A pinned compiler and a single check command are what a CI job runs, and newer defaults for root directory and ambient types break unpinned projects silently.
Explained in: TypeScript: Documentation - TypeScript 6.0 (www.typescriptlang.org), checked 2026-10-11
Done when: After deleting installed dependencies and reinstalling, the type-check script exits cleanly with the same result as before.
13. Create a production-like layout with a shared strict base configuration that the service configuration and the library configuration both extend, and build both from the project root.
Real codebases share one set of strictness and module rules across packages. Extending several configuration files keeps those rules in one place.
Explained in: TypeScript: Documentation - TypeScript 5.0 (www.typescriptlang.org), checked 2026-10-11
Done when: Changing one strictness option in the base file changes the errors reported in both the service and the library on the next build.