• United States+1
  • United Kingdom+44
  • Afghanistan (‫افغانستان‬‎)+93
  • Albania (Shqipëri)+355
  • Algeria (‫الجزائر‬‎)+213
  • American Samoa+1684
  • Andorra+376
  • Angola+244
  • Anguilla+1264
  • Antigua and Barbuda+1268
  • Argentina+54
  • Armenia (Հայաստան)+374
  • Aruba+297
  • Australia+61
  • Austria (Österreich)+43
  • Azerbaijan (Azərbaycan)+994
  • Bahamas+1242
  • Bahrain (‫البحرين‬‎)+973
  • Bangladesh (বাংলাদেশ)+880
  • Barbados+1246
  • Belarus (Беларусь)+375
  • Belgium (België)+32
  • Belize+501
  • Benin (Bénin)+229
  • Bermuda+1441
  • Bhutan (འབྲུག)+975
  • Bolivia+591
  • Bosnia and Herzegovina (Босна и Херцеговина)+387
  • Botswana+267
  • Brazil (Brasil)+55
  • British Indian Ocean Territory+246
  • British Virgin Islands+1284
  • Brunei+673
  • Bulgaria (България)+359
  • Burkina Faso+226
  • Burundi (Uburundi)+257
  • Cambodia (កម្ពុជា)+855
  • Cameroon (Cameroun)+237
  • Canada+1
  • Cape Verde (Kabu Verdi)+238
  • Caribbean Netherlands+599
  • Cayman Islands+1345
  • Central African Republic (République centrafricaine)+236
  • Chad (Tchad)+235
  • Chile+56
  • China (中国)+86
  • Christmas Island+61
  • Cocos (Keeling) Islands+61
  • Colombia+57
  • Comoros (‫جزر القمر‬‎)+269
  • Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)+243
  • Congo (Republic) (Congo-Brazzaville)+242
  • Cook Islands+682
  • Costa Rica+506
  • Côte d’Ivoire+225
  • Croatia (Hrvatska)+385
  • Cuba+53
  • Curaçao+599
  • Cyprus (Κύπρος)+357
  • Czech Republic (Česká republika)+420
  • Denmark (Danmark)+45
  • Djibouti+253
  • Dominica+1767
  • Dominican Republic (República Dominicana)+1
  • Ecuador+593
  • Egypt (‫مصر‬‎)+20
  • El Salvador+503
  • Equatorial Guinea (Guinea Ecuatorial)+240
  • Eritrea+291
  • Estonia (Eesti)+372
  • Ethiopia+251
  • Falkland Islands (Islas Malvinas)+500
  • Faroe Islands (Føroyar)+298
  • Fiji+679
  • Finland (Suomi)+358
  • France+33
  • French Guiana (Guyane française)+594
  • French Polynesia (Polynésie française)+689
  • Gabon+241
  • Gambia+220
  • Georgia (საქართველო)+995
  • Germany (Deutschland)+49
  • Ghana (Gaana)+233
  • Gibraltar+350
  • Greece (Ελλάδα)+30
  • Greenland (Kalaallit Nunaat)+299
  • Grenada+1473
  • Guadeloupe+590
  • Guam+1671
  • Guatemala+502
  • Guernsey+44
  • Guinea (Guinée)+224
  • Guinea-Bissau (Guiné Bissau)+245
  • Guyana+592
  • Haiti+509
  • Honduras+504
  • Hong Kong (香港)+852
  • Hungary (Magyarország)+36
  • Iceland (Ísland)+354
  • India (भारत)+91
  • Indonesia+62
  • Iran (‫ایران‬‎)+98
  • Iraq (‫العراق‬‎)+964
  • Ireland+353
  • Isle of Man+44
  • Israel (‫ישראל‬‎)+972
  • Italy (Italia)+39
  • Jamaica+1876
  • Japan (日本)+81
  • Jersey+44
  • Jordan (‫الأردن‬‎)+962
  • Kazakhstan (Казахстан)+7
  • Kenya+254
  • Kiribati+686
  • Kosovo+383
  • Kuwait (‫الكويت‬‎)+965
  • Kyrgyzstan (Кыргызстан)+996
  • Laos (ລາວ)+856
  • Latvia (Latvija)+371
  • Lebanon (‫لبنان‬‎)+961
  • Lesotho+266
  • Liberia+231
  • Libya (‫ليبيا‬‎)+218
  • Liechtenstein+423
  • Lithuania (Lietuva)+370
  • Luxembourg+352
  • Macau (澳門)+853
  • Macedonia (FYROM) (Македонија)+389
  • Madagascar (Madagasikara)+261
  • Malawi+265
  • Malaysia+60
  • Maldives+960
  • Mali+223
  • Malta+356
  • Marshall Islands+692
  • Martinique+596
  • Mauritania (‫موريتانيا‬‎)+222
  • Mauritius (Moris)+230
  • Mayotte+262
  • Mexico (México)+52
  • Micronesia+691
  • Moldova (Republica Moldova)+373
  • Monaco+377
  • Mongolia (Монгол)+976
  • Montenegro (Crna Gora)+382
  • Montserrat+1664
  • Morocco (‫المغرب‬‎)+212
  • Mozambique (Moçambique)+258
  • Myanmar (Burma) (မြန်မာ)+95
  • Namibia (Namibië)+264
  • Nauru+674
  • Nepal (नेपाल)+977
  • Netherlands (Nederland)+31
  • New Caledonia (Nouvelle-Calédonie)+687
  • New Zealand+64
  • Nicaragua+505
  • Niger (Nijar)+227
  • Nigeria+234
  • Niue+683
  • Norfolk Island+672
  • North Korea (조선 민주주의 인민 공화국)+850
  • Northern Mariana Islands+1670
  • Norway (Norge)+47
  • Oman (‫عُمان‬‎)+968
  • Pakistan (‫پاکستان‬‎)+92
  • Palau+680
  • Palestine (‫فلسطين‬‎)+970
  • Panama (Panamá)+507
  • Papua New Guinea+675
  • Paraguay+595
  • Peru (Perú)+51
  • Philippines+63
  • Poland (Polska)+48
  • Portugal+351
  • Puerto Rico+1
  • Qatar (‫قطر‬‎)+974
  • Réunion (La Réunion)+262
  • Romania (România)+40
  • Russia (Россия)+7
  • Rwanda+250
  • Saint Barthélemy (Saint-Barthélemy)+590
  • Saint Helena+290
  • Saint Kitts and Nevis+1869
  • Saint Lucia+1758
  • Saint Martin (Saint-Martin (partie française))+590
  • Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)+508
  • Saint Vincent and the Grenadines+1784
  • Samoa+685
  • San Marino+378
  • São Tomé and Príncipe (São Tomé e Príncipe)+239
  • Saudi Arabia (‫المملكة العربية السعودية‬‎)+966
  • Senegal (Sénégal)+221
  • Serbia (Србија)+381
  • Seychelles+248
  • Sierra Leone+232
  • Singapore+65
  • Sint Maarten+1721
  • Slovakia (Slovensko)+421
  • Slovenia (Slovenija)+386
  • Solomon Islands+677
  • Somalia (Soomaaliya)+252
  • South Africa+27
  • South Korea (대한민국)+82
  • South Sudan (‫جنوب السودان‬‎)+211
  • Spain (España)+34
  • Sri Lanka (ශ්‍රී ලංකාව)+94
  • Sudan (‫السودان‬‎)+249
  • Suriname+597
  • Svalbard and Jan Mayen+47
  • Swaziland+268
  • Sweden (Sverige)+46
  • Switzerland (Schweiz)+41
  • Syria (‫سوريا‬‎)+963
  • Taiwan (台灣)+886
  • Tajikistan+992
  • Tanzania+255
  • Thailand (ไทย)+66
  • Timor-Leste+670
  • Togo+228
  • Tokelau+690
  • Tonga+676
  • Trinidad and Tobago+1868
  • Tunisia (‫تونس‬‎)+216
  • Turkey (Türkiye)+90
  • Turkmenistan+993
  • Turks and Caicos Islands+1649
  • Tuvalu+688
  • U.S. Virgin Islands+1340
  • Uganda+256
  • Ukraine (Україна)+380
  • United Arab Emirates (‫الإمارات العربية المتحدة‬‎)+971
  • United Kingdom+44
  • United States+1
  • Uruguay+598
  • Uzbekistan (Oʻzbekiston)+998
  • Vanuatu+678
  • Vatican City (Città del Vaticano)+39
  • Venezuela+58
  • Vietnam (Việt Nam)+84
  • Wallis and Futuna+681
  • Western Sahara (‫الصحراء الغربية‬‎)+212
  • Yemen (‫اليمن‬‎)+967
  • Zambia+260
  • Zimbabwe+263
  • Åland Islands+358
