Supercharge Your Node.js and Java Apps: Master API Monitoring with Prometheus

· 4 min read

Photo by Stephen Phillips - Hostreviews.co.uk on Unsplash

Introduction

Welcome to this hands-on guide where I’ll show you how to monitor external API calls using two popular tools: Node.js with Express and Axios, and Java with Spring Boot. I’ve chosen these tools because they are widely used and very effective for building REST APIs.

In this tutorial, we’ll add Prometheus, a monitoring tool, to help you understand how well your APIs are performing. This can make your applications run smoother and more reliably.

Note: This article is for people who already know a bit about Prometheus. If you’re new to Prometheus or want to learn more, there are lots of great articles and resources available online, especially on Medium.

Let’s get started and see how you can apply these monitoring techniques to your projects.

Monitoring with Node.js(Express) and Axios Interceptors

Setting Up the Project

First, set up a new Node.js project and install necessary packages:

mkdir nodejs-prometheus
cd nodejs-prometheus
npm init -y
npm install express axios prom-client

Implementing Prometheus using Axios Interceptors

We use Axios interceptors to measure the duration of API calls. This allows us to automatically track all outgoing requests made via Axios.

const express = require('express');
const axios = require('axios');
const client = require('prom-client');

const app = express();
const register = new client.Registry();

// Create a Histogram metric to record external API call durations
const externalApiDuration = new client.Histogram({
  name: 'external_api_duration_ms',
  help: 'Duration of external API calls in ms',
  labelNames: ['method', 'endpoint', 'status_code'],
  buckets: [50, 100, 200, 300, 400, 500, 1000, 2000, 5000]
});

register.registerMetric(externalApiDuration);

// Set up Axios interceptors
const axiosInstance = axios.create();

axiosInstance.interceptors.request.use(request => {
  // Add a custom property to store the start time of the request
  request.metadata = { startTime: new Date() };
  return request;
});

axiosInstance.interceptors.response.use(
  response => {
    // Calculate duration and record the metric
    const duration = new Date() - response.config.metadata.startTime;
    externalApiDuration.observe({
      method: response.config.method.toUpperCase(),
      endpoint: response.config.url,
      status_code: response.status
    }, duration);
    return response;
  },
  error => {
    // Handle error case
    if (error.config && error.config.metadata) {
      const duration = new Date() - error.config.metadata.startTime;
      externalApiDuration.observe({
        method: error.config.method.toUpperCase(),
        endpoint: error.config.url,
        status_code: error.response ? error.response.status : 500
      }, duration);
    }
    return Promise.reject(error);
  }
);

// Example route that makes an external API call using the instrumented Axios instance
app.get('/fetch-data', async (req, res) => {
  try {
    const response = await axiosInstance.get('https://jsonplaceholder.typicode.com/todos');
    res.send(response.data);
  } catch (error) {
    res.status(500).send('Error fetching data');
  }
});

// Expose the metrics endpoint
app.get('/metrics', async (req, res) => {
  res.setHeader('Content-Type', register.contentType);
  res.send(await register.metrics());
});

app.listen(3000, () => {
  console.log('Server is listening on port 3000');
});

Testing and Visualization

Once the application is running, you can access the /metrics endpoint to view your Prometheus metrics. For visualization, Grafana can be connected to your Prometheus server.

Monitoring with Java and Spring Boot

Setting Up the Project

Create a Spring Boot project and include the necessary dependencies for Spring Web, Spring Boot Actuator, and Micrometer Prometheus.

Maven Dependencies

Add the following dependencies to your pom.xml:

<dependencies>
    <!-- Spring Boot Starter Web for REST API functionality -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Boot Actuator for exposing metrics -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- Micrometer Prometheus for Prometheus metrics -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>

Configuring the RestTemplate

Create a configuration class to customize RestTemplate with a metrics-interceptor.

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestClientConfig {
    @Bean
    public RestTemplate restTemplate(MeterRegistry meterRegistry) {
        RestTemplate restTemplate = new RestTemplate();
        
        restTemplate.getInterceptors().add((request, body, execution) -> {
            Timer.Sample sample = Timer.start(meterRegistry);
            try {
                var response = execution.execute(request, body);
                sample.stop(meterRegistry.timer("external_api_requests", "method", request.getMethod().toString(),
                        "uri", request.getURI().toString(), "status", Integer.toString(response.getStatusCode().value())));
                return response;
            } catch (Exception e) {
                sample.stop(meterRegistry.timer("external_api_requests", "method", request.getMethod().toString(),
                        "uri", request.getURI().toString(), "status", "500"));
                throw e;
            }
        });
        return restTemplate;
    }
}

Creating a Sample API Endpoint

Implement a controller that uses the configured RestTemplate to perform an external API call.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;

@Controller
public class ApiController {
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("/fetch-data")
    public ResponseEntity<String> fetchData() {
        String url = "https://jsonplaceholder.typicode.com/todos";
        try {
            String result = restTemplate.getForObject(url, String.class);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Failed to fetch data");
        }
    }
}

Exposing Metrics with Actuator

Configure your application properties to expose the Prometheus metrics endpoint. Then, use the URL /actuator/prometheus as the target for Prometheus to collect metrics from your application.

management.endpoints.web.exposure.include=health,metrics,info,prometheus

Conclusion

Monitoring external API calls provides critical insights into your application’s interactions with third-party services, enhancing reliability and performance. Implementing such monitoring with Prometheus in Node.js and Java is both strategic and straightforward.

Originally published on Medium .

← Back to blog