Full-Stack React, TypeScript, and Node

5 (2 reviews total)
By David Choi
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Chapter 1: Understanding TypeScript

About this book

React sets the standard for building high-performance client-side web apps. Node.js is a scalable application server that is used in thousands of websites, while GraphQL is becoming the standard way for large websites to provide data and services to their users. Together, these technologies, when reinforced with the capabilities of TypeScript, provide a cutting-edge stack for complete web application development.

This book takes a hands-on approach to implementing modern web technologies and the associated methodologies for building full-stack apps. You’ll begin by gaining a strong understanding of TypeScript and how to use it to build high-quality web apps. The chapters that follow delve into client-side development with React using the new Hooks API and Redux. Next, you’ll get to grips with server-side development with Express, including authentication with Redis-based sessions and accessing databases with TypeORM. The book will then show you how to use Apollo GraphQL to build web services for your full-stack app. Later, you’ll learn how to build GraphQL schemas and integrate them with React using Hooks. Finally, you’ll focus on how to deploy your application onto an NGINX server using the AWS cloud.

By the end of this book, you’ll be able to build and deploy complete high-performance web applications using React, Node, and GraphQL.

Publication date:
December 2020
Publisher
Packt
Pages
648
ISBN
9781839219931

 

Chapter 1: Understanding TypeScript

JavaScript is an enormously popular and powerful language. According to GitHub, it is the most popular language in the world (yes, used even more than Python), and the new features in ES6+ continue to add useful capabilities. However, for large application development, its feature set is considered to be incomplete. This is why TypeScript was created.

In this chapter, we'll learn about the TypeScript language, why it was created, and what value it provides to JavaScript developers. We'll learn about the design philosophy Microsoft used in creating TypeScript and why these design decisions added important support in the language for large application development.

We'll also see how TypeScript enhances and improves upon JavaScript. We'll compare and contrast the JavaScript way of writing code with TypeScript. TypeScript has a wealth of cutting-edge features to benefit developers. Chief among them are static typing and Object-Oriented Programming (OOP) capabilities. These features can make for code that is higher quality and easier to maintain.

By the end of this chapter, you will understand some of the limitations of JavaScript that make it difficult to use in large projects. You will also understand how TypeScript fills in some of those gaps and makes writing large, complex applications easier and less prone to error.

In this chapter, we're going to cover the following main topics:

  • What is TypeScript?
  • Why is TypeScript necessary?
 

Technical requirements

In order to take full advantage of this chapter, you should have a basic understanding of JavaScript version ES5 or higher and some experience with building web applications with a JavaScript framework. You'll also need to install Node and a JavaScript code editor, such as Visual Studio Code (VSCode).

You can find the GitHub repository for this chapter at https://github.com/PacktPublishing/Full-Stack-React-TypeScript-and-Node. Use the code in the Chap1 folder.

 

What is TypeScript?

TypeScript is actually two distinct but related technologies – a language and a compiler:

  • The language is a feature-rich, statically typed programming language that adds true object-oriented capabilities to JavaScript.
  • The compiler converts TypeScript code into native JavaScript, but also provides the programmer with assistance in writing code with fewer errors.

TypeScript enables the developer to design software that's of a higher quality. The combination of the language and the compiler enhances the developer's capabilities. By using TypeScript, a developer can write code that is easier to understand and refactor and contains fewer bugs. Additionally, it adds discipline to the development workflow by forcing errors to be fixed while still in development.

TypeScript is a development-time technology. There is no runtime component and no TypeScript code ever runs on any machine. Instead, the TypeScript compiler converts TypeScript into JavaScript and that code is then deployed and run on browsers or servers. It's possible that Microsoft considered developing a runtime for TypeScript. However, unlike the operating system market, Microsoft does not control the ECMAScript standards body (the group that decides what will be in each version of JavaScript). So, getting buy-in from that group would have been difficult and time-consuming. Instead, Microsoft decided to create a tool that enhances a JavaScript developer's productivity and code quality.

