Next.js et Prisma : créer une app fullstack

Publié le 22/07/2024 - Ecrit par Antoine Bourin

6 minutes

Prisma est un ORM open-source. Celui-ci permet de simplifier les requêtes vers vos bases de données : migrations, schémas, souscriptions...

Next.js vous offre un contexte de rendu serveur (composants serveurs, API route handler) qui vous permet d'échanger directement avec des ressources back-end, comme Prisma. 

Next.js utiliser une base de données avec Prisma

Nous allons construire une application complète avec Next JS pour la création de l'interface, l'utilisation de server actions et Prisma pour échanger avec notre base de données : insérer et afficher des données.

Mise en place de l'application

Commençons par l'installation de la stack technique de l'application. Next.js, framework React pour la création de notre application web. Prisma permettra d'interfacer notre application avec une base de données.

Installation de Next.js

Pour l'installation de Next.js, vous pouvez utiliser create-next-app. Il s'agit d'un CLI proposé par le framework pour installer l'application avec une configuration minimale, prête à être modifiée.

Exécutez la commande suivante pour créer l'application :

npx create-next-app@latest

Vous serez amené à répondre à un ensemble de questions pour déterminer la configuration que vous souhaitez. Vous pouvez laisser les choix par défaut.

Une fois la nouvelle application installée, accédez au dossier de l'application et lancez le serveur de développement avec la commande suivante : 

npm run dev

Vous devriez pouvoir constater le lancement de votre application en accédant à l'URL http://localhost:3000 :

Démarrage serveur de développement Next.js

Installation de Prisma

Pour commencer à utiliser Prisma dans notre application Next.js, il nous faudra installer le CLI Prisma pour pouvoir par la suite initialiser la librairie et exécuter des migrations. Exécutez la commande la suivante : 

npm install prisma --save-dev

Schéma de données et initialisation de Prisma

Maintenant que Prisma est installé, il nous faut l'initialiser dans notre application.

La commande suivante permet d'initialiser la librairie avec une base de données SQLite.

Nous utiliserons ici SQLite car il s'agit d'un type de base de données léger et embarqué facile à mettre en place. Sachez que Prisma supporte également tout type de base de données comme MySQL, MongoDB, PostgreSQL...
Voir tous les types de base de données disponibles.

npx prisma init --datasource-provider sqlite

Une fois cette commande effectuée, vous constaterez qu'un nouveau répertoire prisma est apparu. A l'intérieur de celui-ci, un fichier schema.prisma. C'est ce fichier que nous modifierons pour construire le schéma de données de notre base.

Pour cet exemple, nous mettrons en place un schéma simple de deux entités : Note et Category. Ouvrez le fichier schema.prisma et éditez le comme suit : 

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model Category {
  id    Int             @id @default(autoincrement())
  name  String
  notes Note[]
}

model Note {
  id          Int         @id @default(autoincrement())
  title       String
  category    Category    @relation(fields: [categoryId], references: [id])
  categoryId  Int
}

Exécution des migrations

Une fois le schéma modifié, nous devons exécuter une commande de migration. 

npx prisma migrate dev --name init

Cette migration permettra de : 

  • Créer la base de données SQLite, en local, sous forme de fichier dev.db
  • Créer le fichier de migration dans le répertoire prisma et exécuter la migration SQL dans la base.
  • Exécute prisma generate, qui elle-même installera le paquet @prisma/client . Ce paquet permettra d'utiliser PrismaClient avec le modèle de base de données déjà appliqué.

Votre base de données est prête !

Utilisation de PrismaClient en singleton

@prisma/client est la librairie de Prisma qui vous permettra par la suite d'interagir avec votre base de données

Spécifiquement pour Next.js, pour éviter tous problèmes en mode de développement, nous devons créer un service pour récupérer une seule instance de cette classe (singleton). Créez le fichier prisma.ts où vous souhaitez dans l'application et insérez le code suivant :

import { PrismaClient } from "@prisma/client";

const prismaClientSingleton = () => {
  return new PrismaClient();
};