Thanks! We'll be in touch in the next 12 hours
Oops! Something went wrong while submitting the form.

Set Up A Production-ready REST API Server Using TypeScript, Express And PostgreSQL

Birendra Bikram Singh

Full-stack Development

Introduction

So, you have a brilliant idea for a web application. It’s going to be the next big thing, and you are super-excited about it. Maybe you have already started building the perfect React/Angular UI for your app.

Eventually, you realize that, like most web apps, your app is going to be data-intensive and will need a lightning-fast web server. You know that Node.js is the de facto standard for web servers for how well it unifies front-end and back-end web development with JavaScript, so you go for it.

But you want your server to be robust and reliable too. A colleague introduces you to TypeScript, the superset of JavaScript developed by Microsoft, and recommends it for its strict static typing and compilation.

Now comes storing the data. Naturally, you select PostgreSQL. After all, it is the most advanced Relational Database Management System (RDBMS) in the world, with its object-oriented features and extensibility. But RDBMSs can be slow for frequently used data and caching, so you decide to add Redis, the in-memory cache, to decrease data access latency and ease the load off your relational data store.

That’s it. You have a perfect server waiting to be built. And while the initial process of getting it up and running can get arduous, you have come to the right place. This blog is going to guide you through the initial setup process.

Prerequisites

