Build End-to-End Apps with TypeScript

Easily write and maintain large-scale JavaScript code

Gil Fink

March 5, 2014

13 Min Read
red bricks stacked in front of incomplete brick wall

Over the past few years, I've been working a lot with the JavaScript language and building large-scale web applications. Although I have "Stockholm Syndrome" (JavaScript is one of my favorite programming languages), other developers don't share my affection for the language. Developers don't like JavaScript for many reasons. One of the main reasons is that JavaScript is difficult to maintain in a large-scale code base. In addition, JavaScript lacks certain language features, such as modules, classes, and interfaces.

Developers can use two main approaches to avoid JavaScript pitfalls. The first approach is to use plain JavaScript with JavaScript design patterns to mimic the behavior of modules, classes, and other missing JavaScript language features. I use this approach most of the time, but it can be very difficult for junior JavaScript developers because you need know JavaScript quite well to avoid its pitfalls.

Related: Is TypeScript Ready for Prime Time?

The second approach is to use JavaScript preprocessors. JavaScript preprocessors are design-time tools that use custom languages or known languages that later compile into JavaScript. Using this approach helps you create a more object-oriented–like code base and helps code maintainability. JavaScript preprocessors such as CoffeeScript, Dart, or GWT are very popular, but they force you to learn a new language or use languages such as Java or C# that are later compiled into JavaScript. What if you could use JavaScript instead, or a variation of JavaScript?

You're probably asking yourself why you would use JavaScript or a JavaScript variant to compile into JavaScript. One reason is that ECMAScript 6, the latest JavaScript specifications, introduces a lot of the missing features in the JavaScript language. Also, because at the end of the process you get JavaScript code anyway, why not write the code in plain JavaScript from the start?

This is where TypeScript becomes very useful.

TypeScript to the Rescue

A year ago Microsoft released the first preview of a new language, TypeScript, written by a team led by Anders Hejlsberg, creator of the C# language. TypeScript is a JavaScript preprocessor that compiles into plain JavaScript. Unlike other JavaScript preprocessors, TypeScript was built as a typed superset of JavaScript, and it adds support for missing JavaScript features that aren't included in the current version of ECMAScript. TypeScript aligns to the new JavaScript keywords that will be available when ECMAScript 6, the next version of JavaScript specifications, becomes the JavaScript standard. This means that you can use language constructs such as modules and classes in TypeScript now, and later on when ECMAScript 6 becomes the standard, your code will already be regular JavaScript code.

TypeScript is cross-platform and can run on any OS because it can run wherever JavaScript can run. You can use the language to generate server-side code written in JavaScript along with client-side code also written in JavaScript. This option can help you write an end-to-end application with only one language—TypeScript.

To install TypeScript, go to the TypeScript website. On the website you'll find download links and an online playground that you can use to test the language. You can also view TypeScript demos in the website's "run it" section. The website can be very helpful for new TypeScript developers.

I don't go into great detail about TypeScript's features in this article; for a more detailed explanation of the language, see Dan Wahlin's "Build Enterprise-Scale JavaScript Applications with TypeScript." I recommend that you read Wahlin's article before you proceed any further with this article. You'll need a good understanding of what TypeScript is before you jump into writing a simple end-to-end application using the language.

Creating the Server Side with Node.js

To demonstrate the ease of using TypeScript to write an application, let's create a simple gallery of DevConnections conference photos. First, you need to create the server side. The application will use the node.js runtime to run the application back end.

Node.js is a platform to build web servers using the JavaScript language. It runs inside a Google V8 engine environment. V8 is the Chrome browser's JavaScript engine. Node.js uses an event-driven model that helps create an efficient I/O-intensive back end. This article assumes that you know a little bit about node.js and Node Packaged Modules (npm). If you aren't familiar with node.js, you should stop reading and go to the node.js website first.

Our application will also use the Express framework, which is a node.js web application framework. Express helps organize the web application server side into MVC architecture. Express lets you use view engines such as EJS and Jade to create the HTML that's sent to the client. In addition, Express includes a routes module that you can use to create application routes and to access other features that help speed up the creation of a node.js server. For further details about Express, go to the Express website.

Creating the project. To create the application, you need to install node.js Tools for Visual Studio (NTVS). (As I write this article, NTVS is currently in first alpha and might be unstable.) NTVS includes project templates for node.js projects, IntelliSense for node.js code, debugging tools, and many other features that can help you with node.js development inside Visual Studio IDE.

After you install NTVS, create a blank Express application and call it DevConnectionsPhotos. Figure 1 shows the New Project dialog box, which includes all the installed NTVS project templates.

Figure 1: Creating a Blank Express Application

When NTVS asks you whether to run npm to install the missing dependencies for the project, you should select the option to run npm and allow it to retrieve all the Express packages.

