NestJS APM with Elastic and Docker
Tags: nestjs, elasticsearch, docker, apm
Published: 2024-01-04 00:12:41

## Introduction
Observability is a crucial aspect of modern software development, offering a comprehensive view into the performance and behavior of applications. As applications become more complex and distributed, understanding how they function in real-time becomes paramount. Application Performance Monitoring (APM) plays a pivotal role in observability, providing developers with the tools to track, analyze, and optimize their applications.

In this article, we'll delve into the implementation of APM with NestJS and Elasticsearch. By harnessing the power of these technologies, developers can gain valuable insights into their application's health, identify bottlenecks, and enhance overall performance. 

## Pre requirements

- NestJs CLI
- Docker


### 1. Init NestJS app

`$ npm i -g @nestjs/cli`
`$ nest new apm-example`

### 2. Add elastic-apm-node lib
`$ yarn add elastic-apm-node`

### 3. Create env file
**.env**
`ELASTIC_APM_SERVICE_NAME=apm-example
 ELASTIC_APM_SERVER_URL=http://apm-server:8200`

### 4. Add @nestjs/config
`$ yarn add @nestjs/config`

### 5. Import ConfigModule
**app.module.ts**

```
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApmModule } from './apm/apm.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ApmModule,
    ConfigModule.forRoot({
      envFilePath: '.env',
      isGlobal: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

```
### 6. Compiler options
Add compiler options configurations for correct functioning of the agent
[Elastic config agent with ts](https://www.elastic.co/guide/en/apm/agent/nodejs/current/typescript.html)
**tsconfig.json**
`"esModuleInterop": true,
 "moduleResolution": "node"`


### 7. Create apm module and service
`$ nest g module apm`
`$ nest g service apm`

**apm.module.ts**
```
import { Module } from '@nestjs/common';
import { ApmService } from './apm.service';

@Module({
  providers: [ApmService],
})
export class ApmModule {}

```

**apm.service.ts**
```
import { Injectable } from '@nestjs/common';
import 'elastic-apm-node/start';

@Injectable()
export class ApmService {}

```
### 8. Create files Dockerfile and docker-compose
Create Dockerfile and docker-compose.yml in src folder:
**Dockerfile**
```
FROM node:18.12.0-alpine AS build

WORKDIR /app

COPY package.json yarn.lock ./

RUN yarn 

COPY . .

RUN yarn build

FROM node:18.12.0-alpine

WORKDIR /app

COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/yarn.lock ./yarn.lock

EXPOSE ${PORT}

CMD ["yarn", "start:prod"]
```
**docker-compose.yml**
```
version: '3.7'

services:
  apm-example:
    hostname: apm-example
    restart: on-failure
    build:
      context: .
      dockerfile: ./Dockerfile
    volumes:
      - .:/app
    env_file:
      - .env
    ports:
      - "3000:3000"
    command: npm run start:dev
    depends_on:
      - apm-server
    cpus: 1
    mem_limit: 1024M
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    healthcheck:
      interval: 30s
      retries: 10
      test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"'
  kibana:
    image: docker.elastic.co/kibana/kibana:7.12.1
    container_name: kibana
    ports:
      - 5601:5601
    environment:
      ELASTICSEARCH_URL: http://elasticsearch:9200
    cpus: 0.1
    mem_limit: 256M 
    depends_on:
      elasticsearch:
        condition: service_healthy
    healthcheck:
      interval: 40s
      retries: 20
      test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:5601/api/status
  apm-server:
    image: docker.elastic.co/apm/apm-server:7.17.16
    depends_on:
      elasticsearch:
        condition: service_healthy
      kibana:
        condition: service_healthy
    cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"]
    cap_drop: ["ALL"]
    ports:
    - 8200:8200
    command: >
       apm-server -e
         -E apm-server.rum.enabled=true
         -E setup.kibana.host=kibana:5601
         -E setup.template.settings.index.number_of_replicas=0
         -E apm-server.kibana.enabled=true
         -E apm-server.kibana.host=kibana:5601
         -E output.elasticsearch.hosts=["elasticsearch:9200"]
    healthcheck:
      interval: 10s
      retries: 12
      test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:8200/
    cpus: 0.1
    mem_limit: 256M 
volumes:
  es-data:
```
### 9. Run docker containers
`$ docker-compose up --build -d --timeout 180`

### 10. GET /
access the application endpoint to collect the first metrics
[http://localhost:3000](http://localhost:3000)

### 11. Open elastic app
[http://localhost:5601](http://localhost:5601/app/apm/services?rangeFrom=now-5m&rangeTo=now)

you should see something similar to this

![Elastic APM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7vu8b0kda6f9k2y33yu.png)


![Elastic APM Trace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ss4jmaaam53zn5p44cvx.png)

### 12. Collect docker metrics with Metricbeat
[http://localhost:5601/app/home#/tutorial/dockerMetrics](http://localhost:5601/app/home#/tutorial/dockerMetrics)

After install and configure Metricbeat, you should run
`$ sudo metricbeat setup
 $ sudo service metricbeat start`

you should see something similar to this

![Docker metrics received data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqppfyj6qvz5udo6xqbq.png)

Click in **Docker metrics dashboard**

you should see something similar to this

![Docker metrics dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ijeid5mil0lyagqiiadz.png)

Now you can explore Elastic metrics, happy learning!

repo: [apm-example](https://github.com/eduardoconti/apm-example)