In this article, you’ll learn how to set up a Node.js project with TypeScript, ExpressJs, PostgreSQL, TypeORM, and Redis.

Related Post: Backend

  1. API with Node.js + PostgreSQL + TypeORM: Project Setup
  2. API with Node.js + PostgreSQL + TypeORM: JWT Authentication
  3. API with Node.js + PostgreSQL + TypeORM: Send Emails
  4. Node.js, Express, TypeORM, PostgreSQL: CRUD Rest API
  5. Node.js and PostgreSQL: Upload and Resize Multiple Images
API with Node.js + PostgreSQL + TypeORM Project Setup

What the course will cover

  • How to run docker containers with docker-compose
  • How to connect a TypeORM Express app to PostgreSQL
  • How to connect a TypeORM Express app to Redis

Prerequisites

  • Knowledge of Docker and Docker-compose
  • Knowledge of Node.js and Express

Setup Node.js with Express, PostgreSQL, Redis, and TypeORM

Creating PostgreSQL and Redis Database with Docker

The most straightforward way to get PostgreSQL and Redis database instances running on our computer is to use Docker and Docker-compose.

Am going to assume you already have Docker and Docker-compose installed on your machine.

Create a docker-compose.yml file in your root directory and paste the configurations below into it.

docker-compose.yml


version: '3'
services:
  postgres:
    image: postgres:latest
    container_name: postgres
    ports:
      - '6500:5432'
    volumes:
      - progresDB:/var/lib/postgresql/data
    env_file:
      - ./.env

  redis:
    image: redis:alpine
    container_name: redis
    ports:
      - '6379:6379'
    volumes:
      - redisDB:/data
volumes:
  progresDB:
  redisDB:



vs code mysql extension

Later, we’re going to use this VS Code extension to view the data stored in both the PostgreSQL and Redis databases.

To provide the credentials needed by the PostgreSQL Docker image, we need to create a .env file in the root directory.

You can add the .env file to your .gitignore file to avoid committing it. In my case, I will include some of the content in the .env in an example.env so that you can see what the structure of the .env file should look like.

.env


PORT=8000
NODE_ENV=development

POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER=admin
POSTGRES_PASSWORD=password123
POSTGRES_DB=node_typeorm

Once all of the above is set up correctly, we need to run the docker containers.


docker-compose up -d

Setup Environment Variables

The most important thing about our application is to set up environment variables. This will allow us to store sensitive data and we can easily omit it from being committed to a repository.

Am going to use the popular library dotenv to inject the environment variables.

Also, I will use the config library to load our environment variables to make them available to our application. The config library will also help us to provide the TypeScript types for our environment variables.

Initialize a new Typescript Node.js project with this command:


yarn init -y && yarn add -D typescript @types/node && npx tsc --init

Run this command in the terminal to install the dotenv and config packages.


yarn add config dotenv && yarn add -D @types/config

.env


PORT=8000
NODE_ENV=development

POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER=admin
POSTGRES_PASSWORD=password123
POSTGRES_DB=node_typeorm



