Sep 15, 2024

Heroku and Bonsai, a Winning Search Combination

5 min read

With Bonsai, customers get a robust, opinionated cluster that scales elastically to your usage. Every production Bonsai cluster is monitored 24/7, replicated for high availability, and automatically backed up hourly.

Bonsai on Heroku supports the Heroku workflow – because that’s how we work too.

Click a button, launch a cluster.

As your application grows and your needs change, no worries – we’ve got an instance for that. From hobby projects to Private spaces and everything in between, regardless of the type of instance you need, we can help. Find out how we can help you.

Find out how we can help you.

Schedule a free consultation to see how we can create a customized plan to meet your search needs.

Schedule a consultation

Deploying NestJS with Elasticsearch 7.10.2 on Heroku

We'll be using the NestJS application from our "Supercharge Your NestJS App with Hosted Search" article, which you can grab at https://github.com/omc/bonsai-examples/tree/main/nodejs/nestjs! In this article, we'll be working towards parity of this application on the nestjs/bonsai-deployed-on-heroku branch.

Once you've cloned that app, we've got just a little bit of set-up to do before we dive in!

Tip

We're using Elasticsearch version 7.10.2, as it's the last fully open-sourced version available! For additional functionality, we recommend migrating to the current version of OpenSearch!

Application house-keeping and configuration

  1. First, if you cloned the source code with git, you'll need to move the NestJS application to its own directory, outside of the git tree for the bonsai-examples repository: this is due to the fact that we'll be deploying with a git push to Heroku's provided git server!

    # from within the bonsai-examples directory
    mv ./nodejs ../bonsai-example-nodejs
    
  2. Initialize the nestjs sub-directory as a git repository, and add an initial commit:

    cd ../bonsai-example-nodejs/nestjs
    git init
    git add .
    git commit -m "Initial commit"
    

Setting up our Heroku application and dependencies

  1. Ensure you've got the Heroku CLI installed, and are logged in!

  2. From within the application's root folder (e.g. /home/my_user/bonsai-examples/nodejs/nestjs/), create a Heroku app:

    heroku create
    

You should see a response from the CLI that includes an app name (mine was the intriguing, "floating-escarpment-09801"), deployment URL, and git repository link.

Tip

Take note of the app name, URL, and git repository that the Heroku CLI outputs!

Info

If you don't see a new remote named heroku in your directory when you run git remote -v, we'll need to add it ourselves!

Add that git repository link as a remote to our project: git remote add heroku <the git repository link generated by heroku create>

With our app created, we can begin to attach dependencies. We'll start with a PostgreSQL relational database.

Info

For more details on configuration options when provisioning a Heroku database, check out their fantastic docs @ https://devcenter.heroku.com/articles/provisioning-heroku-postgres

Warning

The Heroku PostgreSQL Essential-0 resource costs real money! Don't forget to follow the tear-down steps at the end of the post!

Details on Heroku Postgres Plans are available a https://devcenter.heroku.com/articles/heroku-postgres-plans#essential-tier

heroku addons:create \
heroku-postgresql:essential-0 \
--app floating-escarpment-09801 # this was my app name from above!

Follow the instructions output by the CLI application until the database is provisioned and available for use! Mine only took a couple of minutes before being available!

Next, we'll create a Bonsai Elasticsearch database.

Tip

Bonsai provides a free sandbox cluster to all customers! Details about the Bonsai Heroku addon plans are available at https://elements.heroku.com/addons/bonsai.

For our use case, we'll be using the free sandbox plan, running Elasticsearch:

heroku addons:create \
bonsai:sandbox \
--engine=elasticsearch \
--version=7.10.2 \
--app floating-escarpment-09801

Similar to the previous step, we'll want to wait and monitor this addon for deployment status before moving on, so follow the instructions output by the Heroku CLI until your new Bonsai Elasticsearch cluster is available! Mine was up-and-running in just a few seconds!

Configuring our application to use the Bonsai Elasticsearch 7.10.2 Heroku addon

Next, we'll need to update our application's configuration a tiny bit to ensure that we pick up the new BONSAI_URL environment variable from our newly deployed Bonsai Elasticsearch cluster.

First, we'll update our src/database/config/search.config.ts to use the BONSAI_URL environment variable. Update yours to match!

// src/database/config/search.config.ts
import { ConfigModule, ConfigService, registerAs } from '@nestjs/config';
import { ElasticsearchModuleAsyncOptions } from '@nestjs/elasticsearch';

import { IsOptional, IsString, ValidateIf } from 'class-validator';
import validateConfig from '../../utils/validate-config';
import { SearchConfig } from './search-config.type';

class EnvironmentVariablesValidator {
   @ValidateIf((envValues) => !envValues.BONSAI_URL)
   @IsOptional()
   @IsString()
   BONSAI_URL: string;

