• Share
  • Share

Building applications with TypeScript

Michael Wilson | March 4, 2016

At Slalom, we love JavaScript. We use it to build innovative new functionality into web applications, and we use it to build highly-performant APIs using Node.js. JavaScript is among the most popular programming languages in the world, enabling developers from Slalom, our clients, and our partners to share common paradigms when we’re building web applications.

JavaScript, however, is far from perfect. It’s an untyped, interpreted programming language that’s frequently used for passing around objects to and from external servers. It doesn’t impose many of the restrictions that you’ll find in a compiled language like Java or C#, and while that freedom provides developers with a lot of power, it can also lead to chaos when building large web applications.

Over time, developers have taken various approaches to avoid this chaos. We JavaScript developers are as committed to good development practices as any of our colleagues. When we’re leading development teams, or serving as front-end architects, we work to share and maintain these best practices across our teams, however large or small. That said, the best tool for ensuring consistency across a codebase has always been a compiler.

In 2012, Microsoft introduced TypeScript, a programming language heavily inspired by C#. You may know TypeScript as the language being used to build Angular 2, but this post will focus on the benefits that TypeScript can offer you no matter what framework you choose to use in your web applications and APIs. We’ll provide examples of common JavaScript problems that TypeScript was designed to address, and we’ll show you a more complete example of TypeScript in action in the form of a small application we’ve built using TypeScript, Express, and Gulp.

Getting started

TypeScript code doesn’t run in the browser– rather, developers write their code in TypeScript, and the TypeScript compiler converts it into JavaScript, which the browser executes. For this to work, the development workflow needs an additional step in which the TypeScript code is compiled before being provided to the browser. Such compilation steps weren’t common when TypeScript was introduced, but the rapid rise of front-end development tools like Gulp and Browserify has made the idea of a front-end compiler more palatable.

If you’re using Visual Studio, you can take advantage of built-in support for TypeScript, including compiler warnings and IntelliSense. Similar support is available from other IDEs and editors, including WebStorm and Atom. That said, we recommend an editor-agnostic approach. It’s important to ensure that your development workflow is not tied to any particular IDE or editor. Not only does this keep costs down, but it also enables you to bring new developers onboard quickly.

For an editor-agnostic approach, you can use Gulp along with the gulp-tslint and gulp-typescript plugins to lint and compile TypeScript code. Users who prefer Grunt can use the grunt-typescript and grunt-tslint plugins, which provide similar functionality.

Types

TypeScript’s name is based on its primary feature: support for types. The benefits of types become clear as soon as your application has a service object with more than one client.

Let’s create an application for managing tasks. Within it, we’ll create a service to handle task operations. This service has a method called getTask() that accepts a task ID.

One developer writes a script that uses this task manager to retrieve a task. When doing so, he provides the task ID as an integer.

Another developer writes a script that provides the task ID as a string.

Within your application, this might not be a problem. But once you start integrating with external services, such as APIs, this could lead to problems. Perhaps TaskManager.getTask() issues a JSON object to an API, a JSON object that contains the provided ID as part of an API query. If the API is expecting an integer, and is provided with a string, no results will be returned even if results exist.

In JavaScript, one way to get around this problem would be to coerce the incoming ID to an integer:

TypeScript offers a better solution, though: Specify that TaskManager.getTask() is expecting a numeric argument.

Now, any clients of TaskManager.getTask() will know that they need to provide a numeric argument when calling this method. If they provide a string, the compiler will throw an error. If the developer is using an editor with TypeScript support, she’ll be warned about the problem right away– just as we’ve come to expect with compiled languages like C# and Java.

Interfaces

Support for types helps reduce the chaos of JavaScript development in ways that extend well beyond the previous example. Imagine working with a method that expects a configuration object instead of a primitive. To illustrate this, let’s create an APIManager that expects to be provided with a configuration object.

Now, let’s assume that there are many places in our codebase where APIManager is configured. At some point, the developer working on APIManager decides to rename “baseURL” to just “url”. This is a change that will break all clients of APIManager. In a large JavaScript codebase, such an issue won’t be discovered until runtime; but with TypeScript, you can set APIManager.configure() to expect an object that conforms to a particular interface.

With this, the compiler will notify you about any usage of APIManager.configure() that doesn’t provide an object with a ‘url’ property– specifically, a ‘url’ property that is a string.