Creating the views. In the Views folder, you should replace the layout.jade file with the code in Listing 1. This code is written in Jade view engine style, and it will render the HTML layout of the main application page.

doctype htmlhtml  head    title='DevConnections Photo Gallery'    link(rel='stylesheet', href='/Content/app.css')    link(rel='stylesheet', href='/Content/themes/classic/galleria.classic.css')    script(src='/Scripts/lib/jquery-1.9.0.js')    script(src='/Scripts/lib/galleria-1.2.9.js')    script(src='/Content/themes/classic/galleria.classic.js')    script(src='/Scripts/app/datastructures.js')    script(src='/Scripts/app/dataservice.js')    script(src='/Scripts/app/bootstrapper.js')    script(src='/Scripts/app/app.js')  body    block content

You should also replace the index.jade file, which includes the content block that will be injected into the layout.jade file during runtime. The new code for the index.jade file should look like that in Listing 2.

extends layoutblock content  div#container     header    img#DevConnectionsLogo(src='/Content/Images/DevConnctionsLogo.png', alt='DevConnections Logo')    h1='DevConnections Photo Gallery'     section.main    div#galleria     img#light(src="/Content/Images/Light.png")

The index.jade file includes a declaration of a DIV element with a Galleria ID. You'll use that DIV later on the client side to show the photo gallery that you're implementing.

Implementing the server side. Before you use TypeScript, you should import the TypeScript runtime to the NTVS project. To do so, add the following line of code to the DevConnectionsPhotos.njsproj file:

<Import Project="$(VSToolsPath)\TypeScript\Microsoft.TypeScript.targets" />