I am assuming you have a non-root user with sudo privileges running on Ubuntu 16.04. Before we start, please make sure you have the following: 

  1. NPM (~v6.9.0) and Node.js (~v10.16.0) – You can use this How to Install Node.js on Ubuntu 16.04
  2. Redis – How to install Redis on Ubuntu 16.04
  3. PostgreSQL – How to install PostgreSQL on Ubuntu 16.04

Of course, MacOS or Windows would do fine too for this tutorial, but to use them, please find appropriate installation guides on the Internet before moving forward. 

If you don’t want to go through the steps below, you can check out my GitHub Repo typescript-express-server and use it as your application skeleton. It has been set up with default configurations, which you can change later. Nevertheless, I strongly recommend going through this guide to further your understanding of the project files and configuration nuances.

Initializing Server (Express with TypeScript)

Setting up an Express Application with TypeScript can be done in three steps: 

Initialize project using NPM

Create a folder and run:

npm init

This will ask you a couple of project-specific questions, like name and version, and will create a package.json file, which may look like this:

{
"name": "my-typescript-express-server",
"version": "0.0.0",
"scripts": {
"start": "node ./dist/index.js --env=production",
"start:dev": "ts-node -r tsconfig-paths/register ./src",
},
"dependencies": {
"cookie-parser": "^1.4.5",
"dotenv": "^8.2.0"
},
"devDependencies": {
"find": "^0.3.0",
"fs-extra": "^9.0.1",
}
}
view raw package. json hosted with ❤ by GitHub

This manifest file will contain all the metadata of your project, like module dependencies, configs, and scripts. For more information, check out this very good read about the basics of package.json

Setting up TypeScript Configuration (tsconfig.json)

This file needs to be created in the root of a TypeScript project. During development, TypeScript provides us with the convenience of running the code directly from the .ts extension files. But during production, since Node.js only understands JS, the entire TS files need to be transpiled to JS. Some of the options are: include - specifies the files to be included, exclude -  the files to exclude, and the compiler options: outFIle and moduleResolution.

First, we need to install some TypeScript specific modules: 

npm i typescript ts-node tsconfig-paths

This is the tsconfig.json file with some default configurations:

CODE: https://gist.github.com/velotiotech/2cd42f3db972ebcf47d1819a80870826.j

For a detailed reference, checkout tsconfig.json.

Setting up ESLint

It is not mandatory to use this JavaScript linter, but it’s highly recommended for enforcing code standards and keeping code clean. TypeScript projects once used TSLint, but it has been deprecated in favor of ESLint.

Run this command:

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Create a .eslintrc file in the project root and use the following starter configuration:

{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}
view raw .eslintrc hosted with ❤ by GitHub

Lastly, add a lint script to package.json:

