Easily deploy a NestJS app for only 5€ a month (before VAT)
Tags: hetzner, nestjs, node, typescript
Published: 2022-09-01 19:37:34

## Introduction 

Free [Heroku](https://www.heroku.com/) being gone makes developer need to search for alternative deployment options for their [NodeJS](https://nodejs.org/en/) APPs, in  particular for this article [NestJS](https://nestjs.com/) APIs.

In this short tutorial I am going to show an alternative way to deploy your API, although not for free, for only 5€ a month on a cloud vps.

## Cloud Providers

There are multiple clouds that provide VPS (virtual private servers) which are VM (virtual machines) running an OS (operating system) mainly Linux.

The cheapest cloud providers are [Linode](https://www.linode.com/), [DigitalOcean](https://www.digitalocean.com/), [Vultr](https://www.vultr.com/) and [Hetzner](https://www.hetzner.com/), Hetzner being the cheapest.

## Changes to your NestJS API

Before deployment you need to make some changes to your NestJS API.

### Configuration and Environment Variables

For the intent and purpose of maintaining this tutorial short I will assume your app already have the [Config Module](https://docs.nestjs.com/techniques/configuration#installation) set up with the necessary Environment Variables. However you will need to do some changes to your configurations:

- If you are using a [PostgreSQL](https://www.postgresql.org/) database you will need to change your settings from an options object to a Postgres URL, so have a variable as follows:

```.env
DATABASE_URL='postgresql://USER:PASSWORD@HOST:PORT/DATABASE'
```

- If you are using [MongoDB](https://www.mongodb.com/) you will need to do the same, so you will need the following variable:

```.env
MONGO_URL='mongodb://username:password@host:port/defaultauthdb?options'
```

- If you are using Redis with the [IORedis](https://github.com/luin/ioredis#readme) library it can become a little bit more complicated, but as the other two you need to have a URL variable:

```.env
REDIS_URL='redis://:PASSWORD@HOST:PORT'
```

Since IORedis only support URLs if you use TLS I found that having an util function to de-structure the URL into the RedisOptions interface is easier to set-up, so on your config folder add the following redis-url-to-options.util.ts on the utils directory:

```ts
import { RedisOptions } from 'ioredis';

export const redisUrlToOptions = (url: string): RedisOptions => {
  if (url.includes('://:')) {
    const arr = url.split('://:')[1].split('@');
    const secondArr = arr[1].split(':');

    return {
      password: arr[0],
      host: secondArr[0],
      port: parseInt(secondArr[1], 10),
    };
  }

  const connectionString = url.split('://')[1];
  const arr = connectionString.split(':');
  return {
    host: arr[0],
    port: parseInt(arr[1], 10),
  };
};
```

Hence your config.ts file will look something like this:

```ts
import { IConfig } from './interfaces/config.interface';
import { redisUrlToOptions } from './utils/redis-url-to-options.util';

export function config(): IConfig {
  return {
    // ...
    redis: redisUrlToOptions(process.env.REDIS_URL),
    // ...
  };
}

```

### Changes on the package.json file

Swap the `start` script for the `start:prod` and delete the `start:prod` script.

So it goes from:

```json
{
  // ...
  "scripts": {
    // ...
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    // ...
  },
  // ...
}
```

To:

```json
{
  // ...
  "scripts": {
    // ...
    "start": "node dist/main",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    // ...
  },
  // ...
}
```

## Set Up Hetzner

Firstly [create an account](https://accounts.hetzner.com/signUp), you will need to either send a photo of your passport or do a spendable 20€ deposit to complete the registration process.

After creating an account, login, and on your console create a new project.

### Create a Server

On the server tag of your project click on add server:

![Hetzner Add Server Button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g13tvv6e54cu3v37caq1.jpg)

### Server Options

Select the latest version of Ubuntu LTS (22.04):

![Hetzner Ubuntu Options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0f6n0wp8touwqz46ne7.png)

Add your public ssh key as well if you have not already, if you do not have a ssh key pair just run the following command on your PC terminal:

<sup>NOTE: For window users you might need to install [OpenSSH](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_keymanagement)</sup>

```bash
$ ssh-keygen -t ed25519 -C "your_email@example.com"
```

### Connecting to the server

In your favourite IDE add the connection settings for your server:

- In VSC install the following extension [SSH FS](https://marketplace.visualstudio.com/items?itemName=Kelvin.vscode-sshfs) and add your server configuration where:
    * The host is your server public address;
    * The port is 22;
    * The user is root.

- In WebStorm just add an ssh configuration with the same options as seen above.

Finally open a terminal for you server inside the IDE.

#### Initial Server Commands

Before starting deployment we need to run some commands:

1. Check and update the server software:
    ```bash
    # may take sometime
    $ sudo apt update
    $ sudo apt upgrade
    ```
2. Install [dokku](https://dokku.com/) the PaaS that we will use to deploy our API:
    ```bash
    $ wget https://raw.githubusercontent.com/dokku/dokku/v0.28.1/bootstrap.sh
    $ sudo DOKKU_TAG=v0.28.1 bash bootstrap.sh
    ```
3. Assuming you have a valid domain for the API, go to your domain provider and add an A Record and a CNAME Record pointing to the server Public IP. If you do not have one you will need one, I recommend [namecheap](https://www.namecheap.com/) for the purchase;
4. Set up dokku's SSH keys and Virtualhost settings:
    ```bash
    $ cat ~/.ssh/authorized_keys | dokku ssh-keys:add admin
    $ dokku domains:set-global your-domain.com
    ```

## Deploying the API

For deploying the API you just need go through the following steps:

### On the server terminal

1. Create a new app and connect git:
    ```bash
    $ dokku apps:create app-name
    ```
2. Install your database plugin, create a db instance and link it to your app:
    ```bash
    # For PostgresSQL
    $ sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
    $ dokku postgres:create app-name-db
    $ dokku postgres:link app-name-db app-name

    # For MongoDB
    $ sudo dokku plugin:install https://github.com/dokku/dokku-mongo.git mongo
    $ dokku mongo:create app-name-db
    $ dokku mongo:link app-name-db app-name
    ```

3. If you use [cache](https://docs.nestjs.com/techniques/caching) with [redis](https://redis.io/) in your API, add [Redis plugin](https://github.com/dokku/dokku-redis) to dokku, create a new Redis instance, and link it to your app:
    ```bash
    $ sudo dokku plugin:install https://github.com/dokku/dokku-redis.git redis
    $ dokku redis:create app-name-redis
    $ dokku redis:link app-name-redis app-name
    ```
4. Add all the other environment variables that you need for your API to run:
    ```bash
    $ dokku config:set app-name URL=https://your-domain.com ...
    ```

### On your local terminal

Add a git remote to deploy your app named dokku with your server Public IP Address:

```bash
$ git remote add dokku dokku@server-public-ip-address:app-name
$ git push dokku main:master
```

### Back on the server terminal

Finally set up SSL (for HTTPS) and the main domain or sub domain for your API:

```bash
$ sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
$ dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=your-email@your.domain.com
$ dokku domains:set app-name your-domain.com
$ dokku letsencrypt:enable app-name
$ dokku letsencrypt:cron-job --add 
```

## Conclusion

With this you are now able to deploy your NestJS Apps in any VPS.