Modules

On JavaScript-focused development teams, Node.js is very popular, particular when used in conjunction with the Express framework. Developers use it to build their APIs using JavaScript. One advantage of doing so is that model code can be shared between the API and the front-end. However, this causes problems for teams using AMD modules. Node.js uses the CommonJS module format, which differs both syntactically and conceptually. It’s possible to negotiate the differences between the two using various adapter libraries, but TypeScript offers a better solution: write your code once, and compile it into whatever module format your targets need.

Keeping with our Task Manager example, let’s create a model for tasks:

Now, we can compile one version for Node.js:

And compile another version as an AMD module:

TypeScript in action: the task manager application

As an illustration of the aforementioned concepts, we’ve built an example application called Task Manager. It comprises a REST API built on Express as well as front-end code that communicates with the API. Concepts illustrated in this application include:

  • Editor-agnostic development workflow
  • Integration with third-party libraries and frameworks
  • Support for multiple target types
  • Unit testing TypeScript code

The full application is available here. Let’s walk through each of the aforementioned concepts, using examples from the application.

Editor-agnostic development workflow

Web developers have faced many challenges over the years, and in that time, Microsoft has continued to build new products for web developers to use. Many of them were only available to developers that were willing to commit to a Windows stack. Even some of the client-side solutions, such as Silverlight, weren’t available on all platforms. And if a developer wanted to build web applications using on a Microsoft stack, she needed to use Visual Studio, an IDE that is only available on Windows and can be very expensive.

With TypeScript, Microsoft is taking a very different approach. TypeScript is a strict superset of JavaScript with support for versions up to and including ECMAScript 6, enabling developers to use a familiar syntax while also providing additional language features including types, interfaces, and generics. The language and its tools are open source, a decision that has facilitated a wide array of excellent tools for developers to use. Developers do not need Visual Studio in order to build TypeScript applications– third-party IDEs such as WebStorm include built-in support for it, and tooling is also available for editors such as Atom. TypeScript supports the use of a project file, tsconfig.json, that IDEs and editors use to set development preferences.

The example application uses a tsconfig.json project file, relying on Gulp, tslint, and the TypeScript compiler to validate and compile code. This enables a development workflow that is completely editor-agnostic. A similar workflow can be achieved using Grunt, though we won’t go into detail here on Grunt configuration.

Integration with third-party libraries and frameworks

TypeScript supports the thousands of excellent JavaScript libraries and frameworks that are currently available. For any method that your code uses, TypeScript needs to know, at compile time, what type of data that method expects. When you use a library that is not written in TypeScript, you can provide this type information to TypeScript by using a type declaration file that describes the library, its methods, and the types they expect.

Most popular third-party libraries and frameworks already have type declarations available for you to use courtesy of the folks at DefinitelyTyped. These declarations are available at the DefinitelyTyped GitHub repository, but an even easier way to use them is via the tsd declaration manager, which the example application uses.

Working with JavaScript libraries requires you, as a developer, to consider how you want to write code that integrates with them. These libraries were written in a language that does not support interfaces, and may have very specific requirements about how clients should integrate them. The example application illustrates two possible approaches to this problem.

One approach is to create an interface that describes the functionality you want, and then work around the third-party library’s expectations when you implement the interface. In the example application, this approach was taken when adding generic data driver support to the DataManager object. An interface was created, requiring classes to return promises:

When adding in the MongoDB driver for Node.js, a wrapper class had to be created. That driver uses callbacks instead of promises, whereas the IDataDriver interface specifies that methods must return a promise.

Another approach is to create a class that is designed to integrate specifically with the library or framework that you’re using. The example application uses this approach when working with Express. Among other things, Express is used for configuring API routes, and when doing so it expects to be provided with anonymous functions that are provided with request and response objects when called. The example application accounts for this using an ExpressController that manages communication between Express and a more generic data manager.


Support for multiple target types

The TypeScript code that you write is compiled into regular JavaScript. The TypeScript compiler does this for you, and is capable of compiling your code for different targets. This can mean compiling into JavaScript code that’s compatible with different versions of ECMAScript. By default, TypeScript compiles into ES3-compatible JavaScript, but also supports ES5 and even ES6:

If any of your code would be syntactially incompatible with your specified target, the compiler will warn you about it. You can put this target value into your tsconfig.json file to be provided with these warnings by your IDE or editor while you’re writing code.