{
"name": "my-typescript-express-server",
"version": "0.0.0",
"scripts": {
"start": "node ./dist/index.js --env=production",
"start:dev": "ts-node -r tsconfig-paths/register ./src",
"lint": "eslint . --ext .ts",
},
view raw package.json hosted with ❤ by GitHub

Now, you can run the command below to lint your codebase for lint errors:

npm run lint
view raw run_lint.sh hosted with ❤ by GitHub

ESLint has ample rules to enforce standards in your code. Please look them up at Eslint with TypeScript.

Express App

Finally, we need to install Express, which is as simple as running this command:

npm install --save express @types/express

You need a server file (src/Server.ts), which you can create like this:

import cookieParser from 'cookie-parser';
import express from 'express';
import { BAD_REQUEST } from 'http-status-codes';
import BaseRouter from './routes';
const app = express();
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use(cookieParser());
// Add APIs
app.use('/api', BaseRouter);
// Export express instance
export default app;
view raw Server.ts hosted with ❤ by GitHub

You will also need src/index.ts that will be the entry point for your application:

import app from './Server';
// Start the server
const port = Number(process.env.PORT || 3000);
app.listen(port, () => {
logger.info('Express server started on port: ' + port);
});
view raw index.ts hosted with ❤ by GitHub

Error Handling

Many Express servers are configured to swallow all errors by configuring an Uncaught Exception handler, which in my opinion, is bad news. The best thing to do is to allow the application to crash and restart. Uncaught Exceptions in Node.js is a good read regarding this.

Nonetheless, we are going to configure an error handler that will print errors and send a BadRequest response when an invalid HTTP request comes your API’s way.

In the src/Server.ts, add this:

/// Print API errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
logger.error(err.message, err);
return res.status(BAD_REQUEST).json({
error: err.message,
});
});
view raw Server.ts hosted with ❤ by GitHub

Kudos! You have a basic Express server set up. Fire it up by running:

npm run start:dev

Connecting with the Database Store using TypeORM

We have a basic server ready to go, but we need to connect it to our Postgres database using an ORM. TypeORM is a versatile ORM that supports both Active Record and Data Mapper patterns, unlike all other JavaScript ORMs. It can be installed on our server with the following steps:

npm i --save typeorm pg reflect-metadata

Create an ormconfig.json file in your project root with the following configuration:

{
"synchronize": true,
"logging": false,
"entities": [
"src/entities/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
},
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
]
}
view raw ormconfig.json hosted with ❤ by GitHub

Create a src/db.ts file that will initialize the database connection:

import "reflect-metadata";
import {createConnection} from "typeorm";
import { Tedis } from "tedis";
import logger from '../src/shared/Logger';
export async function intializeDB(): Promise<void> {
await createConnection();
}
view raw db.ts hosted with ❤ by GitHub

TypeORM Entities are classes that represent the data models in our application. We are going to build a User Entity (which application doesn’t have a user, duh!) like this in src/entities/User.ts:

import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
view raw User.ts hosted with ❤ by GitHub

Then, add these lines to src/index.ts:

import { intializeDB } from './db';
intializeDB();
view raw index.ts hosted with ❤ by GitHub

You will need the env variables, like TYPEORM_CONNECTION, TYPEORM_HOST, and TYPEORM_USERNAME, with your postgres db’s connection params. Please check TypeORMs documentation for more details. 

Connecting Redis

We will use Tedis, the TypeScript wrapper for Redis in our server:

npm i tedis

Add these lines to src/db.ts:

export function initializeCache(port: number | undefined) : unknown {
const tedis = new Tedis({
port: port,
host: "127.0.0.1"
});
return tedis;
}
view raw db.ts hosted with ❤ by GitHub

And these lines to src/index.ts:

const redisPORT = Number(process.env.REDIS_PORT || 6379)
initializeCache(redisPORT);
view raw index.ts hosted with ❤ by GitHub

Now, your application code can use the Redis cache using the client created above.

Configuring Logging

Logging is pivotal to an application because it gives us a real-time view of the state of our application. For development, we are going to install the Morgan Request Logger, a library that logs HTTP requests params. It comes really handy for debugging. 

npm i morgan

And include this in src/Server.ts:

app.use(morgan('dev'));
view raw Server.ts hosted with ❤ by GitHub

Winston can be used as the system-wide universal logger. Install it like this:

npm i winston

Then, add a src/shared/Logger.js file:

import { createLogger, format, transports } from 'winston';
// Import Functions
const { File, Console } = transports;
// Init Logger
const logger = createLogger({
level: 'info',
});
const errorStackFormat = format((info) => {
if (info.stack) {
// tslint:disable-next-line:no-console
console.log(info.stack);
return false;
}
return info;
});
const consoleTransport = new Console({
format: format.combine(
format.colorize(),
format.simple(),
errorStackFormat(),
),
});
logger.add(consoleTransport);
}
export default logger;
view raw Logger.js hosted with ❤ by GitHub

