White Hex icon
Introducing: A search & relevancy assessment from engineers, not theorists. Learn more

Sep 16, 2024

Heroku and Bonsai, a Winning Search Combination

10

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.

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 <span class="inline-code"><pre><code>nestjs/bonsai-deployed-on-heroku</code></pre></span> 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 <span class="inline-code"><pre><code>bonsai-examples</code></pre></span> repository: this is due to the fact that we'll be deploying with a <span class="inline-code"><pre><code>git push</code></pre></span> to Heroku's provided git server!


# from within the bonsai-examples directory
mv ./nodejs ../bonsai-example-nodejs


  1. Initialize the <span class="inline-code"><pre><code>nestjs</code></pre></span> 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. <span class="inline-code"><pre><code>/home/my_user/bonsai-examples/nodejs/nestjs/</code></pre></span>), 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

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 <span class="inline-code"><pre><code>sandbox</code></pre></span> 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 <span class="inline-code"><pre><code>BONSAI_URL</code></pre></span> environment variable from our newly deployed Bonsai Elasticsearch cluster.

First, we'll update our <span class="inline-code"><pre><code>src/database/config/search.config.ts</code></pre></span> to use the <span class="inline-code"><pre><code>BONSAI_URL</code></pre></span> 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 <span class="inline-code"><pre><code>src/Procfile</code></pre></span> 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 <span class="inline-code"><pre><code>package.json</code></pre></span>. Specifically, we're going to promote a couple of packages from <span class="inline-code"><pre><code>devDependencies</code></pre></span>  to <span class="inline-code"><pre><code>dependencies</code></pre></span>:

  1. Add this <span class="inline-code"><pre><code>heroku-prebuild</code></pre></span> hook under <span class="inline-code"><pre><code>"scripts"</code></pre></span>:


"scripts": {
  "heroku-prebuild": "export NPM_CONFIG_PRODUCTION=false; export NODE_ENV=; NPM_CONFIG_PRODUCTION=false NODE_ENV=development npm install --only=dev --dev",
}


  1. Move these dependencies from the <span class="inline-code"><pre><code></code>"devDependencies"</pre></span> section to <span class="inline-code"><pre><code>"dependencies"</code></pre></span>:


"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 <span class="inline-code"><pre><code>src/database/data-source.ts</code></pre></span>).



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


Let's run another <span class="inline-code"><pre><code>git commit</code></pre></span> 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!

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.