📍 Guide principal : Développer avec l’API Google Gemini — ce tutoriel assemble plusieurs de ses briques côté web.
Un assistant en console, c’est parfait pour apprendre ; mais vos utilisateurs, eux, attendent une page web où la réponse s’écrit au fil de l’eau, comme dans les interfaces de chat modernes. Ce tutoriel construit exactement cela : un petit serveur Node.js avec Express qui relaie les questions vers Gemini en streaming, et une page web qui affiche la réponse mot à mot. Au passage, on respecte la règle d’or vue dans le guide principal — la clé API ne quitte jamais le serveur. C’est le projet qui transforme tout ce que vous avez appris en une application que l’on peut réellement montrer.
Ce que vous allez apprendre
- Initialiser un projet Node.js et installer le SDK
@google/genai. - Générer du texte et le diffuser en streaming en JavaScript.
- Monter un serveur Express qui expose un point d’entrée de chat.
- Gérer l’historique d’une conversation côté serveur.
- Consommer un flux depuis le navigateur pour un affichage progressif.
- Garder la clé API hors de portée du client.
Ce que vous allez construire
Une application web de chat complète mais minimale : une page unique avec un champ de saisie, un serveur Express qui reçoit le message, interroge Gemini en streaming, et renvoie la réponse au navigateur au fur et à mesure. Le tout en une centaine de lignes, prêt à enrichir.
Prérequis
- Node.js 20 ou plus récent (
node --versionpour vérifier) — le SDK l’exige. - Une clé API Gemini, comme dans le premier tutoriel.
- Des notions de JavaScript et de la ligne de commande.
- ⏱️ Temps estimé : environ 35 minutes.
Étape 1 — Initialiser le projet
On crée un dossier, on initialise un projet Node, et on installe les trois dépendances : le SDK Gemini, Express pour le serveur, et dotenv pour lire la clé depuis un fichier .env.
mkdir chatbot-gemini
cd chatbot-gemini
npm init -y
npm install @google/genai express dotenv
Le SDK JavaScript s’utilise avec la syntaxe des modules ES (les import). Ouvrez le fichier package.json généré et ajoutez-y la ligne "type": "module", qui active cette syntaxe. Créez ensuite un fichier .env avec votre clé, et un .gitignore contenant .env et node_modules/.
GEMINI_API_KEY=collez_votre_cle_ici
✅ Point d’étape —
package.jsoncontient"type": "module"et les trois dépendances apparaissent sousdependencies.
Étape 2 — Premier appel en JavaScript
Avant le serveur, vérifions que l’API répond depuis Node. Le SDK JavaScript suit la même logique qu’en Python : un client, un appel, une réponse. Créez test.js :
import 'dotenv/config';
import { GoogleGenAI } from '@google/genai';
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: 'Dis bonjour en une phrase.',
});
console.log(response.text);
Exécutez avec node test.js. La ligne import 'dotenv/config' charge la clé depuis .env ; on crée le client avec new GoogleGenAI, puis ai.models.generateContent renvoie un objet dont response.text contient la réponse. Si une phrase de salutation s’affiche, l’authentification et le SDK fonctionnent.
✅ Point d’étape — Une phrase s’affiche dans le terminal. Une erreur d’import signale souvent l’oubli de
"type": "module"danspackage.json.
Étape 3 — Diffuser la réponse en streaming
Pour une interface vivante, on veut le streaming : la réponse arrive par fragments. La méthode change de generateContent à generateContentStream, et on parcourt le flux avec une boucle for await.
import 'dotenv/config';
import { GoogleGenAI } from '@google/genai';
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const flux = await ai.models.generateContentStream({
model: 'gemini-2.5-flash',
contents: 'Explique en un paragraphe ce qu\'est un serveur web.',
});
for await (const morceau of flux) {
process.stdout.write(morceau.text);
}
console.log();
Chaque morceau du flux porte un bout de texte dans morceau.text ; process.stdout.write l’affiche sans saut de ligne, si bien que le texte se construit progressivement à l’écran. C’est ce flux que l’on va, à l’étape suivante, rediriger vers le navigateur au lieu du terminal.
✅ Point d’étape — Le texte apparaît graduellement. Sinon, vérifiez l’appel à
generateContentStreamet la bouclefor await.
Le streaming, sous le capot
Pourquoi le streaming donne-t-il une telle impression de réactivité ? La différence n’est pas dans le temps total — une réponse complète met le même temps à être générée — mais dans le moment où l’utilisateur voit quelque chose. Sans streaming, le serveur attend que Gemini ait fini de produire toute la réponse, puis l’envoie d’un bloc : l’utilisateur fixe un écran figé pendant plusieurs secondes. Avec le streaming, dès que le modèle a produit ses premiers mots, ils partent vers le navigateur. Le temps avant le premier caractère affiché chute de quelques secondes à quelques centaines de millisecondes. Techniquement, le serveur garde la connexion HTTP ouverte et écrit des fragments au fil de l’eau, tandis que le navigateur lit ce flux morceau par morceau. Cette perception de vitesse compte énormément sur une connexion à débit modeste, où chaque seconde d’attente perçue pèse sur l’expérience.
Étape 4 — Monter le serveur Express
On construit maintenant le serveur. Il expose un point d’entrée /chat qui reçoit un message, interroge Gemini en streaming, et écrit chaque fragment dans la réponse HTTP au fur et à mesure. Créez serveur.js :
import 'dotenv/config';
import express from 'express';
import { GoogleGenAI } from '@google/genai';
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const app = express();
app.use(express.json());
app.use(express.static('public'));
app.post('/chat', async (req, res) => {
const historique = req.body.historique || [];
historique.push({ role: 'user', parts: [{ text: req.body.message }] });
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
const flux = await ai.models.generateContentStream({
model: 'gemini-2.5-flash',
contents: historique,
});
for await (const morceau of flux) {
if (morceau.text) res.write(morceau.text);
}
res.end();
});
app.listen(3000, () => console.log('Serveur sur http://localhost:3000'));
Décortiquons les pièces. express.json() permet de lire le corps JSON des requêtes ; express.static('public') servira notre page web. Le point d’entrée /chat récupère le message, lance le streaming vers Gemini, et relaie chaque fragment au client via res.write. On ne ferme la réponse avec res.end() qu’une fois le flux épuisé. Lancez avec node serveur.js : le message de démarrage doit s’afficher.
✅ Point d’étape — Le terminal affiche « Serveur sur http://localhost:3000 ». Le serveur attend, c’est normal : il nous manque la page web.
Étape 5 — Comprendre l’historique
Vous avez peut-être remarqué le tableau historique. C’est lui qui donne de la mémoire à la conversation. Plutôt que d’envoyer une simple chaîne, on envoie une liste de tours, chacun avec un rôle — user pour l’utilisateur, model pour les réponses passées — et son texte. Le navigateur renverra cet historique à chaque message, et le serveur y ajoutera le nouveau tour de l’utilisateur avant l’appel.
Côté client, après chaque réponse complète, on ajoutera donc deux entrées à l’historique : le message de l’utilisateur et la réponse du modèle. Le serveur reste ainsi sans état, ce qui le simplifie : toute la mémoire vit dans l’historique transporté par les requêtes. Pour une vraie application multi-utilisateurs, on rangerait plutôt cet historique dans une session côté serveur, mais le principe des rôles reste le même.
Un point à garder à l’esprit pour la suite : comme tout l’historique repart au modèle à chaque message, une conversation qui s’allonge coûte de plus en plus cher en jetons. Pour un assistant à échanges courts, c’est négligeable ; pour un service qui tient de longs dialogues, on apprend vite à tronquer les tours les plus anciens, ou à les résumer périodiquement, afin de garder le contexte utile sans laisser la facture enfler. Ce n’est pas à gérer dès la première version, mais c’est un réflexe à acquérir avant la mise en production.
Étape 6 — La page web qui consomme le flux
Dernière pièce : l’interface. Créez un dossier public et, dedans, un fichier index.html. Il contient un champ de saisie et un script qui envoie le message au serveur, puis lit la réponse en streaming pour l’afficher au fur et à mesure.
<!DOCTYPE html>
<html lang="fr">
<head><meta charset="utf-8"><title>Chat Gemini</title></head>
<body>
<div id="conversation"></div>
<input id="saisie" placeholder="Votre message">
<button id="envoyer">Envoyer</button>
<script>
const historique = [];
document.getElementById('envoyer').onclick = async () => {
const champ = document.getElementById('saisie');
const message = champ.value;
champ.value = '';
const conv = document.getElementById('conversation');
conv.innerHTML += '<p><b>Vous :</b> ' + message + '</p>';
const bulle = document.createElement('p');
bulle.innerHTML = '<b>Assistant :</b> ';
conv.appendChild(bulle);
const res = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: message, historique: historique }),
});
const lecteur = res.body.getReader();
const decodeur = new TextDecoder();
let complet = '';
while (true) {
const lot = await lecteur.read();
if (lot.done) break;
const texte = decodeur.decode(lot.value, { stream: true });
complet += texte;
bulle.innerHTML += texte;
}
historique.push({ role: 'user', parts: [{ text: message }] });
historique.push({ role: 'model', parts: [{ text: complet }] });
};
</script>
</body>
</html>
Le script fait trois choses. Il envoie le message et l’historique au serveur en JSON. Il lit ensuite la réponse en streaming grâce à un reader sur le corps de la réponse : chaque lot de données est décodé et ajouté à la bulle de l’assistant, qui se remplit sous vos yeux. Enfin, une fois la réponse complète, il met à jour l’historique avec les deux nouveaux tours, pour que le message suivant garde le contexte. Rechargez http://localhost:3000 et discutez : la réponse s’écrit progressivement, et l’assistant se souvient de ce que vous avez dit.
✅ Point d’étape — La réponse s’affiche mot à mot, et une question de suivi (« et en moins cher ? ») tient compte du message précédent.
Étape 7 — La clé reste côté serveur
Observez bien l’architecture : le navigateur n’a jamais vu la clé API. Il parle à votre serveur, et c’est le serveur, seul détenteur de la clé, qui parle à Gemini. C’est la règle absolue rappelée dans le guide principal. Si vous aviez appelé Gemini directement depuis le JavaScript de la page, n’importe quel visiteur aurait pu lire votre clé dans le code source et l’utiliser à vos frais. Le serveur intermédiaire n’est pas une complication : c’est la condition d’une application sûre.
✅ Point d’étape — Cherchez votre clé dans le code de la page (clic droit, code source) : elle ne doit apparaître nulle part.
Étape 8 — Penser au déploiement
Sur votre machine, le serveur tourne tant que le terminal reste ouvert. En production, on veut qu’il survive aux fermetures de session et redémarre s’il plante. Trois réflexes suffisent pour démarrer. D’abord, on confie le processus à un gestionnaire comme pm2, qui relance automatiquement le serveur en cas d’arrêt. Ensuite, la clé API ne vit plus dans un fichier .env déposé sur le serveur, mais dans les variables d’environnement de l’hébergeur, plus sûres. Enfin, on place le serveur derrière un reverse proxy qui gère le HTTPS, condition indispensable pour une application publique. Un petit serveur de ce type tient sans peine sur l’offre la moins chère d’un hébergeur à quelques milliers de francs par mois, car il ne fait que relayer des requêtes — le travail lourd se passe chez Gemini.
✅ Point d’étape — Vous savez nommer les trois éléments d’une mise en production : gestionnaire de processus, secrets côté hôte, HTTPS.
🐞 Pièges fréquents
| Symptôme | Cause probable | Correctif |
|---|---|---|
Cannot use import statement |
"type": "module" absent de package.json. |
L’ajouter, puis relancer. |
| Clé non lue | import 'dotenv/config' manquant ou .env mal formé. |
Importer dotenv en première ligne ; vérifier le .env. |
| La page ne se charge pas | Dossier public absent ou express.static oublié. |
Créer public/index.html et servir le dossier. |
| Réponse affichée d’un bloc | Lecture de la réponse en une fois au lieu du flux. | Utiliser le reader et la boucle de lecture. |
| L’assistant n’a pas de mémoire | Historique non renvoyé ou non mis à jour côté client. | Pousser les tours user et model après chaque réponse. |
✅ Récapitulatif
Vous avez assemblé une vraie application de chat. Vous savez initialiser un projet Node en modules ES, appeler Gemini en JavaScript, diffuser une réponse en streaming, monter un serveur Express qui relaie ce flux, gérer l’historique par rôles pour la mémoire de conversation, et consommer le flux côté navigateur pour un affichage progressif — le tout en gardant la clé hors de portée du client. C’est le squelette sur lequel se construisent la plupart des assistants web : il ne reste qu’à brancher dessus les autres briques de cette série, comme le RAG pour répondre sur vos données.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
npm install @google/genai express dotenv |
Installer les dépendances. |
new GoogleGenAI({ apiKey }) |
Créer le client. |
ai.models.generateContentStream({ model, contents }) |
Générer en streaming. |
for await (const morceau of flux) |
Parcourir les fragments. |
res.write(...) / res.end() |
Relayer le flux au client. |
{ role, parts: [{ text }] } |
Un tour de conversation. |
💪 À vous de jouer
Donnez une personnalité à l’assistant via une instruction système, et ajoutez un bouton « Effacer » qui vide l’historique pour repartir d’une conversation vierge.
Voir une solution
// Cote serveur, passer une instruction systeme dans la configuration :
const flux = await ai.models.generateContentStream({
model: 'gemini-2.5-flash',
contents: historique,
config: {
systemInstruction: 'Tu es un assistant commercial poli, en francais, concis.',
},
});
// Cote client, le bouton Effacer :
document.getElementById('effacer').onclick = () => {
historique.length = 0;
document.getElementById('conversation').innerHTML = '';
};
L’instruction système se passe dans config.systemInstruction côté serveur, et vider historique côté client suffit à réinitialiser la mémoire.
Tutoriels suivants
- Recherche sémantique et RAG — faire répondre ce chatbot sur vos propres documents.
- Function calling — lui permettre de déclencher des actions.
Pour aller plus loin
🔝 Retour au guide principal de l’API Gemini. La documentation officielle du SDK @google/genai détaille les options de streaming et de configuration.
Questions fréquentes
Pourquoi un serveur, et pas un appel direct depuis la page ? Parce que la clé API serait alors visible de tous. Le serveur la garde secrète et sert d’intermédiaire.
Le serveur garde-t-il l’historique ? Dans cette version, non : l’historique voyage avec les requêtes. Pour une application réelle, on le stockerait dans une session côté serveur.
Peut-on faire la même chose avec un framework front comme React ? Oui. La logique de lecture du flux est identique ; seul l’affichage change selon le framework.
Et en Python côté serveur ? C’est possible aussi, avec un framework comme FastAPI et la version Python du SDK ; le principe du relais de flux est le même.