Commencer nodejs

Ce cours fait partiellement suite au cours de React.js.

Introduction à Node

Node.js c'est une manière d'éxécuter du JavaScript de manière "serveur". Normalement, le JavaScript c'est utilisé dans le navigateur, mais avec Node on peut faire toutes sortes de choses, des serveurs webs, des APIs, des applications natives (oui, oui!), etc.

Objectifs

  • Comprendre comment Node fonctionne
  • Créer un serveur web avec Node et Hono
  • Créer une API avec Node et Prisma

Création du projet

Créez votre projet à côté de votre front, donc par exemple :

		├── front-end
├── back-end
   ├── src
   ├── package.json
   └── etc...
	

Pour créer le projet, on va utiliser Hono. La commande suivante va vous demander de choisir un template, scrollez avec les flèches pour trouver le templater NodeJS puis appuyez sur la touche entrée : (si on vous demande le package manager, on utilise npm)

		npm create hono@latest back-end
	

Après avoir créé le projet, on oublie pas d'installer les dépendances :

		npm install
	

Puis, pour lancer le serveur, on utilise la commande (comme un peu partout) :

		npm run dev
	

Concepts de base, API REST

Avec la template de base, vous y trouverez une route GET / qui vous renvoie un text avec le message "Hello World". Donc si vous allez sur http://localhost:3000/, vous devriez voir ce message.

Qu'est-ce que ça veut dire GET / ? Ça veut dire que la fonction liée, s'éxécutera lorsque qu'on fera une requête GET sur / (Votre navigateur fait des requêtes GET pour récupérer les pages, par exemple http://localhost:3000/). Vous connaissez déjà la requête POST, par exemple. Pour créer une tâche par exemple, on pourrait dire POST /tasks par exemple, avec un body qui contient les données de la tâche. Ça ressemblerait à ça :

index.js
		app.post('/tasks', async (c) => {
	// On récupère les données depuis le body de la requête
	const { title, description } = await c.req.json();
 
	// TODO: Ajouter la tâche à la base de données
 
	// Puis renvoyer un message de confirmation
	return c.json({ message: 'Task created' });
});
	

Création de la BDD et initialisation de Drizzle

Pour gérer la BDD, on va utiliser Drizzle, c'est un ORM (Object-Relational Mapping) qui permet de gérer la BDD de manière fonctionnelle, plutôt que de faire des requêtes SQL manuellement. Avec Drizzle, on va choisir SQLite comme type de base de données, parce que c'est plus simple à utiliser.

Pour commencer, on va installer Drizzle, @libsql/client, dotenv (je vous expliquerai pourquoi plus tard) et drizzle-kit (qui nous permettra de gérer le schéma de la base de données plus facilement)

		npm i drizzle-orm @libsql/client dotenv
npm i -D drizzle-kit
	

Variables d'environnement

C'est le moment de parler de dotenv, en fait c'est un paquet qui sert à importer ce qu'on appelle des "variables d'environnement". C'est des variables qui sont stockées dans un fichier .env et qui sont utilisées dans le code. C'est utile pour stocker des informations sensibles, comme les mots de passe, les tokens, etc. C'est aussi pratique pour stocker des variables qui sont différentes en fonction de l'environnement (dev / votre pc, prod / votre serveur, etc).

C'est donc dans ce fichier qu'on va stocker l'URL de la base de données :

.env
		DATABASE_URL=file:./dev.db
	

Puis on va créer notre connection à la base de données dans un fichier à part :

src/db/clientDb.js
		// Cette ligne nous sert à importer les variables d'environnement
import "dotenv/config";
import { drizzle } from "drizzle-orm/libsql";
// Et on y accède en utilisant process.env.NOM_DE_LA_VARIABLE
export const db = drizzle(process.env.DATABASE_URL);
	

Création du schéma de la base de données

Avec un ORM, le schéma de la base de données n'est pas géré directement avec quelque chose comme PhpMyAdmin comme vous auriez pu le faire. Ici, on va définir notre schéma dans le code :

src/db/schema.js
		// On importe les différents types de champs que l'on peut utiliser avec SQLite
import { sql } from "drizzle-orm";
import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";
 
export const tasks = sqliteTable("tasks", {
  // On crée nos champs et on leur donne un type
  id: integer().primaryKey({ autoIncrement: true }),
  title: text().notNull(),
  description: text().notNull(),
  status: text().notNull(),
 
  // C'est toujours une bonne idée de mettre des champs pour la date de création et de mise à jour
  createdAt: integer({ mode: "timestamp" })
    .notNull()
    .default(sql`(CURRENT_DATE)`),
  updatedAt: integer({ mode: "timestamp" }),
});
	

Pour terminer, on a besoin de dire à drizzle où se trouvent nos fichiers, avec une configuration, qui doit être mise à la racine du projet :

drizzle.config.js
		import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
 
export default defineConfig({
	out: './drizzle',
	// On dit à drizzle où se trouve le schéma de la base de données
	schema: './src/db/schema.js',
	// On dit à drizzle que la base de données est SQLite
	dialect: 'sqlite',
	// On lui donne l'identifiant de la base de données
	dbCredentials: {
		url: process.env.DATABASE_URL
	}
});
	

Initialisation de la base de données

On a créé le schéma dans le code, et maintenant on va l'appliquer à la base de données. Pour ça, on va utiliser la commande :

		npx drizzle-kit push
	

Et voilà, on a créé notre schéma dans la base de données ! On peut vérifier que ça a fonctionné avec la commande :

		npx drizzle-kit studio
	

Qui va vous ouvrir une interface pour gérer la base de données. Vous devriez voir vos tables et les données qui y sont stockées.

Création des routes liées aux tâches

		// Route qui renvoie la liste des tâches
app.get("/tasks", async (c) => {
	// on récupère plusieurs (toutes, ici) tâches
  const tasks = await db.query.tasks.findMany();
 
 // On confirme que tout s'est bien passé et on renvoie les taches
  return c.json({
		status: "success",
		data: tasks
	});
});
 
// Route qui crée une nouvelle tâche
app.post("/tasks", async (c) => {
	// On récupère les données depuis le body
  const { title, description, status } = await c.req.json();
	// On insère la tache dans la BDD
  const task = await db.insert(tasks).values({ title, description, status });
	// On confirme que tout s'est bien passé et on renvoie la tache
  return c.json({
		status: "success",
		data: task
	});
});
 
// Route qui met à jour une tâche
app.put("/tasks/:id", async (c) => {
	// On récupère l'id de la tâche depuis le paramètre de la route
  const { id } = c.req.param();
	// On récupère les données depuis le body
  const { title, description, status } = await c.req.json();
	// On met à jour la tache dans la BDD
  const task = await db
    .update(tasks)
    .set({ title, description, status })
    .where(eq(tasks.id, id));
	// On confirme que tout s'est bien passé et on renvoie la tache
  return c.json({
		status: "success",
		data: task
	});
});