Earlier, we mentioned that TypeScript can compile modules into different formats. For example, if you wanted to compile code into an AMD module:

Compiling one source file into two different module formats is a less common use case for smaller applications. For larger applications, though, AMD modules are useful– commonly-used for applications where features are provided to users based on their permissions. The example application illustrates how you can do this by compiling for more than one target. It’s a distinction that becomes relevant not only at runtime, but also when executing unit tests.

Unit test TypeScript code

Unit testing is as crucial on the front-end as it is in any other layer of the application, if not more so. Unit testing allows developers to quickly test assumptions about the design of their services and data structures. Additionally, front-end code often is focused on integration with several libraries, frameworks, and APIs, and a comprehensive testing harness enables developers to quickly validate new versions of libraries or frameworks, while also providing new developers on the team a window into the assumptions that have previously been made about the format of data provided by APIs. This post won’t attempt to provide comprehensive overview of front-end unit testing; instead, we’ll provide examples of how you can write unit tests for your TypeScript code.

Any unit tests that you write for TypeScript code should themselves be written in TypeScript. Your tests will be compiled into JavaScript and executed using your preferred test runner. The example application illustrates how you can compile TypeScript code for different targets. You’ll want to test your code according to its intended target– which means that our API tests will be compiled into CommonJS modules, and our UI tests will be compiled into AMD modules. As with all other TypeScript code, this distinction between targets isn’t important when we’re writing our tests, since TypeScript will solve those problems for us. However, there are some differences in how the tests will be executed that are important to understand.

All of our unit tests use the Mocha test framework. Since Mocha can also serve as a test runner, executing our Mocha unit tests therefore becomes a simple matter of calling Mocha from within a Gulp task. The gulp-mocha plugin can help us here:

Now, executing gulp test:api will compile and execute all of our API unit tests.

Unit tests for the UI are a little more complicated to execute. They’re compiled into AMD modules, which means that we’ll need an AMD module loader as part of our test harness. When we run the application, we use RequireJS for this purpose. Our test harness can use it as well, thanks to the Karma test runner, which will run our unit tests inside of a headless web browser– specifically, PhantomJS. When combined with the karma-requirejs plugin, this provides us with a test harness in which our AMD modules can be unit-tested.

In addition to a Gulp task, we’ll need two other pieces of configuration to make this happen. The first is a Karma configuration file, which sits at the root of our source tree:

If you’re not using karma-requirejs, this configuration file is usually all that you need to start running tests with Karma. Since we are using karma-requirejs, though, we’ll need an additional test runner that can provide RequireJS with the configuration information that it needs in order to know where it can find third-party libraries. For an example, our unit test may state that it needs Lodash, and when it does so, it doesn’t need to specify the location of the Lodash library. As long as we have a type declaration file, our IDE knows how to describe the Lodash library, and can provide us with IntelliSense and compiler warnings. RequireJS doesn’t know about type declarations, though, and will need to be provided with the location of the Lodash library.

The final piece will be to add a Gulp task for executing our UI unit tests. No special Gulp plugin is needed for this; we can use the Karma library directly.


An example unit test

Front-end developers have a wide variety of tools available for writing unit tests. The example application uses Mochafor its test framework, Chai andChai-as-Promised for test assertions, and Sinon for generating spies, stubs, and mocks. These libraries are widely-used by JavaScript developers, and can be used just as effectively with TypeScript.

A bright future for TypeScript

Web development standards and tooling are advancing at a faster rate than ever before. ECMAScript 6 was finalized only a few months ago, and it will be some time before it has widespread browser adoption, yet developers are already using it in web applications via compilers such as Google’s Traceur. Interest in the next generation of JavaScript has never been higher. And yet interest in TypeScript remains higher still.

That’s not surprising when considering that TypeScript is now in its third year of availability and continues to receive regular updates with new features. Tools for TypeScript development continue to evolve as well, with built-in support available in free editors like Visual Studio Code and popular IDEs such as WebStorm. If you’d like to build something new using TypeScript, the next-generation version of Angular is written in TypeScript, and is available now in a Developer Preview release.

Further reading

Michael Wilson headshot

Michael Wilson is a Solution Architect in Slalom Silicon Valley's Technology Enablement practice, where he builds beautiful applications that help clients find insights in vast amounts of data. Follow Michael on LinkedIn.