JWT_ACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT1FJQkFBSkJBSjdVblpyNUxpUGJxbDRENlo3VHVKK2NFMkI0Y3FzbnUzeUJaUHo2NmtqZDhJT1RFdjlNCkpEdmhMQ05PczYyWHBZcmFZYU5HS3UrN3Q4YVVjcWNoRzJNQ0F3RUFBUUpBRS84YXRKY29tdlVkOXVZeE5JRGQKWHFMc3dabUlma25yVGRxUWwxVVR5QWFPRWpIRGFnR0lGdEhRZE5IZTAybkp6a2Z1WkdWSkJVRmo1aTJJVyszMQpxUUloQU9LQVRxOVpSZHR0T0JDWWJLR3VpbjZnd1FVZ2YzUGkwSjh3Snh3cjNRVDFBaUVBczRRZ0lhR1c1V3NaCmNqWUQ3bVpwcXBiRkdubnBvMVR6QU1YS2psQ2dKL2NDSUQyZVZFbWx5cmhnSlNGMnBnN3lNZUV6RUcrNW9KTEIKUUtvZDZuWGloUFZGQWlCY0ZuUXhMR1p1NjhEUzhOaVZiQjNhYjV0TzJLazhxekE0L2ozSlFaeld3d0lnU2N1awoyZWlBM3E5ci9EdDQ3OUZHek4xSEVSdzJ2TXZjeEV6REZWRnV5aDg9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
JWT_ACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSjdVblpyNUxpUGJxbDRENlo3VHVKK2NFMkI0Y3Fzbgp1M3lCWlB6NjZramQ4SU9URXY5TUpEdmhMQ05PczYyWHBZcmFZYU5HS3UrN3Q4YVVjcWNoRzJNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==
JWT_REFRESH_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT0FJQkFBSkFmTmJzOVJhUGNnVG5RalB4cExLUXkvRHdBSVh5NFNiUWdlejFNU2pwWnozQ0tsTXdpWWwyCm5IUVg5Rk5NcU1pMmxnL29yUjJDTUJxRzFNdnlvN1EyTVFJREFRQUJBa0FKUStwU1JscGZHLzRONjgwRGJEMVMKNVk3cWV3YUxyMVhLVHN2ajJpVjRoQUdxb0d3K3NvbEZUalp1Y21xaW9vY1cydTNXYVdxdkNrVmtvamc2OFFBQgpBaUVBNElqNkFXbG91am84T1YvSEJ3elZpaDQzRllCNEpkZnd3SjFwYTBRVFlWRUNJUUNPVlhMdGZOTFh3cXFlCndZNVIrWktwY3JBb0tBMjVYM3M1cEhVNFhYVk80UUlnSDM0VzBxdmVMSUNPZ2QyVkpNQUFFMmM1Z3FLS040U2EKRituOEp6ZGRJSUVDSUZ4SllUeEU2L3lEdHRjNnpzbXVGWDhTNHM4V3NWZFpabStJaDR5bFpGTmhBaUFGRWJwSApqdlZqSHlPdlIyQ2F1clFmTmU3QldWVnVlODNQN3pYSXV3eHl5Zz09Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
JWT_REFRESH_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZzd0RRWUpLb1pJaHZjTkFRRUJCUUFEU2dBd1J3SkFmTmJzOVJhUGNnVG5RalB4cExLUXkvRHdBSVh5NFNiUQpnZXoxTVNqcFp6M0NLbE13aVlsMm5IUVg5Rk5NcU1pMmxnL29yUjJDTUJxRzFNdnlvN1EyTVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t

It is a convention for environment variables to be in uppercase.

In the root directory create a config folder and within the config folder create two files named default.ts and custom-environment-variables.ts.

default.ts


export default {
  origin: 'http://localhost:3000',
  accessTokenExpiresIn: 15,
  refreshTokenExpiresIn: 60,
  redisCacheExpiresIn: 60,
};


custom-environment-variables.ts


export default {
  port: 'PORT',
  postgresConfig: {
    host: 'POSTGRES_HOST',
    port: 'POSTGRES_PORT',
    username: 'POSTGRES_USER',
    password: 'POSTGRES_PASSWORD',
    database: 'POSTGRES_DB',
  },
  accessTokenPrivateKey: 'JWT_ACCESS_TOKEN_PRIVATE_KEY',
  accessTokenPublicKey: 'JWT_ACCESS_TOKEN_PUBLIC_KEY',
  refreshTokenPrivateKey: 'JWT_REFRESH_TOKEN_PRIVATE_KEY',
  refreshTokenPublicKey: 'JWT_REFRESH_TOKEN_PUBLIC_KEY',
};


Validating Environment Variables

Forgetting to add an environment variable or using the right type for an environment variable value can lead to unexpected bugs which will cause your application to malfunction.

To prevent this, we’re going to use the envalid package to validate the environment variables.


yarn add envalid

src/utils/validateEnv.ts


import { cleanEnv, port, str } from 'envalid';

const validateEnv = () => {
  cleanEnv(process.env, {
    NODE_ENV: str(),
    PORT: port(),
    POSTGRES_HOST: str(),
    POSTGRES_PORT: port(),
    POSTGRES_USER: str(),
    POSTGRES_PASSWORD: str(),
    POSTGRES_DB: str(),
    JWT_ACCESS_TOKEN_PRIVATE_KEY: str(),
    JWT_ACCESS_TOKEN_PUBLIC_KEY: str(),
    JWT_REFRESH_TOKEN_PRIVATE_KEY: str(),
    JWT_REFRESH_TOKEN_PUBLIC_KEY: str(),
  });
};

export default validateEnv;


The envalid package will throw an error if we forget to provide any of the defined variables or if they’re of the wrong types.

Initialize a New TypeORM Express App

We can use the TypeORM CLI to generate a base Express project with everything already set up.

To do that, you need to install typeorm package globally or as a dependency.


yarn add typeorm

Next, run this command to generate the Express TypeORM boilerplate project.

