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 thedevDependencies
section ofpackage.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".