So then, if TypeScript has no runtime, how do developers get running code? TypeScript uses a process called transpilation. Transpilation is a method where code from one language is "compiled" or converted into another language. What this means is that all TypeScript code ultimately is converted into JavaScript code before it is finally deployed and run.

In this section, we've learned what TypeScript is and how it works. In the next section, we'll learn about why these features are necessary for building large, complex applications.

 

Why is TypeScript necessary?

The JavaScript programming language was created by Brendan Eich and was added to the Netscape browser in 1995. Since that time, JavaScript has enjoyed enormous success and is now used to build server and desktop apps as well. However, this popularity and ubiquity have turned out to be a problem as well as a benefit. As larger and larger apps have been created, developers have started to notice the limitations of the language.

Large application development has greater needs than the browser development JavaScript was first created for. At a high level, almost all large application development languages, such as Java, C++, C#, and so on, provide static typing and OOP capabilities. In this section, we'll go over the advantages of static typing over JavaScript's dynamic typing. We'll also learn about OOP and why JavaScript's method of doing OOP is too limited to use for large apps.

But first, we'll need to install a few packages and programs to allow our examples. To do this, follow these instructions:

  1. Let's install Node first. You can download Node from here: https://nodejs.org/. Node gives us npm, which is a JavaScript dependency manager that will allow us to install TypeScript. We'll dive deep into Node in Chapter 8, Learning Server-Side Development with Node.js and Express.
  2. Install VSCode. It is a free code editor and its high-quality and rich features have quickly made it the standard development application for writing JavaScript code on any platform. You can use any code editor you like, but I will use VSCode extensively in this book.
  3. Create a folder in your personal directory called HandsOnTypeScript. We'll save all our project code into this folder.

    Important Note

    If you don't want to type the code yourself, you can download the full source code as mentioned in the Technical requirements section.

  4. Inside HandsOnTypeScript, create another folder called Chap1.
  5. Open VSCode and go to File | Open, and then open the Chap1 folder you just created. Then, select View | Terminal and enable the terminal window within your VSCode window.
  6. Type the following command into the terminal. This command will initialize your project so that it can accept npm package dependencies. You'll need this because TypeScript is downloaded as an npm package:
    npm init

    You should see a screen like this:

    Figure 1.1 – npm init screen

    Figure 1.1 – npm init screen

    You can accept the defaults for all the prompts as we will only install TypeScript for now.

  7. Install TypeScript with the following command:
    npm install typescript

After all the items have been installed, your VSCode screen should look like this:

Figure 1.2 – VSCode after setup is complete

Figure 1.2 – VSCode after setup is complete

We've finished installing and setting up our environment. Now, we can take a look at some examples that will help us better understand the benefits of TypeScript.

Dynamic versus static typing

Every programming language has and makes use of types. A type is simply a set of rules that describe an object and can be reused. JavaScript is a dynamically typed language. In JavaScript, new variables do not need to declare their type and even after they are set, they can be reset to a different type. This feature adds awesome flexibility to the language, but it is also the source of many bugs.