   @ValidateIf((envValues) => !envValues.ELASTICSEARCH_NODE)
   @IsOptional()
   @IsString()
   ELASTICSEARCH_NODE: string;

   @IsString()
   @IsOptional()
   ELASTICSEARCH_PASSWORD: string;

   @IsString()
   @IsOptional()
   ELASTICSEARCH_USERNAME: string;
}

export const elasticSearchModuleOptions: ElasticsearchModuleAsyncOptions = {
   imports: [ConfigModule],
   inject: [ConfigService],
   useFactory: (configService: ConfigService) => {
      const username = configService.get('elasticSearch.auth.username', {
         infer: true,
      });
      const opts = {
         node: configService.get('elasticSearch.node', { infer: true }),
      };

      if (username) {
         const authOpts = {
            auth: {
               username: username,
               password: configService.get('elasticSearch.auth.password', {
                  infer: true,
               }),
            },
         };
         return { ...opts, ...authOpts };
      }
      return opts;

   },
};

export default registerAs('elasticSearch', () => {
   validateConfig(process.env, EnvironmentVariablesValidator);

   return {
      node: process.env.ELASTICSEARCH_NODE || process.env.BONSAI_URL,
      auth: {
         username: process.env.ELASTICSEARCH_USERNAME,
         password: process.env.ELASTICSEARCH_PASSWORD,
      },
   };
});

Next, as part of our deployment, we'll want to run our search database seed script in production, so let's update the src/Procfile to look like this:

web: npm run start:prod  
release: echo '' > .env && npm run migration:run && npm run seed:run:
relational && npm run seed:run:search

Save these new changes with a git commit:

git add src/database/config/search.config.ts
git commit -m "Add support for the Heroku Bonsai Elasticsearch addon and for
seeding the search database in production"

And, since Heroku prunes some of our development dependencies, which we'll use for running database migrations, we need to do a little bit of work on our package.json. Specifically, we're going to promote a couple of packages from devDependencies to dependencies:

  1. Add this heroku-prebuild hook under "scripts":

    "scripts": {
    "heroku-prebuild": "export NPM_CONFIG_PRODUCTION=false; export NODE_ENV=;
    NPM_CONFIG_PRODUCTION=false NODE_ENV=development npm install --only=dev --dev",
    }
    
  2. Move these dependencies from the "devDependencies" section to "dependencies":

    "dependencies": {
    "env-cmd": "10.1.0",
    "tsconfig-paths": "4.2.0",
    }
    

Finally, since we know that our production PostgreSQL deployment will be using SSL (as Heroku requires it for all Heroku Postgres production databases), we can set that and a few other relevant database settings for our production app right on the command line (because our app is expecting them in src/database/data-source.ts).

heroku config:set DATABASE_TYPE=postgres
heroku config:set DATABASE_SSL_ENABLED=true
heroku config:set DATABASE_REJECT_UNAUTHORIZED=false

Let's run another git commit before our final push:

git add package.json
git commit -m "Update package.json; we're ready to deploy"

Deploying with Bonsai Elasticsearch to Heroku with a git push

The moment we've all been waiting for. The pièce de résistance.

git push heroku main

This single command will trigger the applications build pipeline, run database migrations, seed the relational database, and then seed the search database!

It all happened in under a minute for me, and now we're ready to search through our movie titles:

$ curl https://floating-escarpment-09801-691a2e0ace9f.herokuapp.com/api/v1/movies?search=the%20third%20element

{
  "data": [
    {
      "id": 6,
      "title": "the fifth element",
      "createdAt": "2024-09-06T20:48:46.293Z",
      "updatedAt": "2024-09-06T20:48:46.293Z",
      "deletedAt": null,
      "__entity": "MovieEntity"
    },
    {
      "id": 24,
      "title": "the avengers",
      "createdAt": "2024-09-06T20:48:46.343Z",
      "updatedAt": "2024-09-06T20:48:46.343Z",
      "deletedAt": null,
      "__entity": "MovieEntity"
    },
    ...
  ]
}

Next Steps for your Heroku-deployed Elasticsearch / OpenSearch cluster

Now that you're live with the Bonsai Search stack on the Heroku platform, send us an email and we'll happily work with you to understand where you are on your search journey and where you want to go. We're excited to help you take your search to the next level!

Ready to take a closer look at Bonsai?

Find out if Bonsai is a good fit for you in just 15 minutes.

Learn how a managed service works and why it’s valuable to dev teams

You won’t be pressured or used in any manipulative sales tactics

We’ll get a deep understanding of your current tech stack and needs

Get all the information you need to decide to continue exploring Bonsai services

Calming Bonsai waves