Now, you can use this logger from anywhere in the code, be it for error logging in your API methods or for debugging purposes:

import logger from '@shared/Logger';
export async function intializeDB(): Promise<void> {
await createConnection()
logger.info('Database successfully initialized');
}
view raw index.ts hosted with ❤ by GitHub

Creating your First API Service

This is the moment you have been waiting for: creating your first API service for your application, the crux of the functionality that will define your web application.

This API service is a simple GET request handler, which returns all the users in your database. You should have src/Users.ts, which can look like:

import { Request, Response, Router } from 'express';
import { BAD_REQUEST, CREATED, OK } from 'http-status-codes';
import { ParamsDictionary } from 'express-serve-static-core';
import { getConnection } from "typeorm";
import { User } from "../entities/User";
import { paramMissingError } from '../shared/constants';
const router = Router();
router.get('/all', async (req: Request, res: Response) => {
const users = await getConnection()
.getRepository(User)
.createQueryBuilder("user")
.getMany();
return res.status(OK).json({users});
});
view raw users.ts hosted with ❤ by GitHub

Add src/routes/index.ts

import { Router } from 'express';
import UserRouter from './Users';
// Init router and path
const router = Router();
// Add sub-routes
router.use('/users', UserRouter);
// Export the base-router
export default router;
view raw index.ts hosted with ❤ by GitHub

Voila! Your API service is ready. Fire up your server, and then use Postman to make requests to your API and see the magic happen. 

You can also add other API services for fetching a user by ID, deleting a user, creating a user, and updating a user. I will not discuss them here to keep this blog short. You can find these in the Github repository I mentioned in the beginning.

Deploying your Server to Production

What we have been doing has been in the development phase. Now, we need to take this to production. You just need to have a <project-root>/build.js </project-root>script that will create a <project-root>/dist</project-root> folder and transpile all the TypeScript files that you have written. It can look like this: 

const fsE = require('fs-extra');
const childProcess = require('child_process');
// Remove current build
fsE.removeSync('./dist/');
// Copy front-end files
fsE.copySync('./src/public', './dist/public');
fsE.copySync('./src/views', './dist/views');
// Transpile the typescript files
childProcess.execSync('tsc --build tsconfig.prod.json');
view raw build.js hosted with ❤ by GitHub

Then, add this line to your <project-root>/package.json</project-root>:

