Quand on développe une API autrement qu'en TypeScript, on se retrouve souvent avec un problème de synchronisation : les types existent côté serveur, mais il faut les recopier ou les maintenir à la main côté client. Dès que l'API évolue, un oubli peut rapidement créer un décalage entre les deux parties. ConnectRPC apporte une solution en permettant de construire une API gRPC compatible avec les navigateurs à partir de de définition protobuf.
Le principe de ConnectRPC
ConnectRPC s'appuie sur les fichiers Protocol Buffers pour définir le contrat de l'API. Un fichier .proto décrit à la fois :
- les messages envoyés par le client.
- les messages retournés par le serveur.
- les services et les méthodes disponibles.
Ce fichier devient la source de vérité du projet et permettront de générer du code côté backend pour implémenter les handlers, mais aussi du côté frontend pour obtenir un client typé.
Dans cet exemple, on définit une méthode Greet qui reçoit une requête contenant un prénom et un nom, puis retourne une réponse contenant un message. ConnectRPC va ensuite utiliser cette définition pour générer les types et les fonctions nécessaires.
Générer le code avec Buf
Pour générer le code, ConnectRPC s'appuie sur Buf, qui permet de vérifier les fichiers .proto, de gérer les dépendances et de lancer les générateurs nécessaires.
Le fichier buf.gen.yml permet de préciser ce que l'on veut générer. On peut par exemple générer le code Go pour le backend, puis ajouter une étape supplémentaire pour générer le code TypeScript destiné au frontend. La configuration dépendra du langage utilisé, mais ConnectRPC supporte nativement Go, Node.js, Python, Kotlin, Swift et Dart.
Implémenter le serveur
Côté serveur, ConnectRPC génère une interface que l'on doit implémenter. Chaque méthode définie dans le fichier .proto correspond à une fonction à écrire dans un handler. La logique métier reste dans votre code, mais la signature de la méthode vient du fichier .proto. Si le contrat change, l'interface générée change aussi, et l'éditeur ou le linter peut immédiatement signaler ce qu'il faut corriger.
Une fois le handler créé, on peut l'enregistrer dans notre serveur web.
Par exemple, en Go :
La version Node.js suit le même principe :
JSON et gRPC
ConnectRPC offre l'avantage de supporter plusieurs modes de communication. L'API peut être appelée en gRPC, avec un transfert binaire plus performant, mais elle peut aussi être appelée en JSON pour la rendre compatible avec les navigateur web (le gRPC classique repose sur des fonctionnalités HTTP/2 qui ne sont pas utilisables en JavaScript via fetch()).
Validation des données
ConnectRPC peut aussi s'intégrer à un système de validation basé sur les annotations dans les fichiers .proto. L'idée est d'ajouter des règles directement sur les champs, puis de les faire appliquer côté serveur avec un intercepteur.
Les requêtes invalides seront alors rejetées automatiquement, avec une erreur structurée. Cela permet de centraliser une partie des règles dans le contrat de l'API, plutôt que de les éparpiller manuellement dans chaque handler.
Utilisation côté frontend
Côté frontend, ConnectRPC fournit les outils nécessaires pour créer un client à partir du service généré. On configure d'abord un transport, qui indique comment communiquer avec l'API, puis on crée le client.
Le point important ici est que greet, firstName, lastName et message sont connus grâce au code généré depuis le fichier .proto. Si le contrat change, TypeScript pourra signaler les appels à mettre à jour.
Intégration avec React Query
Si vous utilisez React, il existe aussi une intégration avec React Query via connect-query. Elle permet d'utiliser les services générés avec les hooks habituels, comme useQuery ou useMutation.
On conserve alors les avantages de React Query, comme la gestion du chargement et du cache, tout en profitant des types générés depuis le fichier .proto.
Cache HTTP et méthodes GET
Par défaut, les appels RPC JSON sont tous faits en POST. Pour profiter du cache HTTP, ConnectRPC permet de déclarer certaines méthodes comme immutables afin qu'elles puissent être appelées avec la méthode GET.
C'est utile pour les méthodes de lecture qui ne modifient pas l'état du serveur. Il faut alors ajuster la configuration côté client.
Limites et cas particuliers
ConnectRPC ne cherche pas à remplacer toute votre application HTTP. Vous pouvez très bien avoir une partie de votre API en gRPC et conserver des routes classiques à côté. C'est notamment pertinent pour des routes spéciales, comme celles qui servent à envoyer des fichiers depuis le navigateur. Il est possible de représenter un fichier sous forme binaire encodée, mais ce n'est pas forcément optimal. Une approche plus simple consiste à garder une route dédiée avec un FormData, séparée de la partie gRPC.
La partie authentification et sécurité sort aussi du cadre de ConnectRPC. C'est à vous de mettre en place les mécanismes nécessaires. Dans la plupart des cas, on passera les informations utiles via les en-têtes, avec une logique spécifique côté serveur.
Quand utiliser ConnectRPC ?
ConnectRPC est intéressant lorsque vous voulez partager les types entre un backend et un frontend, surtout si les deux parties ne sont pas écrites dans le même langage. On garde les fichiers .proto comme source de vérité, on génère le code des deux côtés, et on évite de maintenir les types à la main.
L'outil est particulièrement adapté si vous utilisez Go côté serveur et TypeScript côté client, mais il existe aussi des intégrations pour d'autres langages comme Python, Kotlin ou Dart.
Pour aller plus loin, vous pouvez consulter la documentation officielle : connectrpc.com.