You can delete the package.lock file if you are using yarn. Finally, run yarn install to install all the dependencies.


npx typeorm init --database postgres --express

The --database flag tells it what database to use. I used postgres since that’s what am writing about.

Next, change all the names of the folders created by TypeORM to their plural forms. Example: controller -> controllers .

Connecting an Express application with PostgreSQL

Also, move the data-source.ts into the utils folder and paste the code snippets below into it.

src/utils/data-source.ts


require('dotenv').config()
import 'reflect-metadata';
import { DataSource } from 'typeorm';
import config from 'config';

const postgresConfig = config.get<{
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
}>('postgresConfig');

export const AppDataSource = new DataSource({
  ...postgresConfig,
  type: 'postgres',
  synchronize: false,
  logging: false,
  entities: ['src/entities/**/*.entity{.ts,.js}'],
  migrations: ['src/migrations/**/*{.ts,.js}'],
  subscribers: ['src/subscribers/**/*{.ts,.js}'],
});


Next, paste the TypeScript configurations below into the tsconfig.json to avoid getting unnecessary errors.

tsconfig.json


{
  "compilerOptions": {
    "target": "es2016",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "skipLibCheck": true,
    "outDir": "./build",
    "rootDir": "."
  }
}


At the time of writing this article, the TypeORM Express base project doesn’t come with the Express type definition file so run yarn add -D @types/express to install it manually.

Connecting an ExpressJs application to Redis

src/utils/connectRedis.ts


import { createClient } from 'redis';

const redisUrl = 'redis://localhost:6379';

const redisClient = createClient({
  url: redisUrl,
});

const connectRedis = async () => {
  try {
    await redisClient.connect();
    console.log('Redis client connected successfully');
    redisClient.set('try', 'Hello Welcome to Express with TypeORM');
  } catch (error) {
    console.log(error);
    setTimeout(connectRedis, 5000);
  }
};

connectRedis();

export default redisClient;


Next, change the index.ts file to app.ts and paste the code snippets below into it.

src/app.ts


require('dotenv').config();
import express, { Response } from 'express';
import config from 'config';
import validateEnv from './utils/validateEnv';
import { AppDataSource } from './utils/data-source';
import redisClient from './utils/connectRedis';

AppDataSource.initialize()
  .then(async () => {
    // VALIDATE ENV
    validateEnv();

    const app = express();

    // MIDDLEWARE

    // 1. Body parser

    // 2. Logger

    // 3. Cookie Parser

    // 4. Cors

    // ROUTES

    // HEALTH CHECKER
    app.get('/api/healthchecker', async (_, res: Response) => {
      const message = await redisClient.get('try');
      res.status(200).json({
        status: 'success',
        message,
      });
    });

    // UNHANDLED ROUTE

    // GLOBAL ERROR HANDLER

    const port = config.get<number>('port');
    app.listen(port);

    console.log(`Server started on port: ${port}`);
  })
  .catch((error) => console.log(error));


Next, remove body-parser since Express has body-parser already built-in. So run the command below to remove body-parser and install ts-node-dev .


yarn remove body-parser && yarn add -D ts-node-dev

Your package.json should look somewhat like this after installing all the dependencies.

package.json


{
 "dependencies": {
    "config": "^3.3.7",
    "dotenv": "^16.0.0",
    "envalid": "^7.3.1",
    "express": "^4.18.1",
    "pg": "^8.4.0",
    "redis": "^4.1.0",
    "reflect-metadata": "^0.1.13",
    "typeorm": "0.3.6"
  },
  "devDependencies": {
    "@types/config": "^0.0.41",
    "@types/express": "^4.17.13",
    "@types/node": "^16.11.10",
    "ts-node": "10.7.0",
    "ts-node-dev": "^1.1.8",
    "typescript": "4.5.2"
  }
}

The ts-node-dev package will allows us automatically restart the server.

Now, update the start script in the package.json to use ts-node-dev .

package.json


{
 "scripts": {
    "start": "ts-node-dev --respawn --transpile-only --exit-child src/app.ts",
    "typeorm": "typeorm-ts-node-commonjs"
  }
}

Finally, run yarn start to start the development server on port: 8000 assuming you used the same configurations in this article.

The PostgreSQL and Redis Docker containers must be running before you start the development server.

In your browser, enter this URL http://localhost:8000/api/healthchecker and you should see a success message.

API with Node.js PostgreSQL TypeORM test message

Conclusion

In this article, you learned how to set up Node.js, ExpessJs, TypeScript, PostgreSQL, and Redis with TypeORM.

Source code on GitHub