TypeScript uses a better alternative called static typing. Static typing forces the developer to indicate the type of a variable up front, when they create it. This removes ambiguity and eliminates many conversion errors between types. In the following steps, we'll take a look at some examples of the pitfalls of dynamic typing and how TypeScript's static typing can eliminate them:

  1. On the root of the Chap1 folder, let's create a file called string-vs-number.ts. The .ts file extension is a TypeScript specific extension and allows the TypeScript compiler to recognize the file and transpile it into JavaScript. Next, enter the following code into the file and save it:
    let a = 5;
    let b = '6';
    console.log(a + b);
  2. Now, in the terminal, type the following:
    tsc string-vs-number.ts

    tsc is the command to execute the TypeScript compiler, and the filename is telling the compiler to check and transpile the file into JavaScript.

  3. Once you run the tsc command, you should see a new file, string-vs-number.js, in the same folder. Let's run this file:
    node string-vs-number.js

    The node command acts as a runtime environment for the JavaScript file to run. The reason why this works is that Node uses Google's Chrome browser engine, V8, to run JavaScript code. So, once you have run this script, you should see this:

    56

    Obviously, if we add two numbers together normally, we want a sum to happen, not a string concatenation. However, since the JavaScript runtime has no way of knowing this, it guesses the desired intent and converts the a number variable into a string and appends it to variable b. This situation may seem unlikely in real-world code but if left unchecked it could occur, because in web development, most inputs coming in from HTML come in as strings—even if the user types a number.

  4. Now, let's introduce TypeScript's static typing into this code and see what happens. First, let's delete the .js file, as the TypeScript compiler may consider there to be two copies of the a and b variables. Take a look at this code:
    let a: number = 5;
    let b: number = '6';
    console.log(a + b);
  5. If you run the tsc compiler on this code, you will get the error Type "'6'" is not assignable to the type 'number'. This is exactly what we want. The compiler tells us that there is an error in our code and prevents the compilation from successfully compiling. Since we indicated that both variables are supposed to be numbers, the compiler checks for that and complains when it finds it not to be true. So, if we fix this code and set b to be a number, let's see what happens:
    let a: number = 5;
    let b: number = 6;
    console.log(a + b);
  6. Now, if you run the compiler, it will complete successfully, and running the JavaScript will result in the value 11:
Figure 1.3 – Valid numbers addition

Figure 1.3 – Valid numbers addition

Great, when we set b incorrectly, TypeScript caught our error and prevented it from being used at runtime.

Let's look at another more complex example, as it's like what you might see in larger app code:

  1. Let's create a new .ts file called test-age.ts and add the following code to it:
    function canDrive(usr) {    
        console.log("user is", usr.name);     
        
        if(usr.age >= 16) {
            console.log("allow to drive");
        } else {
            console.log("do not allow to drive");
        }
    } 
        
    const tom = { 
        name: "tom"
    } 
    canDrive (tom); 

    As you can see, the code has a function that checks the age of a user and determines, based on that age, whether they are allowed to drive. After the function definition, we see that a user is created, but with no age property. Let's pretend that the developer wanted to fill that in later based on user input. Now, below that user creation, the canDrive function is called and it claims the user is not allowed to drive. If it turned out that user tom was over 16 years old and this function triggered another action to be taken based on the user's age, obviously this could lead to a whole host of issues.

    There are ways in JavaScript to deal with this problem, or at least partially. We could use a for loop to iterate through all of the property key names of the user object and check for an age name. Then, we could throw an exception or have some other error handler to deal with this issue. However, if we had to do this on every function, it would become inefficient and onerous very quickly. Additionally, we would be doing these checks while the code is running. Obviously, for these errors, we would prefer catching them before they make it out to users. TypeScript provides a simple solution to this issue and catches the error before the code even makes it into production. Take a look at the following updated code:

    interface User {
        name: string;
        age: number;
    }
        
    function canDrive(usr: User) {     
        console.log("user is", usr.name);     
        
        if(usr.age >= 16) {
            console.log("allow to drive");
        } else {
            console.log("do not allow to drive");
        }
    } 
            
    const tom = { 
        name: "tom"
    } 
    canDrive (tom); 

    Let's go through this updated code. At the top, we see something called an interface and it is given a name of User. An interface is one possible kind of type in TypeScript. I'll detail interfaces and other types in later chapters, but for now, let's just take a look at this example. The User interface has the two fields that we need: name and age. Now, below that, we see that our canDrive function's usr parameter has a colon and the User type on it. This is called a type annotation and it means that we are telling the compiler only to allow parameters of the User type to be given to canDrive. Therefore, when I try and compile this code with TypeScript, the compiler complains that when canDrive is called, age is missing from the passed-in parameter, because our tom object does not have that property:

    Figure 1.4 – canDrive error

    Figure 1.4 – canDrive error

  2. So, once again, the compiler has caught our error. Let's fix this issue by giving tom a type:
    const tom: User = { 
        name: "tom"
    } 
  3. If we give tom a type of User, but do not add the required age property, we get the following error:
    Property 'age' is missing in type '{ name: string; }' but required in type 'User'.ts(2741)

    However, if we add the missing age property, the error goes away and our canDrive function works as it should. Here's the final working code:

    interface User {
        name: string;
        age: number;
    }
        
    function canDrive(usr: User) {     
        console.log("user is", usr.name);     
        
        if(usr.age >= 16) {
            console.log("allow to drive");
        } else {
            console.log("do not allow to drive");
        }
    } 
        
    // let's pretend sometime later someone else uses the   //function canDrive
    const tom: User = { 
        name: "tom",
        age: 25
    } 
    canDrive (tom); 

    This code provides the required age property in the tom variable so that when canDrive is executed, the check for usr.age is done correctly and the appropriate code is then run.

