From time to time people ask how to get started with a project using TypeScript as programming language for Node.JS. With this article we describe the setup of a minimal Node.JS project using TypeScript as programming language. It is also a starting point for other articles in scope of Node.JS related contributions.

We show how to start a Node.JS project with TypeScript and tsc from scratch with NPM. This example allows to be extended to a Node.JS server side project using an application-server such as express.js.

Concept

We start from scratch with an empty project folder and install necessary packages with NPM, use tsc (which comes with typescript) as tool to translate TypeScript files to JS and Node.js as environment to run the code. The application we create is tiny in first place. We know that it could be created in JavaScript easily with less effort, but this is a starting point for a server application.

Next we will use nodemon to watch for changes in the source files and restart the application automatically. This is a convenient feature during development.

After that we will extend the project to provide a simple web server using express.js. This is a common use case for Node.JS applications.

Getting Started

It is assumed that you have "node" and "npm" installed on your system. If not, visit nodejs.org for instructions and download of packages. Install one of the latest stable release from there.

Let's start. Change to your playground folder and create a new project directory, initialize NPM and install required packages:

$ mkdir node-ts-demo
$ cd node-ts-demo
$ npm init

Answer the questions. As main file define dist/hello.js instead of index.js. Outcome is a more or less package.json file in the current folder. Next we install required packages.

$ npm install --save-dev typescript
$ mkdir src

The directory src contains source files: programs, classes etc. written in TypeScript language. They will be translated manually or automatically to JS files using the TypeScript compiler installed before. Results will be stored in a folder named dist. This folder will be created automatically. In order to have something to be compiled, please create the file src/hello.ts. Example:

const text = "Hello World!";
console.log(text);

Transpile with TypeScript

It writes the text "Hello World!" to console. To transpile this source file change to the project root directory and:

$ ./node_modules/.bin/tsc src/hello.ts --outDir dist

Outcome is a file dist/hello.js containing the transpiled code. You can run it with node dist/hello.js and see the expected output on the console.

$ node dist/hello.js

will print "Hello World!" to the console.

Automate with npm build script

To avoid manual transpilation we can automate this with a "build" and a "run" script in package.json. Add the following lines to package.json:

"scripts": {
    ...
    "build": "tsc src/hello.ts --outDir dist",
    "exec": "node dist/hello.js",
    ...
  },

Now you can run the build script with npm run build and see the transpiled code in dist/hello.js being updated.

To run the application use npm run exec and see the output on the console.

$ npm run exec

will print "Hello World!" to the console.

Fine. But this can be achieved with a single command as well:

$ npm run build && npm run exec

Combined build script to transpile and run

And we can combine this in a single script of package.json:

"scripts": {
    ...
    "build-exec": "npm run build && npm run exec",
    ...
  },

Run this with npm run build-exec and see the output on the console.

$ npm run build-exec

> ts-demo@1.0.0 build-exec
> npm run build && npm run exec


> ts-demo@1.0.0 build
> tsc src/hello.ts --outDir dist


> ts-demo@1.0.0 exec
> node dist/hello.js

Hello World!

OK, this makes things simpler, but what we really want is to have the application running and to restart it automatically after changes to the source file. This is the next step.

Watch mode with nodemon

To avoid manual restart of the application after changes to the source file we can use nodemon. This is one of many options. Install it with:

$ npm install --save-dev nodemon

And add a new new scripts to package.json:

"scripts": {
    ...
    "dev:watch": "nodemon -e ts --exec 'npm run build-exec'",
    ...
  },

This means that nodemon watches for changes to files with extension ts and executes the script npm run build-exec whenever a change is detected.

Now you can start the application with dev:watch and see the output on the console. If you change the source file src/hello.ts and save it, nodemon will restart the application automatically.

$ npm run dev:watch

So - for example - if you change the text in src/hello.ts from Hello World! to Hello TypeScript! and save the file, you will see the new text on the console.

[nodemon] clean exit - waiting for changes before restart
[nodemon] restarting due to changes...
[nodemon] starting `npm run build-exec`