"scripts": {
"build": "node build.js",
"lint": "eslint . --ext .ts",
view raw package.json hosted with ❤ by GitHub

Now, you can use:

node build.js

Doing so builds up the <project-root>/dist</project-root> folder and transpiles your code. You can deploy this folder to your deployment environment and run it to start your production server:

npm start

Note: You will need to do some additional setting up of your Nginx or AWS Virtual Machine to complete your deployment, which is beyond the scope of this blog.

Going Forward

Congratulations. You have made it through this tutorial that guided you through the process of setting up a web server. But this is just the beginning, and there is no end to the improvements and optimizations that you can add to your server to make it better and sturdier. And you will continue to discover them in your journey of developing your web application. Some of the key points that I want to mention are:

Managing Environments

Your Web server will be operated in multiple environments, such as development, testing, and production. Some of the vital configurations like AWS credentials and DB passwords are sensitive information, and managing them per environment is key to your development and deployment cycle. I strongly recommend using libraries like Dotenv and keeping your env configurations separate in your codebase. You can look up typescript-express-server for this.

Configuring Swagger

Software developers nowadays swear by this tool. It’s proved to be a godsend for API documentation and keeping APIs in confirmation with the OpenAPI standard. On top of that, it also does API requests validation according to your API specifications. I strongly recommend you configure this in your web server.

Writing Tests

Writing API tests and unit tests can be a crucial part of web application development as it exposes possible gaps in your systems. You can use Superagent, the lightweight REST API, to test your APIs for all possible requests and response scenarios. Please look up the src/spec in typescript-express-server about how to use it. You can also use Postman for API Testing Automation. For most of the services that you write, you should make sure to add unit tests for each of those using Jest.

Further Reading

  1. Node.js production checklist
  2. Node.js production best practices
  3. Production best practices: performance and reliability
Get the latest engineering blogs delivered straight to your inbox.
No spam. Only expert insights.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Did you like the blog? If yes, we're sure you'll also like to work with the people who write them - our best-in-class engineering team.

We're looking for talented developers who are passionate about new emerging technologies. If that's you, get in touch with us.

Explore current openings

Set Up A Production-ready REST API Server Using TypeScript, Express And PostgreSQL

Introduction

So, you have a brilliant idea for a web application. It’s going to be the next big thing, and you are super-excited about it. Maybe you have already started building the perfect React/Angular UI for your app.

Eventually, you realize that, like most web apps, your app is going to be data-intensive and will need a lightning-fast web server. You know that Node.js is the de facto standard for web servers for how well it unifies front-end and back-end web development with JavaScript, so you go for it.

But you want your server to be robust and reliable too. A colleague introduces you to TypeScript, the superset of JavaScript developed by Microsoft, and recommends it for its strict static typing and compilation.

Now comes storing the data. Naturally, you select PostgreSQL. After all, it is the most advanced Relational Database Management System (RDBMS) in the world, with its object-oriented features and extensibility. But RDBMSs can be slow for frequently used data and caching, so you decide to add Redis, the in-memory cache, to decrease data access latency and ease the load off your relational data store.

That’s it. You have a perfect server waiting to be built. And while the initial process of getting it up and running can get arduous, you have come to the right place. This blog is going to guide you through the initial setup process.

Prerequisites

I am assuming you have a non-root user with sudo privileges running on Ubuntu 16.04. Before we start, please make sure you have the following: 

  1. NPM (~v6.9.0) and Node.js (~v10.16.0) – You can use this How to Install Node.js on Ubuntu 16.04
  2. Redis – How to install Redis on Ubuntu 16.04
  3. PostgreSQL – How to install PostgreSQL on Ubuntu 16.04

Of course, MacOS or Windows would do fine too for this tutorial, but to use them, please find appropriate installation guides on the Internet before moving forward. 

If you don’t want to go through the steps below, you can check out my GitHub Repo typescript-express-server and use it as your application skeleton. It has been set up with default configurations, which you can change later. Nevertheless, I strongly recommend going through this guide to further your understanding of the project files and configuration nuances.

Initializing Server (Express with TypeScript)

Setting up an Express Application with TypeScript can be done in three steps: 

Initialize project using NPM

Create a folder and run:

npm init

This will ask you a couple of project-specific questions, like name and version, and will create a package.json file, which may look like this:

{
"name": "my-typescript-express-server",
"version": "0.0.0",
"scripts": {
"start": "node ./dist/index.js --env=production",
"start:dev": "ts-node -r tsconfig-paths/register ./src",
},
"dependencies": {
"cookie-parser": "^1.4.5",
"dotenv": "^8.2.0"
},
"devDependencies": {
"find": "^0.3.0",
"fs-extra": "^9.0.1",
}
}
view raw package. json hosted with ❤ by GitHub

This manifest file will contain all the metadata of your project, like module dependencies, configs, and scripts. For more information, check out this very good read about the basics of package.json

Setting up TypeScript Configuration (tsconfig.json)

This file needs to be created in the root of a TypeScript project. During development, TypeScript provides us with the convenience of running the code directly from the .ts extension files. But during production, since Node.js only understands JS, the entire TS files need to be transpiled to JS. Some of the options are: include - specifies the files to be included, exclude -  the files to exclude, and the compiler options: outFIle and moduleResolution.

First, we need to install some TypeScript specific modules: 

npm i typescript ts-node tsconfig-paths

This is the tsconfig.json file with some default configurations:

CODE: https://gist.github.com/velotiotech/2cd42f3db972ebcf47d1819a80870826.j

For a detailed reference, checkout tsconfig.json.

Setting up ESLint

It is not mandatory to use this JavaScript linter, but it’s highly recommended for enforcing code standards and keeping code clean. TypeScript projects once used TSLint, but it has been deprecated in favor of ESLint.

Run this command:

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Create a .eslintrc file in the project root and use the following starter configuration:

{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}
view raw .eslintrc hosted with ❤ by GitHub

Lastly, add a lint script to package.json:

{
"name": "my-typescript-express-server",
"version": "0.0.0",
"scripts": {
"start": "node ./dist/index.js --env=production",
"start:dev": "ts-node -r tsconfig-paths/register ./src",
"lint": "eslint . --ext .ts",
},
view raw package.json hosted with ❤ by GitHub

Now, you can run the command below to lint your codebase for lint errors:

npm run lint
view raw run_lint.sh hosted with ❤ by GitHub

ESLint has ample rules to enforce standards in your code. Please look them up at Eslint with TypeScript.

Express App

Finally, we need to install Express, which is as simple as running this command:

npm install --save express @types/express

You need a server file (src/Server.ts), which you can create like this:

import cookieParser from 'cookie-parser';
import express from 'express';
import { BAD_REQUEST } from 'http-status-codes';
import BaseRouter from './routes';
const app = express();
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use(cookieParser());
// Add APIs
app.use('/api', BaseRouter);
// Export express instance
export default app;
view raw Server.ts hosted with ❤ by GitHub

You will also need src/index.ts that will be the entry point for your application:

import app from './Server';
// Start the server
const port = Number(process.env.PORT || 3000);
app.listen(port, () => {
logger.info('Express server started on port: ' + port);
});
view raw index.ts hosted with ❤ by GitHub

Error Handling

Many Express servers are configured to swallow all errors by configuring an Uncaught Exception handler, which in my opinion, is bad news. The best thing to do is to allow the application to crash and restart. Uncaught Exceptions in Node.js is a good read regarding this.

Nonetheless, we are going to configure an error handler that will print errors and send a BadRequest response when an invalid HTTP request comes your API’s way.

In the src/Server.ts, add this:

/// Print API errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
logger.error(err.message, err);
return res.status(BAD_REQUEST).json({
error: err.message,
});
});
view raw Server.ts hosted with ❤ by GitHub