This line of code imports TypeScript to the project and allows you to use it to compile TypeScript files. (Note that the TypeScript Visual Studio runtime wasn't a part of NTVS projects at the time I wrote this article.)

Now that the environment is ready and you've created the main web page, you should rename the app.js file, which exists in the root of the project, to app.ts by changing its postfix to .ts. Performing this action forces the code to run as TypeScript code rather than JavaScript code. Because TypeScript is a JavaScript superset, you can transform the app.js file, which is a simple Express template, to app.ts without any problems.

In the app.ts file, you should add a module dependency on the node.js file system module. This module exists under the name fs. To use this module, you should create a new variable called fs under the Module dependencies comment, as Listing 3 shows.

/** * Module dependencies. */var express = require('express');var routes = require('./routes');var user = require('./routes/user');var http = require('http');var path = require('path');var fs = require('fs');

You should use a function called getAllFileURIs, as in Listing 4, that receives a folder name and a callback function. The getAllFileURIs function will use the folder name to open that folder; later, it will return all the file URIs in that folder.

var getAllFileURIs = function(folder: string, callback: Function): void {    var results = [],    relativePath = folder.substr(8);    fs.readdir(folder, (err, files) => {    if (err) {        callback([]);    };    files.forEach(function(file) {        file = relativePath + '/' + file;        results.push(file);    });    callback(results);    });};

You can see that I used lambdas in the code and types for the arguments that the function receives. These features come from TypeScript and aren't currently part of JavaScript.

After you write the getAllFileURIs function, you should add an endpoint called getAllImages on your server. This endpoint uses the getAllFileURIs function to fetch all the URIs for files that exist in the /public/Content/Photos folder. Listing 5 shows what the implementation of this endpoint should look like. In Listing 5, whenever a request arrives to the getAllImages endpoint, an array of image URIs is serialized to JSON format and is written to the response.

app.get('/getAllImages', (req, res) => {    getAllFileURIs('./public/Content/Photos', (imageUris) => {    res.writeHead(200, { 'Content-Type': 'application/json' });    res.write(JSON.stringify(imageUris));    res.end();    });});

Your server code is now ready to run. Be sure to set the generated app.js file as the startup file for the node.js application. Figure 2 shows a running DevConnections photo gallery with only the server implementation. (Notice that there are no photos in the gallery yet.) Now that you have a working server side, you need to create the client side.

Figure 2: DevConnections Photo Gallery with only the Server Implementation

Creating the Client Side Using JavaScript Libraries

You'll use two libraries on the client side: jQuery and Galleria. You're probably already familiar with jQuery; Galleria is a JavaScript library that's used to create a gallery from an image array. You can download Galleria, or you can use your own gallery library as long as you adjust the code, as I show you later in this article

Setting up the folders. In the public folder that was created by the Express project template, create a Scripts folder that you'll use to hold all the scripts that the application uses. Under the Scripts folder, add two new folders named app and lib. Put all the application TypeScript files and the generated JavaScript files in the app folder. Put the jQuery and Galleria JavaScript files in the lib folder.

To use JavaScript libraries as though they were created with TypeScript, you need to import the libraries' declaration files to the project. A declaration file is a file that ends with the .d.ts postfix; it describes the interfaces of the library. Having a declaration file can help the TypeScript environment understand the types included in a typed library. However, not all libraries have a declaration file. A known GitHub repository called DefinitelyTyped includes most of the major libraries' declaration files. You should download the jquery.d.ts declaration file and put it under the lib folder. Unfortunately, Galleria isn't included in DefinitelyTyped. Now you're ready to create the TypeScript files and use the libraries.

Creating the client-side implementation. The first step in configuring the client side is to create the data structures for Galleria image information and for Galleria configuration options. Create a new TypeScript file in the app folder that exists in the Scripts folder. Call the new file datastructures.ts. Both of the classes you'll create will be a part of the app.data.structures module. The code in Listing 6 implements the data structures.

The data structure implementation is very simple. The data structures include properties that the application will later use to configure the image gallery.

After you've created the data structures, you need to configure the interaction with the server, and you need to fetch the images for the gallery. To accomplish these tasks, you need to implement a data service class. Create a new TypeScript file in the app folder that exists in the Scripts folder. Call the new file dataservice.ts. The data service's responsibility will be to call the getAllImages endpoint and use the array of image URIs to create Galleria images, as Listing 7 shows.

/// <reference path="../lib/jquery.d.ts" />/// <reference path="datastructures.ts" />module app.data {    import structures = app.data.structures;    export interface IDataService {    getImages: () => JQueryPromise;    }    export class DataService implements IDataService {    getImages(): JQueryPromise {        var deferred = $.Deferred();        var result: structures.GalleriaImage[] = [];        $.getJSON("/getAllImages", {}, (imageUris) => {        $.each(imageUris, (index, item) => {            result.push(new structures.GalleriaImage(new structures.GalleriaImageConfigOptions(item, "", "", "My title" + index, "My description" + index, "")));        });        deferred.resolve(result);        });        return deferred.promise();    }    }}

As you can see in Listing 7, one of the first steps is to import the app.data.structures module. Later on, you declare an interface that exposes a getImages function. The getImages function returns a JQueryPromise object that will help defer the execution of the getImages operation until the getJSON function returns and runs its success callback. When the getJSON success callback runs, it creates a GalleriaImage object for each image URI that's part of the array that was returned by the server.

Now that you have data structures and a data service, you need to configure the Galleria object. Create a new TypeScript file in the app folder that exists in the Scripts folder. Call the new file bootstrapper.ts. In the bootstrapper.ts file, create a Bootstrapper class that's responsible for running the Galleria object, as Listing 8 shows.

/// <reference path="../lib/jquery.d.ts" />/// <reference path="dataservice.ts" />declare var Galleria;module app {    import data = app.data;    export interface IBootstrapper {    run: () => void;    }    export class Bootstrapper implements IBootstrapper {    run() {        this.getData().then((images) => {        Galleria.configure({            imageCrop: true,            transition: 'fade',            dataSource: images,            autoplay: true        });        Galleria.run('#galleria');        });    }    getData(): JQueryPromise {        var dataService = new data.DataService();        return dataService.getImages();    }    }}

One of the interesting things in the implementation is the call to declare var Galleria. Because Galleria doesn't have a declaration file, you need to declare its object. This is where the declare keyword becomes very handy. You use the declare keyword to inform the TypeScript runtime that the declared variable is dynamic and should be used with the any type.

The last part of the puzzle is the app.ts file. You need to create this file in the app folder that exists in the Scripts folder. Don't be confused between the app.ts file of node.js and this app.ts file, which is used to run the application. The code in Listing 9 implements the client-side app.ts file.

/// <reference path="bootstrapper.ts" />module app {    // start the app on load    window.addEventListener("DOMContentLoaded", (evt) => {    var bootstrapper = new app.Bootstrapper();    bootstrapper.run();    }, false);}

Now that all the parts are in place, the final result is an image gallery (see Figure 3). You can put images in the Images folder, which exists under the Content folder. You can download the complete application that I used to create the DevConnections photo gallery.

Figure 3: Final DevConnections Photo Gallery

The TypeScript Advantage

In this article you learned how to use TypeScript to build a simple application. TypeScript can help you bridge the missing JavaScript features that will eventually be available in ECMAScript 6. TypeScript allows you to write large-scale JavaScript code and maintain it more easily. You can use TypeScript both in the application front end and in the application back end with frameworks and environments that run JavaScript.

For More on TypeScript, see Blair Greenwood's article "TypeScript Moves Toward Final Release with Visual Studio 2013 Update 2 CTP2."

About the Author

Gil Fink

http://www.gilfink.net

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like