Logo

dev-resources.site

for different kinds of informations.

Building a Production Stack: Docker, Meilisearch, NGINX & NestJS

Published at
12/31/2024
Categories
docker
nestjs
nginx
meilisearch
Author
biomousavi
Categories
4 categories in total
docker
open
nestjs
open
nginx
open
meilisearch
open
Author
10 person written this
biomousavi
open
Building a Production Stack: Docker, Meilisearch, NGINX & NestJS

Introduction

If you’re reading this, chances are you’re already familiar with Elasticsearch and are now curious about Meilisearch. So, no need to start from the basics, let’s dive right into why Meilisearch might be the better choice!

  1. Simplicity: Meilisearch has 90% fewer configuration options compared to Elasticsearch, meaning less time spent learning and setting up. For example, while Elasticsearch often requires 5–10 configurations just to index and search efficiently, Meilisearch works well out of the box with minimal tweaks. 😎

  2. Performance: Meilisearch is designed for instant search and is optimized for low-latency results, typically under 50ms, which makes it feel snappier for end users. Elasticsearch, while powerful, often requires extra optimization to reach similar response times for smaller datasets.

  3. Typo Tolerance: Meilisearch provides built-in typo tolerance without extra configuration, improving the user experience significantly.

If your use case doesn’t require massive scalability or advanced querying, Meilisearch is the simpler, faster, and more cost-effective option!

and if you are a Rustacean 🦀, yeah it is written in Rust language.

What’s the Plan?

We’re going to create a Nest application to seed Meilisearch, set up Meilisearch itself, and configure NGINX as reverse proxy in front of it.

NestJS Application

  • The repository with the complete code is linked at the end of this article.

Start by preparing your NestJS application.

Install the Meilisearch JavaScript Client:

npm install meilisearch
Enter fullscreen mode Exit fullscreen mode

Now we want to create an endpoint to generate apiKey for front-end application, so we need a controller like this

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller('/')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('api-key')
  async createApiKey() {
    return this.appService.createApiKey();
  }
}

Enter fullscreen mode Exit fullscreen mode

then we need to add the functionality to our module service:

import { Injectable, OnModuleInit } from '@nestjs/common';
import { MeiliSearch } from 'meilisearch';

@Injectable()
export class AppService implements OnModuleInit {
  private meiliClient: MeiliSearch;
  private index = 'movies';

  constructor() {
    // Create new Meilisearch Instance
    this.meiliClient = new MeiliSearch({
      host: 'http://meilisearch:7700',
      apiKey: 'masterKey',
    });

    // Check MeiliSearch Health
    this.meiliClient.health().then(console.log);
  }

// Seed Meilisearch
  async onModuleInit() {
    await this.seed();
  }

// Generate API key for front-end search
  async createApiKey() {
    const key = await this.meiliClient.createKey({
      expiresAt: null,
      indexes: [this.index],
      name: 'Search API Key',
      actions: ['search', 'documents.get'],
      description: 'Use it to search from the frontend code',
    });

    return key;
  }

// Seeding Functionality
  private async seed() {
    const index = this.meiliClient.index(this.index);

    const documents = [
      { id: 1, title: 'Nick Mousavi', genres: ['Romance', 'Drama'] },
      { id: 2, title: 'Wonder Woman', genres: ['Action', 'Adventure'] },
      { id: 3, title: 'Life of Pi', genres: ['Adventure', 'Drama'] },
      { id: 4, title: 'Mad Max: Fury Road', genres: ['Adventure'] },
      { id: 5, title: 'Moana', genres: ['Fantasy', 'Action'] },
      { id: 6, title: 'Philadelphia', genres: ['Drama'] },
    ];

    await index.addDocuments(documents);
  }
}
Enter fullscreen mode Exit fullscreen mode

MeiliSearch Option Object:

  • apiKey: 'masterKey' is the api key that we will configure in meilisearch service in docker-compose.yml file.
  • host: 'http://meilisearch:7700' is the Meilisearch address in our docker network.

