NATS est un système permettant de gérer une distribution de messages et notamment une file d'attente côté serveur pour vos applications.

C'est un système très facile à utiliser et à mettre en place.

Dans ce cas pratique, nous verrons comment l'installer et l'utiliser avec NodeJS dans le contexte d'une file d'attente et d'un worker qui traite les messages qu'il reçoit au fur et à mesure.

Prérequis

Pour suivre l'installation vous devez connaître les bases de l'éditeur de texte "nano" et avoir installé docker sur votre machine.

Pour la partie Node JS, il est nécessaire d'installer le runtime Node JS et éventuellement Git.

Installation

Création du docker-compose

Connectez-vous sur votre serveur via SSH, créez un dossier pour NATS et positionnez-vous dessus.

mkdir nats
cd nats

Utilisez nano pour créer un fichier "docker-compose.yml".

nano docker-compose.yml

Insérez ceci dans le fichier :

services:
  nats:
    image: nats:latest
    container_name: nats_server
    ports:
      - "4222:4222"
      - "8222:8222"
    environment:
      - NATS_SERVER_NAME=nats-server
      - NATS_MAX_CONNECTIONS=1024
      - NATS_USER=nats-user
      - NATS_PASSWORD=<votre-mot-de-passe>
    volumes:
      - ./nats.conf:/etc/nats.conf
      - nats_data:/data
    command: ["-js", "--config", "/etc/nats.conf"]
    restart: unless-stopped

  nats_cli:
    image: natsio/nats-box:latest
    container_name: nats_cli
    depends_on:
      - nats
    environment:
      - NATS_URL=nats://nats-user:<votre-mot-de-passe>@nats:4222
    entrypoint: /bin/sh
    stdin_open: true
    tty: true

volumes:
  nats_data:
    driver: local
⚠️
Pensez à remplacer <votre-mot-de-passe> par un mot de passe sécurisé !

Créer le fichier de configuration NATS

Utilisez nano pour créer un fichier "nats.conf".

nano nats.conf

Insérez ceci dans le fichier :

authorization {
  users = [
    {user: "nats-user", password: "<votre-mot-de-passe>"}
  ]
}

Lancer l’app

docker compose up -d

Utilisation CLI

Pour utiliser shell à l’intérieur du conteneur CLI :

docker exec -it nats_cli /bin/sh

S’abonner à un sujet NATS

nats sub test

Publier sur un sujet

nats pub test "message"

Publier depuis un serveur distant

nats --user="nats-user" --password="<votre-mot-de-passe>" --server=nats://<ip-de-votre-serveur>:4222 pub test "essai"

Utilisation avec NodeJS

Installer dotenv et NATS

npm i dotenv nats

Créer un fichier .env

Ce fichier évite de coder les valeurs sensibles dans le dur, il définit des "Variables d'environnement".

NATS_URL="nats://<ip-de-votre-serveur>:4222"
NATS_USER="nats-user"
NATS_PASSWORD="<votre-mot-de-passe>"

Créer nats.js pour se simplifier la vie :

Ce bout de code crée une classe NATS pour faciliter la mise en place d'un système de file d'attente avec worker(s).

import { connect } from 'nats';
import dotenv from 'dotenv';
dotenv.config();

const { NATS_URL, NATS_USER, NATS_PASSWORD } = process.env;

export class NATS {
	constructor() {
		this.connection = null;
	}

	async connect() {
		try {
			this.connection = await connect({
				servers: NATS_URL,
				user: NATS_USER,
				pass: NATS_PASSWORD
			});
		} catch (e) {
			console.error('Erreur connection NATS : ', e);
			throw Error('Erreur NATS, veuillez réessayer plus tard...');
		}
	}

	async publish(subject, data) {
		if (!this.connection) {
			await this.connect();
		}

		try {
			await this.connection.publish(subject, JSON.stringify(data));
		} catch (e) {
			console.error('Erreur publication NATS : ', e);
			throw Error('Erreur NATS, veuillez réessayer plus tard...');
		}
	}

	async subscribe(subject, callback, options = {}) {
		if (!this.connection) {
			await this.connect();
		}

		try {
			const subscription = this.connection.subscribe(subject, options);

			for await (const message of subscription) {
				const data = JSON.parse(message.data);
				await callback(data);
			}
		} catch (e) {
			console.error('Erreur subscription NATS : ', e);
			throw Error('Erreur NATS, veuillez réessayer plus tard...');
		}
	}

	async close() {
		if (this.connection) {
			await this.connection.close();
		}
	}
}

export async function simplePublish(subject, data) {
	const nats = new NATS();
	await nats.connect();
	await nats.publish(subject, data);
	await nats.close();
}

Exemple de code émetteur

La méthode la plus simple pour publier sur un sujet (en l'occurence "test").

import { simplePublish } from './nats.js';

async function main(){
  await simplePublish('test', 'Bonjour !');
}

main()

Exemple de code émetteur 2

L'intérêt de ce deuxième exemple est de garder la connexion avec NATS ouverte, puis de la fermer manuellement plutôt que de la réouvrir et refermer à chaque fois comme c'est le cas avec "simplePublish".

import { NATS } from './nats.js';

async function main(){
  const nats = new NATS();
  await nats.publish('test', 'Bonjour !');
  await nats.publish('test', 'Ça va ?');
  await nats.close();
}

main()

Exemple de code Worker

Ici, je crée une file d'attente en définissant une "queue" et je limite le nombre d'exécutions simultanées pour ce Worker à 2.

import { NATS } from './nats.js';

(async () => {
	const nats = new NATS();
	await nats.connect();

	console.log('Worker is listening for test events...');
	await nats.subscribe(
		'test',
		async (data) => {
			console.log(data);
			}
		},
		{
			queue: 'test-group',
			max_in_flight: 2
		}
	);
})();
💡
Afin que le worker fonctionne correctement et sans interruption, je recommande l'utilisation de PM2 (un gestionnaire de processus Node JS).

Aller plus loin

Welcome | NATS Docs

Documentation de NATS

PM2 - Single Page Doc
Advanced process manager for production Node.js applications. Load balancer, logs facility, startup script, micro service management, at a glance.

Documentation de PM2

Merci pour votre lecture.

Installer NATS sur votre VPS