Kudos! You have a basic Express server set up. Fire it up by running:

npm run start:dev

Connecting with the Database Store using TypeORM

We have a basic server ready to go, but we need to connect it to our Postgres database using an ORM. TypeORM is a versatile ORM that supports both Active Record and Data Mapper patterns, unlike all other JavaScript ORMs. It can be installed on our server with the following steps:

npm i --save typeorm pg reflect-metadata

Create an ormconfig.json file in your project root with the following configuration:

{
"synchronize": true,
"logging": false,
"entities": [
"src/entities/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
},
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
]
}
view raw ormconfig.json hosted with ❤ by GitHub

Create a src/db.ts file that will initialize the database connection:

import "reflect-metadata";
import {createConnection} from "typeorm";
import { Tedis } from "tedis";
import logger from '../src/shared/Logger';
export async function intializeDB(): Promise<void> {
await createConnection();
}
view raw db.ts hosted with ❤ by GitHub

TypeORM Entities are classes that represent the data models in our application. We are going to build a User Entity (which application doesn’t have a user, duh!) like this in src/entities/User.ts:

import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}
view raw User.ts hosted with ❤ by GitHub

Then, add these lines to src/index.ts:

import { intializeDB } from './db';
intializeDB();
view raw index.ts hosted with ❤ by GitHub

You will need the env variables, like TYPEORM_CONNECTION, TYPEORM_HOST, and TYPEORM_USERNAME, with your postgres db’s connection params. Please check TypeORMs documentation for more details. 

Connecting Redis

We will use Tedis, the TypeScript wrapper for Redis in our server:

npm i tedis

Add these lines to src/db.ts:

export function initializeCache(port: number | undefined) : unknown {
const tedis = new Tedis({
port: port,
host: "127.0.0.1"
});
return tedis;
}
view raw db.ts hosted with ❤ by GitHub

And these lines to src/index.ts:

const redisPORT = Number(process.env.REDIS_PORT || 6379)
initializeCache(redisPORT);
view raw index.ts hosted with ❤ by GitHub

Now, your application code can use the Redis cache using the client created above.

Configuring Logging

Logging is pivotal to an application because it gives us a real-time view of the state of our application. For development, we are going to install the Morgan Request Logger, a library that logs HTTP requests params. It comes really handy for debugging. 

npm i morgan

And include this in src/Server.ts:

app.use(morgan('dev'));
view raw Server.ts hosted with ❤ by GitHub

Winston can be used as the system-wide universal logger. Install it like this:

npm i winston

Then, add a src/shared/Logger.js file:

import { createLogger, format, transports } from 'winston';
// Import Functions
const { File, Console } = transports;
// Init Logger
const logger = createLogger({
level: 'info',
});
const errorStackFormat = format((info) => {
if (info.stack) {
// tslint:disable-next-line:no-console
console.log(info.stack);
return false;
}
return info;
});
const consoleTransport = new Console({
format: format.combine(
format.colorize(),
format.simple(),
errorStackFormat(),
),
});
logger.add(consoleTransport);
}
export default logger;
view raw Logger.js hosted with ❤ by GitHub