declare const globalThis: {
  prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;

const prisma = globalThis.prismaGlobal ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;

Visualisation de la base de données avec Prisma Studio

Vérifiez si votre base de données a bien été initialisée avec Prisma studio en effectuant la commande suivante :

npx prisma studio

Vous pourrez accéder à l'interface grâce à l'URL http://localhost:5555. Vous pouvez également ajouter de nouvelles données depuis l'interface.

Visualisation des données Prisma studio

Interagir avec une base de données avec Next.js et Prisma

Maintenant que tout est prêt pour notre base de données, il est temps d'interagir avec grâce à notre application front-end.

Création de l'interface

Notre premier objectif sera d'ajouter deux formulaires, l'un pour la création d'une catégorie, l'autre pour l'ajout d'une nouvelle note.

Commençons par créer notre formulaire d'ajout de catégorie, CategoryForm :

const CategoryForm = () => {
  return (
    <form>
      <input type="text" name="category-title" placeholder="Catégorie..." />
      <input type="submit" value="Enregistrer" />
    </form>
  );
};

export default CategoryForm;

Celui-ci peut être importé dans notre page d'accueil, page.tsx :

import CategoryForm from "@/components/CategoryForm";
import styles from "./page.module.css";

export default function Home() {
  return (
    <main className={styles.main}>
      <CategoryForm />
    </main>
  );
}

Action serveur et requête Prisma

Il ne nous manque plus qu'à créer l'action serveur (server action) qui permettra de créer la nouvelle catégorie dans la base de données grâce à Prisma :

"use server";

import prisma from "@/prisma";

export const createCategory = async (formData: FormData) => {
  const categoryTitle = formData.get("category-title") as string;

  await prisma.category.create({
    data: {
      name: categoryTitle,
    },
  });
};

Remarquez que cette action reçoit en paramètre les valeurs des champs sous l'objet FormData.

Il ne nous manque plus qu'à modifier le formulaire pour lier l'action dans l'attribut action de la balise form :

import { createCategory } from "@/actions/category";

const CategoryForm = () => {
  return (
    <form action={createCategory}>
      <input type="text" name="category-title" placeholder="Catégorie..." />
      <input type="submit" value="Enregistrer" />
    </form>
  );
};

export default CategoryForm;

Essayez maintenant de créer une nouvelle catégorie sur l'interface. Elle est bien ajoutée en base de données !

Réinitialisation du formulaire

Pour améliorer notre formulaire, je vous propose de reset (vider) les champs de celui-ci lorsqu'un nouvel ajout est effectué.

Pour ce faire, on peut se servir de la référence du composant avec le hook useRef et utiliser la fonction reset :

"use client";

import { createCategory } from "@/actions/category";
import { useRef } from "react";

const CategoryForm = () => {
  const formRef = useRef<HTMLFormElement>(null);

  return (
    <form
      ref={formRef}
      action={async (formData) => {
        await createCategory(formData);
        formRef.current?.reset();
      }}
    >
      <input type="text" name="category-title" placeholder="Catégorie..." />
      <input type="submit" value="Enregistrer" />
    </form>
  );
};

export default CategoryForm;

Le fait d'utiliser le hook useRef requiert que notre composant devienne composant client et donc que la directive "use client" soit ajoutée.

Affichage des données et mise en cache

Maintenant que nos catégories sont stockées en base de données, place à la récupération et à l'affichage. 

La grande force de Next.js réside également dans le fait que vous puissiez choisir, précisément, la politique de cache de vos données.

Récupération des données

Pour récupérer les données des catégories et les afficher sur notre page, rien de plus simple.

Un composant Next.js est par défaut un composant serveur. Cela signifie que celui-ci peut faire appel à des ressources back-end et donc, faire appel à Prisma :

import CategoryForm from "@/components/CategoryForm";
import styles from "./page.module.css";
import prisma from "@/prisma";

export default async function Home() {
  const categories = await prisma.category.findMany();

  return (
    <main className={styles.main}>
      <CategoryForm />
      <h2>Catégories</h2>
      <ul>
        {categories.map((category) => (
          <li key={category.id}>{category.name}</li>
        ))}
      </ul>
    </main>
  );
}

A noter que vous devez rajouter le mot clé async pour votre composant pour qu'il puisse gérer des appels asynchrones comme celui fait vers notre base de données.

Gestion du cache : revalidatePath

Nos catégories sont maintenant bien affichées.

Cependant, vous constaterez qu'en ajoutant une nouvelle catégorie, les données de la page resteront les mêmes. Même en rafraîchissant la page.

Cela est dû au fait qu'une page avec Next est par défaut statique. Les données de celle-ci sont récupérées une seule fois et sont gardées en cache jusqu'à ce qu'elles soient invalidées, ou purgées.

Pour rafraîchir les données affichées lorsqu'une nouvelle catégorie est ajoutée, il nous est possible d'utiliser la fonction revalidatePath.

Modifions donc l'action serveur avec l'appel de cette fonction :

"use server";

import prisma from "@/prisma";
import { revalidatePath } from "next/cache";

export const createCategory = async (formData: FormData) => {
  const categoryTitle = formData.get("category-title") as string;

  await prisma.category.create({
    data: {
      name: categoryTitle,
    },
  });

  revalidatePath("/");
};

Désormais, une fois qu'une catégorie est ajoutée, le cache affecté à la page d'accueil est invalidé

Réessayez d'ajouter une catégorie. Tada ! La nouvelle catégorie s'affiche immédiatement.

Envie d'en apprendre plus sur Next.js ?

Découvrez une formation Next.js complète :

  • Comprendre et créer un système de routage complet
  • Créer des routes statiques et dynamique
  • Maîtriser le rendu serveur et client
  • Utiliser tous les atouts majeurs proposés par le framework

Plus de 35 heures de contenu vidéo

Plus de 80 cours écrits