Here's a screenshot of the output once this fix is made and the code is run again:

Figure 1.5 – canDrive successful result

Figure 1.5 – canDrive successful result

In this section, we learned about some of the pitfalls of dynamic typing and how static typing can help remove and protect against those issues. Static typing removes ambiguity from code, both to the compiler and other developers. This clarity can reduce errors and make for higher-quality code.

Object-oriented programming

JavaScript is known as an OOP language. It does have some of the capabilities of other OOP languages, such as inheritance. However, JavaScript's implementation is limited both in terms of available language features and design. In this section, we'll take a look at how JavaScript does OOP and how TypeScript improves upon JavaScript's capabilities.

First, let's define what OOP is. There are four major principles of OOP:

  • Encapsulation
  • Abstraction
  • Inheritance
  • Polymorphism

Let's review each one.

Encapsulation

A shorter way of saying encapsulation is information hiding. In every program, you will have data and functions that allow you to do something with that data. When we use encapsulation, we are taking that data and putting it into a container of sorts. This container is known as a class in most programming languages and basically, it protects that data so that nothing outside of the container can modify or view it. Instead, if you want to make use of the data, it must be done through functions that are controlled by the container object. This method of working with object data allows strict control of what happens to that data from a single place in code, instead of being dispersed through many locations across a large application—which can be unwieldy and difficult to maintain.

There are some interpretations of encapsulation that focus mainly on the grouping of members inside a common container. However, in the strict sense of encapsulation, information hiding, JavaScript does not have this capability built in. For most OOP languages, encapsulation requires the ability to explicitly hide a member via a language facility. For example, in TypeScript, you can use the private keyword so that a property cannot be seen or modified outside of its class. Now, it is possible in JavaScript to simulate member privacy through various workarounds, but again this is not part of the native code and adds additional complexity. TypeScript supports encapsulation with access modifiers such as private natively.

Important Note

Privacy for class fields will be supported in ECMAScript 2020. However, as this is a newer feature, it is not supported across all browsers at the time of writing.

Abstraction

Abstraction is related to encapsulation. When using abstraction, you hide the internal implementation of how data is managed and provide a more simplified interface to the outside code. Primarily, this is done to cause "loose coupling." This means that it is desirable for code that is responsible for one set of data to be independent and separated from other code. In this way, it is possible to change the code in one part of the application without adversely affecting the code in another part.

Abstraction for most OOP languages requires the use of a mechanism to provide simplified access to an object, without revealing that object's internal workings. For most languages, this is either an interface or an abstract class. We'll review interfaces more deeply in a later chapter, but for now, interfaces are like classes whose members have no actual working code. You can consider them a shell that only reveals the names and types of object members, but hides how they work. This capability is extremely important in producing the "loose coupling" mentioned previously and allowing code to be more easily modified and maintained. JavaScript does not support interfaces or abstract classes, while TypeScript supports both features.

Inheritance

Inheritance is about code reuse. For example, if you needed to create objects for several types of vehicles—car, truck, and boat—it would be inefficient to write distinct code for each vehicle type. It would be better to create a base type that has the core attributes of all vehicles, and then reuse that code in each specific vehicle type. This way, we write some of the needed code only once and share it across each vehicle type.