> ts-demo@1.0.0 build-exec
> npm run build && npm run exec


> ts-demo@1.0.0 build
> tsc src/hello.ts --outDir dist


> ts-demo@1.0.0 exec
> node dist/hello.js

Hello TypeScript!

Convert it to a web server

In fact there are many situations where you want to run an application as client, e.g. to prepare data to be inserted into a database. But in most cases you want to provide a service to clients. This is often done with a web server. We will use "express.js" for this purpose.

So we change our focus from hello.ts to a new file server.ts in the src directory. This file will contain the code for a simple web server. We will use express.js for this purpose. First install express.js with:

$ npm install --save express

Next create the file src/server.ts. It contains the web service definition. The web service shall include a GET method providing the string "Hello World!". With the following content:

import express, { Request, Response } from 'express';

const app = express();
const port = 3000;

app.get('/hello', (req: Request, res: Response) => {
  res.send('Hello World!');
});

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

If you are suing a modern IDE like Visual Studio Code, you will see that the import statement is marked as error. This is because the TypeScript compiler does not know the express.js types. To fix this, install the types for express.js with:

$ npm install --save-dev @types/express

Outcomes are:

  • the package @types/express has been added to the devDependencies section of package.json
  • the error in the import statement is gone
  • now you can use the types provided by the package @types/express and the IDE will support you with code completion and type checking.

This is a recurring pattern in TypeScript development. If you use a package that does not provide types, you can install the types with @types/package-name from the NPM registry - at least in many cases.

To switch to the new server application, change the scripts build and exec in package.json to:

  "build": "tsc --esModuleInterop src/server.ts --outDir dist",
  "exec": "node dist/server.js",

After that, you may run the server with:

$ npm run dev:watch

Voilà! You have a running web server. Open a browser and navigate to http://localhost:3000/hello. You will see the text "Hello World!" in the browser window.

With a terminal you may use "curl" to get the same result:

$ curl http://localhost:3000/hello
Hello World!

When switching on response headers with -i you will see the HTTP response headers as well:

$ curl -i http://localhost:3000/hello
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 12
ETag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"
Connection: keep-alive
Keep-Alive: timeout=5

Hello World!

So "express" returns a status code 200 (OK) and the text "Hello World!" in the body of the response. Content type is text/html.

In many cases you will want to provide a web server with a REST API. This is a common use case for Node.JS applications. Sos the web server shall respond with JSON data. This is the next step.

Provide JSON data

To provide JSON data, we change the server application to respond with JSON data. We will use the same route /hello as before, but the response will be JSON data. The new content of src/server.ts is:

import express, { Request, Response } from 'express';

const app = express();
const port = 3000;

app.get('/hello', (req: Request, res: Response) => {
  res.json({ message: 'Hello World!' });
});

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

After changing the source file, the server is restarted automatically by nodemon. You can test the new response with curl:

$ curl -i http://localhost:3000/hello
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 26
ETag: W/"1a-iEQ9RXvkycqsT4vWvcdHrxZT8OE"
Connection: keep-alive
Keep-Alive: timeout=5

{"message":"Hello World!"}

Congratulations! You have a running web server providing JSON data via a REST API.

Outlook

Finally this is only a starting point for a Node.JS application. You can extend it with additional routes, middleware, error handling, database access, etc. This is a common use case for Node.JS applications. Express.js is a good starting point for this purpose. When you are familiar with the basics, you can extend the application with additional features. If you think, that express.js is too slow, you can switch to other frameworks like "fastify" or "koa". But this is another story.

When you intend to realize larger applications, you may have a severe look on "Nest.JS". This is a framework for Node.JS applications based on TypeScript. It provides a lot of features out of the box and is a good starting point for larger applications. It shares many concepts with Angular, so if you are familiar with Angular, you will feel at home with Nest.JS.

Summary

In this tutorial we depicted to create a Node.js application in TypeScript from scratch with standard methods. This example serves as starter for additional applications, e.g. express.js based server applications. For larger applications you may consider to use a framework like "Nest.JS".