
TypeScript 4 Design Patterns and Best Practices
By :

When you download and install the source code that accompanies this book, you will find that the files are structured like a typical TypeScript project. We have included all the libraries and configurations you need to evaluate all the examples within the command line or via VSCode. It's useful to know what libraries are included in the examples and what problems they solve. Understanding the different tsconfig
parameters that determine the behavior of the tsc compiler is helpful. You also need to be aware of how to run or debug the unit tests using Jest.
This section covers the following topics:
Let's get started.
We have included several references from external libraries in this book's source code. Our aim is to help you review several of the design patterns within a specific use case. Here is an overview of what they are and what problems they solve:
React
: React is the most popular library for creating user interfaces right now, and it promotes some useful patterns such as composition, component factories, and higher-order components. We will explain the usage of TypeScript with React in Chapter 2, TypeScript Core Principles.Express
: When building web services using TypeScript in Node.js, we want to use a minimal web framework. Express is the most stable choice when creating Node.js applications because it promotes modularity and performance. You will learn more about how to use TypeScript in the server in Chapter 2, TypeScript Core Principles.immutable.js
: This is a library that provides immutable data structures and the relevant utility methods for working with them in an efficient way. Immutability is a concept that we use quite frequently in functional programming, where we do not allow objects to be modified or altered once they have been created. We will learn more about immutability in Chapter 6, Functional Programming with TypeScript.fp-ts
: This is a library that exposes functional programming abstractions such as Monads, Options, and Lens. We will learn more about functional programming in Chapter 6, Functional Programming with TypeScript.rx.js
: This is a library that offers reactive programming abstractions such as Observables in a nice API. Using Observables can help us develop scalable and resilient applications. You will learn more about Observables in Chapter 7, Reactive Programming with TypeScript.inversify.js
: This is a lightweight inversion of control container, or IOC for short. We use IOC to handle object instantiation and lifetime guarantees, as well as to apply Single Responsibility, Open-Closed, Liskov-Substitution, Interface Segregation, and Dependency Inversion (SOLID) principles to our abstractions. We are going to explain more about these SOLID principles in Chapter 8, Developing Modern and Robust TypeScript Applications.Using libraries is an excellent way to promote reusability and reliability. Quite often, when developing enterprise software, there is already a stack preconfigured for you, unless you are exceptionally fortunate to work in greenfield projects.
Next, we will learn how to configure the TypeScript compiler using tsconfig.json
.
When you have a TypeScript source code inside a folder, the Typescript compiler needs to be able to find those files and compile them with some specific flags. Using a tsconfig.json
or a jsconfig.json
file determines the configuration-specific behavior of the compiler.
In most cases, you will only need one tsconfig.json
file to manage all source code, but this is not a requirement. In this book, we will use a more flexible approach when compiling the source code examples.
We have a base tsconfig.json
file that pertains to all the common compiler flags for all the chapters in this book. Then, each chapter will contain its own tsconfig.json
, which inherits from the base config.
To understand what these flags are and what they do, let's describe them briefly now:
module
: Modules define how imports and exports work. In this book, we are using CommonJS, which is the format used for Node.js projects. This means that the code generation will create the relevant require
statements. For example, you can inspect the compiled code of the code generation inside the dist
folder.target
: This specifies the actual code generation target, such as ES6, ES2017, ESNEXT, and so on. This means that some features may not work in all environments, such as Node.js or older browsers. In this project, we will use ES5, which is the lowest common denominator; it has great support.noImplicitAny
: This prevents the program from compiling when TypeScript infers the type as any
. This happens relatively often when you define a function without specifying the types for the parameters. For example, the following program (degToRad.ts
) does not compile when this flag is true:const degToRad = (degree): number => (degree * Math.PI) / 180; > npx tsc --build chapters/chapter-1_Getting_Started_With_Typescript_4 chapters/chapter-1_Getting_Started_With_Typescript_4/degToRad.ts:1:19 - error TS7006: Parameter 'degree' implicitly has an 'any' type. 1 const degToRad = (degree): number => (degree * Math.PI) / 180;
strictNullChecks
: This rule makes undefined and null checks more prevalent. Any time the compiler infers a type as undefined, it will follow any code that does not ensure that null is left unchecked, and it will raise an error.experimentalDecorators
and emitDecoratorMetadata
: In some examples, especially when using Inversify.js
, we use decorators, which are experimental features of JavaScript. Decorators are a very interesting concept, and they also have a relevant design pattern for using classes. Enabling these two flags is a requirement with TypeScript. sourceMap
: This enables source maps that are used when debugging TypeScript code. This will allow, for example, VSCode or the browser to show the original uncompiled Typescript code when stopping at breakpoints.There are also many more compiler flags available that can tweak different aspects of the system. These options usually tweak more specific aspects of the compiler by customizing the restrictiveness of the type checks. For example, using strictBindCallApply
or strictFunctionTypes
may introduce more type checks for the Bind
, Call
, Apply
, or Function
types. Before enabling any extra flags, it is recommended that you achieve consensus with your colleagues to avoid any confusion.
As we mentioned previously, you can run unit tests using the Jest runner. This is a popular testing framework for TypeScript and JavaScript projects as it is easy to get started and it has good integrations with major frameworks. Here, we have provided configuration options for running the unit tests right from VSCode.
To run the tests, you'll have to execute the following command in the console:
npm run-script test
For example, there is a file named mul.ts
that includes a function for multiplying two numbers:
mul.ts
function mul(a: number, b: number) { return a * b; } export default mul;
Then, we also have the test file for this function, which has the same filename but with a test.ts
extension:
mul.test.ts
import mul from "./mul"; test("multiplies 2 and 3 to give 6", () => { expect(mul(2, 3)).toBe(6); });
When you execute these test cases, you will see the runner results:
npm test > [email protected] test TypeScript-4-Design-Patterns-and-Best-Practices > jest PASS chapters/chapter-1_Getting_Started_With_Typescript_4/mul.test.tsmultiplies 2 and 3 to give 12 (1 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total
We will frequently use Jest to verify some assumptions of design patterns. For example, we will test whether the Singleton design pattern uses only one instance and does not create more, or whether the Factory pattern constructs objects with the right type and nothing else. Writing good unit test cases is often a requirement before releasing code to production, so it's crucial to always test your abstractions promptly.
Change the font size
Change margin width
Change background colour