Now, you can use this logger from anywhere in the code, be it for error logging in your API methods or for debugging purposes:

import logger from '@shared/Logger';
export async function intializeDB(): Promise<void> {
await createConnection()
logger.info('Database successfully initialized');
}
view raw index.ts hosted with ❤ by GitHub

Creating your First API Service

This is the moment you have been waiting for: creating your first API service for your application, the crux of the functionality that will define your web application.

This API service is a simple GET request handler, which returns all the users in your database. You should have src/Users.ts, which can look like:

import { Request, Response, Router } from 'express';
import { BAD_REQUEST, CREATED, OK } from 'http-status-codes';
import { ParamsDictionary } from 'express-serve-static-core';
import { getConnection } from "typeorm";
import { User } from "../entities/User";
import { paramMissingError } from '../shared/constants';
const router = Router();
router.get('/all', async (req: Request, res: Response) => {
const users = await getConnection()
.getRepository(User)
.createQueryBuilder("user")
.getMany();
return res.status(OK).json({users});
});
view raw users.ts hosted with ❤ by GitHub

Add src/routes/index.ts

import { Router } from 'express';
import UserRouter from './Users';
// Init router and path
const router = Router();
// Add sub-routes
router.use('/users', UserRouter);
// Export the base-router
export default router;
view raw index.ts hosted with ❤ by GitHub

Voila! Your API service is ready. Fire up your server, and then use Postman to make requests to your API and see the magic happen. 

You can also add other API services for fetching a user by ID, deleting a user, creating a user, and updating a user. I will not discuss them here to keep this blog short. You can find these in the Github repository I mentioned in the beginning.

Deploying your Server to Production

What we have been doing has been in the development phase. Now, we need to take this to production. You just need to have a <project-root>/build.js </project-root>script that will create a <project-root>/dist</project-root> folder and transpile all the TypeScript files that you have written. It can look like this: 

const fsE = require('fs-extra');
const childProcess = require('child_process');
// Remove current build
fsE.removeSync('./dist/');
// Copy front-end files
fsE.copySync('./src/public', './dist/public');
fsE.copySync('./src/views', './dist/views');
// Transpile the typescript files
childProcess.execSync('tsc --build tsconfig.prod.json');
view raw build.js hosted with ❤ by GitHub

Then, add this line to your <project-root>/package.json</project-root>:

"scripts": {
"build": "node build.js",
"lint": "eslint . --ext .ts",
view raw package.json hosted with ❤ by GitHub

Now, you can use:

node build.js

Doing so builds up the <project-root>/dist</project-root> folder and transpiles your code. You can deploy this folder to your deployment environment and run it to start your production server:

npm start

Note: You will need to do some additional setting up of your Nginx or AWS Virtual Machine to complete your deployment, which is beyond the scope of this blog.

Going Forward

Congratulations. You have made it through this tutorial that guided you through the process of setting up a web server. But this is just the beginning, and there is no end to the improvements and optimizations that you can add to your server to make it better and sturdier. And you will continue to discover them in your journey of developing your web application. Some of the key points that I want to mention are:

Managing Environments

Your Web server will be operated in multiple environments, such as development, testing, and production. Some of the vital configurations like AWS credentials and DB passwords are sensitive information, and managing them per environment is key to your development and deployment cycle. I strongly recommend using libraries like Dotenv and keeping your env configurations separate in your codebase. You can look up typescript-express-server for this.

Configuring Swagger

Software developers nowadays swear by this tool. It’s proved to be a godsend for API documentation and keeping APIs in confirmation with the OpenAPI standard. On top of that, it also does API requests validation according to your API specifications. I strongly recommend you configure this in your web server.

Writing Tests

Writing API tests and unit tests can be a crucial part of web application development as it exposes possible gaps in your systems. You can use Superagent, the lightweight REST API, to test your APIs for all possible requests and response scenarios. Please look up the src/spec in typescript-express-server about how to use it. You can also use Postman for API Testing Automation. For most of the services that you write, you should make sure to add unit tests for each of those using Jest.

Further Reading

  1. Node.js production checklist
  2. Node.js production best practices
  3. Production best practices: performance and reliability

Did you like the blog? If yes, we're sure you'll also like to work with the people who write them - our best-in-class engineering team.

We're looking for talented developers who are passionate about new emerging technologies. If that's you, get in touch with us.

Explore current openings