Both JavaScript and TypeScript support classes and inheritance. If you're not familiar with classes, a class is a kind of type that stores a related set of fields and also may have functions that can act on those fields. JavaScript supports inheritance by using a system called prototypical inheritance. Basically, what this means is that in JavaScript, every object instance of a specific type shares the same instance of a single core object. This core object is the prototype, and whatever fields or functions are created on the prototype, they are accessible across the various object instances. This is a good way of saving resources, such as memory, but it does not have the level of flexibility or sophistication of the inheritance model in TypeScript.

In TypeScript, classes can inherit from other classes but they can also inherit from interfaces and abstract classes. Since JavaScript does not have these features, in comparison, its prototypical inheritance is limited. Additionally, JavaScript has no ability to inherit from multiple classes directly, which is another method of doing code reuse called multiple inheritance. But TypeScript does allow multiple inheritance using mixins. We'll dive deep into all these features later, but basically, the point is that TypeScript has a more capable inheritance model that allows for more kinds of inheritance and therefore more ways to reuse code.

Polymorphism

Polymorphism is related to inheritance. In polymorphism, it is possible to create an object that can be set to one of any number of possible types that inherit from the same base lineage. This capability is useful for scenarios where the type needed is not immediately knowable but can be set at runtime once the appropriate circumstances have arisen.

This feature is used less often in OOP code than some of the other features, but nevertheless can be useful. In the case of JavaScript, there is no direct language support for polymorphism, but due to its dynamic typing, it can be simulated reasonably well (some JavaScript enthusiasts will strongly disagree with this statement, but please hear me out).

Let's look at an example. It is possible to use JavaScript class inheritance to create a base class and have multiple classes that inherit from this one parent base class. Then, by using standard JavaScript variable declaration, which does not indicate the type, we can set the type instance at runtime to whichever inheriting class is appropriate. The issue I find is that there is no way to force the variable to be of a specific base type since there is no way to declare types in JavaScript, therefore there is no way of enforcing only classes that inherit from the one base type during development. So, again, you have to resort to workarounds such as using the instanceof keyword in order to test for certain types at runtime, to try and enforce type safety.

In the case of TypeScript, static typing is on by default and forces type declaration when the variable is first created. Additionally, TypeScript supports interfaces, which can be implemented by classes. Therefore, declaring a variable to be of a specific interface type forces all classes instantiated to that variable to be inheritors of the same interface. Again, this is all done at development time before code is deployed. This system is more explicit, enforceable, and reliable than the one in JavaScript.

In this section, we have learned about OOP and its importance in large application development. We've also understood why TypeScript's OOP capabilities are significantly better and more feature-rich than JavaScript's.

 

Summary

In this chapter, we introduced TypeScript and learned why it was created. We learned why type safety and OOP capabilities can be so important for building large apps. Then, we saw some examples comparing dynamic typing and static typing and saw why static typing might be a better way of writing code. Finally, we compared the styles of OOP between the two languages and learned why TypeScript has the better and more capable system. The information in this chapter has given us a good high-level conceptual understanding of the benefits of TypeScript.

In the next chapter, we'll do a deeper dive into the TypeScript language. We'll learn more about types and investigate some of the most important features of TypeScript, such as classes, interfaces, and generics. This chapter should give you a strong foundation for using the various frameworks and libraries in the JavaScript ecosystem.

About the Author

  • David Choi

    David Choi is a developer with over 10 years' experience in building enterprise-class applications using a variety of frameworks and languages. Most of his professional development experience has involved working in finance for companies such as JPMorgan, CSFB, and Franklin Templeton. He currently works at his own start-up, DzHaven, building an application to help devs help other devs.

    You can find David on YouTube at the David Choi channel, or on Twitter at jsoneaday.

    Browse publications by this author

Latest Reviews

(2 reviews total)
Everything OK, no complaints.
I am learning React and this looks like a great book to learn from.
Full-Stack React, TypeScript, and Node
Unlock this book and the full library for FREE
Start free trial