NodeJS


Journaux liées à cette note :

Journal du mercredi 12 février 2025 à 10:44 #PostgREST, #backup, #JaiDécouvert

En travaillant sur le projet Projet 23 - "Ajouter le support pg_basebackup incremental à restic-pg_dump-docker", j'ai découvert que PostgreSQL propose deux protocoles de communication :

Sans vraiment comprendre, en 2020, j'avais activé le protocole streaming replication dans ce POC postgresql-streaming-replication-playground. Mais je n'avais jamais pris conscience de l'existence de ce second protocole.

Les développeurs de PostgreSQL semblent avoir décidé de créer un second protocole parce qu'ils n'ont pas du tout le même objectif.
Streaming Replication Protocol est optimisé dans la transmission des WAL et des snapshots (copie de l'intégralité du dossier PGDATA).

Le protocole Streaming Replication Protocol est entre autres utilisé par pg_basebackup, barman, pgBackRest, ou Patroni.

Comment activer Streaming Replication Protocol ?

Les images Docker Postgres setup par défaut le fichier pg_hba.conf suivant :

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust
# IPv6 local connections:
host    all             all             ::1/128                 trust
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     trust
host    replication     all             127.0.0.1/32            trust
host    replication     all             ::1/128                 trust
host all all all scram-sha-256

J'ai compris que par défaut, toutes ces lignes configurent l'accès au Frontend/Backend Protocol.
Seules les lignes qui contiennent replication configurent l'accès au Streaming Replication Protocol.

Pour permettre l'accès au Streaming Replication Protocol par réseau TCP/IP en dehors du container Docker il est nécessaire d'ajouter la ligne suivante :

host    replication     all             all                     scram-sha-256

Suite à cette découverte, j'ai repensé à :

Pour faire face à ce problème, j'ai exploré fin 2023 une solution basée sur pgBackRest : Implémenter un POC de pgBackRest.
Je suis plus ou moins arrivé au bout de ce POC mais je n'ai pas été satisfait du résultat.
Je n'ai pas réussi à configurer pgBackRest en "pure Docker sidecar".
De plus, j'ai trouvé la restauration du backup difficile à exécuter.

source

et je me suis demandé si mon échec de configuration de pgBackRest en Docker sidecar n'était pas seulement dû au fait que je n'avais pas activé Streaming Replication Protocol 🤔.

La réponse semble être "oui" et "non".

Je suis tombé sur l'issue suivante : pgbackrest with postgresql in docker.

The problem might be harder than you think unfortunately. If the pgBackRest process is running on the VM (docker host), it will try to connect to PG locally using the unix socket, not the tcp "localhost" connection.

source

Je pense comprendre que pgBackRest ne permet pas d'utiliser des INET sockets pour communiquer avec PostgreSQL.

Toutefois, je me dis que je pourrais partager le volume PGDATA avec le sidecar pgBackRest pour lui donner accès à l'Unix Socket du Streaming Replication Protocol 🤔.

Entre mon exploration de pg_basebackup, mes envies de tester barman et de continuer mon POC pgBackRest… je me dis que je ne suis pas encore au bout de ce Yak!.

Playground qui présente comment je setup un projet Python Flask en 2025 #dev-kit, #python, #mise, #docker, #WSL, #playground, #software-engineering

Je pense que cela doit faire depuis 2015 que je n'ai pas développé une application en Python Flask !

Entre 2008 et 2015, j'ai beaucoup itéré dans mes méthodes d'installation et de setup de mes environnements de développement Python.

D'après mes souvenirs, si je devais dresser la liste des différentes étapes, ça donnerai ceci :

  • 2006 : aucune méthode, j'installe Python 🙂
  • 2007 : je me bats avec setuptools et distutils (mais ça va, c'était plus mature que ce que je pouvais trouver dans le monde PHP qui n'avait pas encore imaginé composer)
  • 2008 : je trouve la paie avec virtualenv
  • 2010 : j'ai peur d'écrire des scripts en Bash alors à la place, j'écris un script bootstrap.py dans lequel j'essaie d'automatiser au maximum l'installation du projet
  • 2012 : je me bats avec buildout pour essayer d'automatiser des éléments d'installation. Avec le recul, je réalise que je n'ai jamais rien compris à buildout
  • 2012 : j'utilise Vagrant pour fixer les éléments d'installation, je suis plutôt satisfait
  • 2015 : je suis radicale, j'enferme tout l'environnement de dev Python dans un container de développement, je monte un path volume pour exposer le code source du projet dans le container. Je bricole en entrypoint avec la commande "sleep".

Des choses ont changé depuis 2015.

Mais, une chose que je n'ai pas changée, c'est que je continue à suivre le modèle The Twelve-Factors App et je continue à déployer tous mes projets packagé dans des images Docker. Généralement avec un simple docker-compose.yml sur le serveur, ou alors Kubernetes pour des projets de plus grande envergure… mais cela ne m'arrive jamais en pratique, je travaille toujours sur des petits projets.

Choses qui ont changé : depuis fin 2018, j'ai décidé de ne plus utiliser Docker dans mes environnements de développement pour les projets codés en NodeJS, Golang, Python

Au départ, cela a commencé par uniquement les projets en NodeJS pour des raisons de performance.

J'ai ensuite découvert Asdf et plus récemment Mise. À partir de cela, tout est devenu plus facilement pour moi.
Avec Asdf, je n'ai plus besoin "d'enfermer" mes projets dans des containers Docker pour fixer l'environnement de développement, les versions…

Cette introduction est un peu longue, je n'ai pas abordé le sujet principal de cette note 🙂.

Je viens de publier un playground d'un exemple de projet minimaliste Python Flask suivant mes pratiques de 2025.

Voici son repository : mise-python-flask-playground

Ce playground est "propulsé" par Docker et Mise.

J'ai documenté la méthode d'installation pour :

Je précise que je n'ai pas eu l'occasion de tester l'installation sous Windows, hier j'ai essayé, mais je n'ai pas réussi à installer WSL2 sous Windows dans un Virtualbox lancé sous Fedora. Je suis à la recherche d'une personne pour tester si mes instructions d'installation sont valides ou non.

Briques technologiques présentes dans le playground :

Voici quelques petites subtilités.

Dans le fichier alembic.ini j'ai modifié le paramètre file_template parce que j'aime que les fichiers de migration soient classés par ordre chronologique :

[alembic]
# template used to generate migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s

Ce qui donne par exemple :

20250205_124639_users.py
20250205_125437_add_user_lastname.py

Ici le port de PostgreSQL est généré dynamiquement par docker compose :

  postgres:
    image: postgres:17
	...
	ports:
      - 5432 # <= ici

Avec cela, fini les conflits de port quand je lance plusieurs projets en même temps sur ma workstation.

L'URL vers le serveur PostgreSQL est générée dynamiquement par le script get_postgres_url.sh qui est appelé par le fichier .envrc. Tout cela se passe de manière transparente.

J'initialise ici les extensions PostgreSQL :

def init_db():
    db.drop_all()
    db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'))
    db.session.execute(db.text('CREATE EXTENSION IF NOT EXISTS "unaccent"'))
    db.session.commit()
    db.create_all()

et ici dans la première migration :

def upgrade():
    op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
    op.execute('CREATE EXTENSION IF NOT EXISTS "unaccent";')
    op.create_table('users',
        sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
        sa.Column('firstname', sa.String(), nullable=False),
        sa.PrimaryKeyConstraint('id')
    )

Journal du mercredi 29 janvier 2025 à 11:55 #serveur-http, #Nuxt, #javascript, #JaiDécouvert, #JeMeDemande

#JaiDécouvert Nitro (https://nitro.build/)

Next Generation Server Toolkit.

Create web servers with everything you need and deploy them wherever you prefer.

source

D'après ce que j'ai compris, Nitro est un serveur http en NodeJS qui a été spécialement conçu pour Nuxt.js.

Nitro a été introduit fin 2021 dans la version 3 de Nuxt.

Nitro fait partie de l'écosystème UnJS.

Je découvre l'existence de UnJS Ecosystem. #JeMeDemande si ce projet a un lien avec VoidZero 🤔.

Je viens de vérifier, SvelteKit ne semble pas utiliser Nitro. Nitro semble être utilisé uniquement par Nuxt.js.

Journal du lundi 09 décembre 2024 à 15:50 #dev-kit, #docker

J'utilise la fonctionnalité Docker volume mounts dans tous mes projets depuis septembre 2015.

Généralement, sous la forme suivante :

services:
  postgres:
    image: postgres:17
    ...
	volumes:
      - ./volumes/postgres/:/var/lib/postgresql/data/

D'après mes recherches, la fonctionnalité volumes mounts a été introduite dans la version 0.5.0 en juillet 2013.

À cette époque, je crois me souvenir que Docker permettait aussi de créer des volumes anonymes. Je n'ai jamais apprécié les volumes anonymes, car lorsqu'un conteneur était supprimé, il devenait compliqué de retrouver le volume associé.
À cette époque, Docker était nouveau et j'avais très peur de perdre des données, par exemple, les volumes d'une instance PostgreSQL.

J'ai donc décidé qu'il était préférable de renoncer aux volumes anonymes et d'opter systématiquement pour des volume mounts.

Ensuite, peut-être en janvier 2016, Docker a introduit les named volumes, qui permettent de créer des volumes avec des noms précis, par exemple :

services:
  postgres:
    image: postgres:17
    ...
	volumes:
      - postgres:/var/lib/postgresql/data/

volumes:
  postgres:
    name: postgres
$ docker volume ls
DRIVER    VOLUME NAME
local     postgres

Ce volume est physiquement stocké dans le dossier /var/lib/docker/volumes/postgres/_data.

Depuis, j'ai toujours préféré les volumes mounts aux named volumes pour les raisons pratiques suivantes :

  • Travaillant souvent sur plusieurs projets, j'utilise les volume mounts pour éviter les collisions. Lorsque j'ai essayé les named volumes, une question s'est posée : quel nom attribuer aux volumes PostgreSQL ? « postgres » ? Mais alors, quel nom donner au volume PostgreSQL dans le projet B ? Avec les volume mounts, ce problème ne se pose pas.
  • J'apprécie de savoir qu'en supprimant un projet avec rm -rf ~/git/github.com/stephan-klein/foobar/, cette commande effacera non seulement l'intégralité du projet, mais également ses volumes Docker.
  • Avec les mounted volume, je peux facilement consulter le contenu des volumes. Je n'ai pas besoin d'utiliser docker volume inspect pour trouver le chemin du volume.

La stratégie que j'ai choisie basée sur volumes mounts a quelques inconvénients :

  • Le owner du dossier volumes/, situé dans le répertoire du projet, est root. Cela entraîne fréquemment des problèmes de permissions, par exemple lors de l'exécution des scripts de linter dans le dossier du projet. Pour supprimer le projet, je dois donc utiliser sudo. Je précise que ce problème n'existe pas sous MacOS. Je pense que ce problème pourrait être contourné sous Linux en utilisant podman.
  • La commande docker compose down -v ne détruit pas les volumes.

Je suis pleinement conscient que ma méthode basée sur les volume mounts est minoritaire. En revanche, j'observe qu'une grande majorité des développeurs privilégie l'utilisation des named volumes.

Par exemple, cet été, un collègue a repris l'un de mes projets, et l'une des premières choses qu'il a faites a été de migrer ma configuration de volume mounts vers des named volumes pour résoudre un problème de permissions lié à Prettier, eslint ou Jest. En effet, la fonctionnalité ignore de ces outils ne fonctionne pas si NodeJS n'a pas les droits d'accès à un dossier du projet 😔.

Aujourd'hui, je me suis lancé dans la recherche d'une solution me permettant d'utiliser des named volumes tout en évitant les problèmes de collision entre projets.

Je pense que j'ai trouvé une solution satisfaisante 🙂.

Je l'ai décrite et testée dans le repository docker-named-volume-playground.

Ce repository d'exemple contient 2 projets distincts, nommés project_a et project_b.
J'ai instancié deux fois chacun de ces projets. Voici la liste des dossiers :

$ tree
.
├── project_a_instance_1
│   ├── docker-compose.yml
│   └── .envrc
├── project_a_instance_2
│   ├── docker-compose.yml
│   └── .envrc
├── project_b_instance_1
│   ├── docker-compose.yml
│   └── .envrc
├── project_b_instance_2
│   ├── docker-compose.yml
│   └── .envrc
└── README.md

Ce repository illustre l'organisation de plusieurs instances de différents projets sur la workstation du développeur.
Il ne doit pas être utilisé tel quel comme base pour un projet.
Par exemple, le "vrai" repository du projet projet_a se limiterait aux fichiers suivants : docker-compose.yml et .envrc.

Voici le contenu d'un de ces fichiers .envrc :

export PROJECT_NAME="project_a"
export INSTANCE_ID=$(pwd | shasum -a 1 | awk '{print $1}' | cut -c 1-12) # Used to define docker volume path
export COMPOSE_PROJECT_NAME=${PROJECT_NAME}_${INSTANCE_ID}

L'astuce que j'utilise est au niveau de INSTANCE_ID. Cet identifiant est généré de telle manière qu'il soit unique pour chaque instance de projet installée sur la workstation du développeur.
J'ai choisi de générer cet identifiant à partir du chemin complet vers le dossier de l'instance, je le passe dans la commande shasum et je garde les 12 premiers caractères.

J'utilise ensuite la valeur de COMPOSE_PROJECT_NAME dans le docker-compose.yml pour nommer le named volume :

services:
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: postgres
      POSTGRES_PASSWORD: password
    ports:
      - 5432
    volumes:
      - postgres:/var/lib/postgresql/data/
    healthcheck:
      test: ["CMD", "sh", "-c", "pg_isready -U $$POSTGRES_USER -h $$(hostname -i)"]
      interval: 10s
      start_period: 30s

volumes:
  postgres:
     name: ${COMPOSE_PROJECT_NAME}_postgres

Exemples de valeurs générées pour l'instance installée dans /home/stephane/git/github.com/stephane-klein/docker-named-volume-playground/project_a_instance_1 :

  • INSTANCE_ID=d4cfab7403e2
  • COMPOSE_PROJECT_NAME=project_a_d4cfab7403e2
  • Nom du container postgresql : project_a_d4cfab7403e2-postgres-1
  • Nom du volume postgresql : project_a_a04e7305aa09_postgres

Conclusion

Cette méthode me permet de suivre une pratique plus mainstream — utiliser les named volumes Docker — tout en évitant la collision des noms de volumes.

Je suis conscient que ce billet est un peu long pour expliquer quelque chose de simple, mais je tenais à partager l'historique de ma démarche.

Je pense que je vais dorénavant utiliser cette méthode pour tous mes nouveaux projets.


20224-12-10 11h27 : Je tiens à préciser qu'avec la configuration suivante :

services:
  postgres:
    image: postgres:17
    ...
	volumes:
      - postgres:/var/lib/postgresql/data/

volumes:
  postgres:

Quand le nom du volume postgres n'est pas défini, docker-compose le nomme sous la forme ${COMPOSE_PROJECT_NAME}_postgres. Si le projet est stocké dans le dossier foobar, alors le volume sera nommé foobar_postgres.

$ docker volume ls
DRIVER    VOLUME NAME
local     foobar_postgres

Pourquoi faire un refactoring de Nuxt.js vers HTMX ? 🤔 #WebDev, #htmx, #django, #Nuxt, #SvelteKit

En étudiant l'annonce Développeur(se) back-end en CDI de Brief.me j'ai été intrigué par :

Migrer notre front-end existant de Nuxt.js vers HTMX.

Je me suis demandé quelle est la motivation de passer d'un framework de type Nuxt.js, NextJS ou SvelteKit à htmx 🤔.

J'utilise SvelteKit depuis deux ans, qui est comparable Nuxt.js, principalement en mode SSR avec Hydration, et je suis totalement satisfait. Ma Developer eXperience est excellente. Je trouve ce framework minimaliste, conforme au principe Keep it simple, stupid! (KISS), et performant.

Après réflexion, j'ai réalisé que mon expérience de développeur (DX) serait moindre si j'héritais d'un backend codé dans un langage autre que Javascript : Python, PHP, Ruby, Golang

Et Brief.me semble être dans ce cas de figure :

Je me suis connecté à mon compte Brief.me et en regardant ce que me retourne Wappalyzer, je constate que le site est effectivement propulsé par Nuxt.js, VueJS.

En regardant ce qu'il se passe dans l'onglet Réseau de mon navigateur, je constate que https://app.brief.me est un site web de type SPA. Le contenu des articles est chargé via l'api https://www.brief.me/api/ qui est propulsée par Django REST framework.

Si je résume, la stack est la suivante : PostgreSQL => Django => Django REST framework <=> Nuxt.js => Rendu HTML via VueJS.

Je suppose que ce qui motive la migration de Nuxt.js vers HTMX est la suppression de couches.
Plus précisément, je suppose que l'équipe tech de Brief.me souhaite supprimer les couches suivantes :

Et utiliser simplement le système de template de Django en SSR pour afficher le contenu des articles et implémenter les quelques éléments dynamiques côté browser avec HTMX.
De mon point de vue, ceci a pour avantage de largement simplifier la stack, de simplifier le déploiement et d'accélérer le chargement des pages.

Ma conclusion : la librairie HTMX semble être un choix très pertinent quand elle est utilisée dans une stack non NodeJS.

Journal du mercredi 11 septembre 2024 à 11:14 #projet, #javascript, #playground, #JeMeDemande

Dans la branche gibbon-replay-js du projet Idée d'un outil de session recoding web minimaliste basé sur rrweb, j'ai essayé sans succès d'extraire du code dans un package Javascript.

Pour le moment l'import suivant ne fonctionne pas :

import gibbonReplayJs from 'gibbon-replay-js';

Quand je lance pnpm run build, j'ai l'erreur suivante :

$ pnpm run build
...
x Build failed in 336ms
error during build:
src/routes/(record)/+layout.svelte (2:11): "default" is not exported by "packages/gibbon-replay-js/dist/index.js", imported by "src/routes/(record)/+layout.svelte".
file: /home/stephane/git/github.com/stephane-klein/gibbon-replay-poc/src/routes/(record)/+layout.svelte:2:11

1: <script>
2:     import gibbonReplayJs from 'gibbon-replay-js';

Et quand je lance pnpm run dev, j'ai l'erreur suivante :

$ pnpm run dev

...

11:21:21 [vite] Error when evaluating SSR module /packages/gibbon-replay-js/dist/index.js:
|- ReferenceError: exports is not defined
    at eval (/home/stephane/git/github.com/stephane-klein/gibbon-replay-poc/packages/gibbon-replay-js/dist/index.js:5:23)
    at instantiateModule (file:///home/stephane/git/github.com/stephane-klein/gibbon-replay-poc/node_modules/.pnpm/vite@5.4.3/node_modules/vite/dist/node/chunks/dep-BaOMuo4I.js:52904:11)

11:21:21 [vite] Error when evaluating SSR module /src/routes/(record)/+layout.svelte:
|- ReferenceError: exports is not defined
    at eval (/home/stephane/git/github.com/stephane-klein/gibbon-replay-poc/packages/gibbon-replay-js/dist/index.js:5:23)
    at instantiateModule (file:///home/stephane/git/github.com/stephane-klein/gibbon-replay-poc/node_modules/.pnpm/vite@5.4.3/node_modules/vite/dist/node/chunks/dep-BaOMuo4I.js:52904:11)

Suite à cette frustration, j'ai envie de créer un projet, sans doute nommé javascript-package-playground dans lequel je souhaite étudier les sujets suivants :

  • mise en place d'une librairie /packages/lib1/ qui contient une librairie javascript, qui peut être importé avec la méthode ECMAScript Modules ;
  • mise en place d'une app NodeJS dans /services/app1_nodejs/ qui utilise lib1 ;
  • mise en place d'une app SvelteKit dans /services/app2_sveltekit/ qui utilise lib1 dans un fichier coté server et dans une page web coté browser ;
  • mise en place d'une librairie /packages/lib2 qui utilise lib1

Je souhaite décliner ces 2 libs et 2 apps sous plusieurs déclinaisons d'implémentation :

Et le tout encore dans deux déclinaisons : Javascript et TypeScript.

Je ne souhaite pas supporter CommonJS qui est sur le déclin, remplacé par ECMAScript Modules.

Dans ce playground, je souhaite aussi me perfectionner dans l'usage de pnpm link et pnpm workspace.

#JeMeDemande si ces connaissances sont totalement maitrisées et évidentes chez mes amis développeurs Javascript 🤔 et s'ils les considèrent comme "basiques".