createKey method options:

  • expiresAt: When the key becomes invalid (null means never expires)
  • indexes: Which Meilisearch indexes this key can access
  • name: Key identifier for your reference
  • actions: Permissions (search: can search, documents.get: can fetch individual documents)
  • description: Note about key's purpose

Now your NestJS application is ready to connect to MeiliSearch server and generate API keys.

Dockerizing

Here, we'll create a Dockerfile to containerize our NestJS application for production. At the root of your NestJS project, create a file named Dockerfile:

FROM node:22.12.0 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . ./
RUN npm run build && npm prune --production



FROM gcr.io/distroless/nodejs22-debian12 AS production
ENV NODE_ENV=production
WORKDIR /app 
COPY --from=build /app/dist /app/dist
COPY --from=build /app/node_modules /app/node_modules
COPY --from=build /app/package.json /app/package.json

Enter fullscreen mode Exit fullscreen mode

Perfect, now our NestJS production build is ready to add to our docker-compose.yml file.

NGINX Configuration

We'll use Nginx as a reverse proxy to route requests to our API and Meilisearch services in the Docker Compose setup. Create an nginx.conf file at the root of the project and add the following content:

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;

    upstream api {
        server api:3000;
    }

    upstream meilisearch {
        server meilisearch:7700;
    }

    server {
        listen 80;

        # API endpoints
        location /api/ {
            proxy_pass http://api/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        # Meilisearch endpoints
        location /search/ {
            proxy_pass http://meilisearch/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now your NGINX webserver is ready to route requests to api and meilisearch services.

Docker Compose

We’ll use Docker Compose to manage all the services (API, NGINX, and Meilisearch) together.

Start by creating a docker-compose.yaml file at the root of the project and add the following content. Don’t worry, I’ll explain each service step by step!

services:

  # Nestjs API Service
  api:
    build:
      context: .
      target: production
      dockerfile: ./Dockerfile
    restart: unless-stopped
    networks:
      - biomousavi
    depends_on:
      - meilisearch
    ports:
      - 3000:3000
    command: 'dist/main.js'

  # Meilisearch Service
  meilisearch:
    image: getmeili/meilisearch:v1.12
    volumes:
      - meilisearch-volume:/meili_data
    ports:
      - '7700:7700'
    environment:
      - MEILI_MASTER_KEY=masterKey
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:7700']
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - biomousavi

  # Nginx Service
  nginx:
    image: nginx:alpine
    ports:
      - '80:80'
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - biomousavi
    depends_on:
      - api
      - meilisearch

networks:
  biomousavi:
    driver: bridge

volumes:
  meilisearch-volume:
Enter fullscreen mode Exit fullscreen mode

This Docker Compose file defines three services:

  1. API Service: Runs the NestJS application in production mode, exposing it on port 3000. It depends on the Meilisearch service to start first.

  2. Meilisearch Service: Runs Meilisearch for search functionality, storing its data in a persistent volume and secured with a master key. It listens on port 7700.

  3. Nginx Service: Acts as a reverse proxy, routing requests to the API and Meilisearch. It uses a custom Nginx configuration and exposes port 80 for external access.

Your file structure should be something similar to this:

docker-nginx-meilisearch-nest-folder-structure

Run The Application

To build and run the application, use one of the following commands:

docker compose up --build
#OR run in detached mode
docker compose up --build -d
Enter fullscreen mode Exit fullscreen mode

Generate a New API Key

In your client application, send a GET request to obtain a new API key for search requests:

curl 'http://localhost/api/api-key'
Enter fullscreen mode Exit fullscreen mode

This request calls the NestJS API and returns a JSON response like this:

{
  "name": "Search API Key",
  "description": "Use it to search from the frontend code",
  "key": "a5e62c45497396bb1b0535b4cc4b84dec37713c5bdb4b78f18624af6f33ebac7",
  "uid": "82f8a1a4-77cd-481d-9fcb-21fdde13246f",
  "actions": ["search", "documents.get"],
  "indexes": ["movies"],
  "expiresAt": null,
  "createdAt": "2024-12-31T18:06:45.190728957Z",
  "updatedAt": "2024-12-31T18:06:45.190728957Z"
}
Enter fullscreen mode Exit fullscreen mode

Perform a Search Request

Use the obtained API key (key) to search your index. Add it as a bearer token in the request:

curl 'http://localhost/search/indexes/movies/search?q=mad+max' -H 'Authorization: Bearer your_api_key'
Enter fullscreen mode Exit fullscreen mode
  • Replace your_api_key with the key from the previous response.

It sends request to Meilisearch service.

The response should be similar to the following JSON:

{
  "hits": [
    {
      "id": 4,
      "title": "Mad Max: Fury Road",
      "genres": [
        "Adventure"
      ]
    }
  ],
  "query": "mad max",
  "processingTimeMs": 8,
  "limit": 20,
  "offset": 0,
  "estimatedTotalHits": 1
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

By combining Docker, Meilisearch, NGINX, and NestJS, you can set up a scalable, production-ready search solution with minimal configuration. This guide should help you streamline the process of creating, deploying your search engine. Whether you're optimizing for performance or simplicity, this stack provides a solid foundation for your next project.

If you found this guide helpful ❤️ and saved you time, I'd be incredibly grateful if you could show some support by giving the repository a ⭐ star on GitHub!

nginx Article's
30 articles in total
Favicon
nginx-mod-http-geoip
Favicon
How to run a Nginx-web server
Favicon
ngx whitelist/blacklist module
Favicon
Nginx Simplified: Technical Insights with Real-World Analogies
Favicon
Nginx Configuration Tips for Secure Communication: Enabling mTLS and checking client fingerprint
Favicon
Building a Scalable Reverse Proxy Server like Nginx with Node.js and TypeScript
Favicon
Deploy NestJS and NextJS application in same server using pm2 and Nginx
Favicon
Setting Up an NGINX Reverse Proxy with a Node.js Cluster Using Docker
Favicon
การทำ HTTPS ด้วย Certbot และ Nginx บน Ubuntu Server
Favicon
How to configure Free SSL Certificate on Nginx using Certbot
Favicon
Docker Hands-on: Learn Docker Volume and Bind Mounts with Sample Projects using NGINX
Favicon
自建的git远程仓库,在push时413 Request Entity Too Large
Favicon
Optimize SvelteKit performance with brotli compression
Favicon
I’m running a Spring Boot application inside a Docker container on my VM. The application works fine over HTTP, and I can access all endpoints via http://127.0.0.1:8080. I’ve set up NGINX as a reverse proxy to serve HTTPS requests. No errors for http reqs.
Favicon
Deploying a MERN App on Azure: The Smart Way
Favicon
My First Full-Stack Deployment with Docker and NGINX as Load Balancer
Favicon
Streamlined Release Process for a Web Application: Trunk-Based Development with Feature Flags
Favicon
How to Install NGINX on Ubuntu 22.04
Favicon
Secure Nginx with Let's Encrypt on Ubuntu
Favicon
Kubernetes Ingress Controllers and NGINX Ingress: A Complete Guide
Favicon
What is HTTP 499 Status Code and How to Fix it?
Favicon
Docker Compose Demo: Running Multiple Services with Two Domains on Localhost
Favicon
Building a Production Stack: Docker, Meilisearch, NGINX & NestJS
Favicon
Step-by-Step Guide: Assigning a Namecheap Domain to DigitalOcean Hosting with Nginx
Favicon
Streamlining React CI/CD Pipelines with GitHub Actions
Favicon
Connecting to an EC2 Instance with Ubuntu and Installing NGINX on AWS
Favicon
Installing Nginx Web Server on Linux: A Step-by-Step Guide
Favicon
Hosting multiple Websites on a single Nginx Server
Favicon
Unleashing the Power of NGINX as an API Gateway
Favicon
Installing Wordpress with Nginx in Ubuntu

Featured ones: