Apprendre Prisma : Configuration du Schéma Prisma

prisma.schema est le fichier de configuration principal de Prisma. Il permet de définir la source de données, le modèle de données et le générateur utilisé.

Icône de calendrier
Intermédiaire
4 chapitres

Détaillons ensemble les informations contenues dans le fichier de configuration 📄 prisma.schema.

Source de données

Il s’agit dans un premier temps de spécifier les détails de la source de données à laquelle Prisma doit se connecter.

Connecteur

Cela passe par la définition d’un connecteur de base de données, on parle de provider, faisant référence au SGBD à utiliser. Nous allons dans notre cas travailler avec MySQL.

prisma.schema
copié !
datasource db {
	provider = "mysql",
	// ...
}

Prisma propose les connecteurs suivants : postgresql, mysql, sqlite, mondodb, cockroachdb, sqlserver.

La suite de cette formation sera axée sur l’exploitation de bases de données relationnelles. Les scénarios avec MongoDB ne seront donc pas traités.

URL de connexion à la BDD

L’URL de connexion à la base de données est à définir dans la propriété url. On remarque que la valeur est récupérée au sein de la variable d’environnement DATABASE_URL.

prisma.schema
copié !
datasource db {
	// ...
	url = env("DATABASE_URL")
}

Spécifions alors l’URL de connexion à notre base de données au sein du fichier 📄 .env.

Le format de l’URL de connexion à la base de données dépend généralement du type de base de données que vous utilisez. Pour le SGBD MySQL, il se présente sous cette forme :

copié !
SGBD://USER:PASSWORD@HOST:PORT/DATABASE
  • SGBD : le Système de Gestion de Base de Données à utiliser (mysql, postgresql…)
  • USER : le nom de l’utilisateur de votre base de données
  • PASSWORD : le mot de passe de votre utilisateur de base de données
  • PORT : le port sur lequel s’exécute votre serveur de base de données (3306 pour MySQL et 3307 pour MariaDB)
  • DATABASE : le nom de la base de données

Je pourrais par exemple la compléter avec les informations suivantes pour le SGBD MariaDB :

prisma.schema
copié !
DATABASE_URL="mysql://root:@localhost:3307/laconsole_prisma_demo"

Générateur

Le rôle du générateur est de déterminer quels assets seront générés lors de l’exécution de la commande prisma generate (abordée plus tard).

prisma.schema
copié !
generator client {
	provider = "prisma-client-js"
}

provider est par défaut défini à prisma-client-js, ce qui nous convient parfaitement.

Modèle de données

Il s’agit de définir les entités manipulées au sein de notre application, ainsi que leurs caractéristiques. Il peut s’agit de User, Article, Post, Product, Comment, Tag

Pour cela, il est important de maîtriser 4 concepts fondamentaux : les modèles, les champs, les attributs et les relations.

Modèles

Au sein de notre application, les modèles représentent les tables.

On définit un modèle de données avec le mot-clé model suivi du nom du modèle. Un nom de modèle Prisma doit correspondre au nom de la table en base de données.

Les conventions de dénomination des modèles Prisma sont en PascalCase et en respectant la forme singulière.

prisma.schema
copié !
model User {
	// Fields
}

model Post {
	// Fields
}
Possibilité d'utiliser la snake_case

Si votre table est nommée selon la convention de dénomination la plus répandue, à savoir en snake_case et en respectant la forme plurielle, vous pouvez nommer votre modèle ainsi :

prisma.schema
copié !
model users {
	// Fields
}

model posts {
	// Fields
}

Cependant, sachez que vous avez toujours la possibilité d’adhérer à la convention Prisma, sans renommer vos tables en base de données à l’aide de l’attribut @@map.

prisma.schema
copié !
model User {
	// Fields
	@@map("users")
}

Champs

Les propriétés d’un modèle sont appelées champs.

Un champ est défini par son nom, son type et des attributs (optionnels).

Nom

Le nom d’un champ décrit explicitement la donnée qu’il stocke. On l’écrit en camelCase.

prisma.schema
copié !
model User {
	id
	email
	firstName
	lastName
}

model Post {
	id
	createdAt
	title
	description
	content
	published
}

model Category {
	id
	name
}

De la même manière que pour un nom de modèle, un nom de champ Prisma doit correspondre au nom de la colonne en base de données.

En cas d’utilisation de conventions de nommage différentes entre le modèle Prisma et la BDD, il est possible d’utiliser l’attribut @map :

prisma.schema
copié !
model User {
	// ...
	firstName  String  @map("first_name")
}

Type

Le type d’un champ détermine la structure de donnée qu’il contient. On les écrit en PascalCase juste après le nom du champ.

On distingue 2 grandes catégories de types de données :

  1. Les types scalaires (scalar) : regroupant tous les types usuels de la programmation et des bases de données (String, Int, Boolean…).
  2. Les types de modèles (relations) : indiquant qu’un modèle est lié à un autre.
prisma.schema
copié !
model User {
	id         Int
	email      String
	firstName  String
	lastName   String
}

model Post {
	id           Int
	createdAt    DateTime
	title        String
	description  String
	content      String
	published    Boolean
}

model Category {
	id    Int
	name  String
}
Enumération

Une énumération est une liste de valeurs qui peut être définie par le mot-clé enum, suivi du nom de l’énumération.

On applique la même convention de nommage que pour les modèles ; à savoir en PascalCase, et en respectant la forme singulière. Les valeurs sont quant à elles à écrire en UPPERCASE.

prisma.schema
copié !
enum Role {
	USER
	ADMIN
}

Désormais, on peut l’utiliser pour typer un champ depuis un modèle avec Role.

prisma.schema
copié !
model User {
	// ...
	role  Role
}
Modificateurs de type

Les modificateurs de type permettent de modifier le caractère d’un type de champ. Au nombre de deux, on les écrit à la fin du type.

[] : Transforme un champ en liste.

prisma.schema
copié !
model Post {
	// ...
	keywords  String[]
}

? : Rend un champ facultatif.

prisma.schema
copié !
model User {
	// ...
	firstName  String?
	lastName   String?
}
Attributs de type de base de données natifs

Chaque type Prisma (String, Int…) est par défaut mappé à un type de base de données sous-jacent, relatif au connecteur utilisé. Le connecteur MySQL mappe par exemple String en varchar(191).

Il est néanmoins possible de modifier cela en faisant un usage complémentaire de type de données natif, déterminant quel type natif spécifique doit être créé dans la base de données.

Ces attributs particuliers doivent être écrits en PascalCase et être préfixés de @db. (db correspondant au nom de votre source de données).

prisma.schema
copié !
datasource db {
	provider = "mysql",
	url = env("DATABASE_URL")
}
prisma.schema
copié !
model Post {
	id     Int     @id
	// ...
	title  String  @db.Text
}

Vous trouverez sur la documentation officielle, la liste des attributs de type de base de données natifs.

Attributs (optionnels)

Les attributs permettent de modifier le comportement d’un champ ou d’un modèle (aussi appelé “block”). En fonction de leur portée, on les distinguera par leur syntaxe :

Attributs de champ avec @

Les attributs de champ sont préfixés du caractère @ et permettent de modifier le comportement d’un champ spécifique.

Un attribut de champ se note à la suite du type du champ.

AttributDescriptionExemple
@idDéfinit un champ comme identifiant.id Int @id
@defaultDéfinit une valeur par défaut pour un champ.published Boolean @default(false)
@uniqueDéfinit une contrainte unique pour ce champ.email String @unique
@relationDéfinit les meta-informations pour construire une relation. Nous étudierons ce point en détails par la suite.author User @relation(fields: [authorId], references: [id])
@mapMappe un champ Prisma avec une colonne de BDD nommée différemment.firstName String @map("first_name")
@updatedAtStocke automatiquement l’heure à laquelle un enregistrement a été mis à jour pour la dernière foisupdatedAt DateTime @updatedAt
@ignoreExclue un champ du modèle. Ce champ ne sera par la suite pas traité par le client Prisma.email String @ignore
Attributs de block avec @@

Les attributs de block sont préfixés du double caractère @@ et permettent de modifier le comportement d’un modèle / d’un groupe de champs.

Un attribut de block se note à la fin du modèle (après la liste des champs).

AttributDescriptionExemple
@@idDéfinit un identifiant à partir de plusieurs champs (on parle d’identifiant composite).@@id([field1, field2])
@@uniqueDéfinit une contrainte unique pour un groupe de champs.@@unique([field1, field2])
@@indexDéfinit un index sur un groupe de champs.@@index([field1, field2])
@@mapMappe un modèle Prisma avec une table de BDD nommée différemment.@@map("users")
@@ignoreExclue un modèle. Ce modèle ne sera par la suite pas traité par le client Prisma.@@ignore
Fonctions

Prisma bénéficie aussi de fonctions qui peuvent être utilisées pour spécifier des valeurs par défaut sur les champs d’un modèle. Voici les plus utiles d’entre elles :

FonctionDescriptionExemple
autoincrement()Génère un identifiant auto-incrémenté pour les BDD relationnelles.id Int @id @default(autoincrement())
uuid()Génère un identifiant unique basé sur la spécification UUID.id String @id @default(uuid())
now()Définissez un horodatage de l’heure à laquelle un enregistrement est créé.createdAt DateTime @default(now())
prisma.schema
copié !
model User {
	id           Int       @id @default(autoincrement())
	email        String    @unique
	firstName    String?
	lastName     String?
	role         Role      @default(USER)
}

model Profile {
	id           Int       @id @default(autoincrement())
	bio          String
}

model Post {
	id           Int       @id @default(autoincrement())
	createdAt    DateTime  @default(now())
	title        String
	description  String
	content      String
	published    Boolean   @default(false)
}

model Category {
	id           Int       @id @default(autoincrement())
	name         String
}

enum Role {
	USER
	ADMIN
}

Relations

Une relation consiste à connecter deux modèles Prisma.

Pour implémenter une relation au sein de nos modèles de données, il faut d’abord définir la multiplicité de la relation.

Relation OneToOne

Voici un exemple de relation OneToOne :

prisma.schema
copié !
model User {
	id       Int       @id @default(autoincrement())
	// ...
	profile  Profile?
}

model Profile {
	id      Int   @id @default(autoincrement())
	// ...
	user    User  @relation(fields: [userId], references: [id])
	userId  Int   @unique
}

Pour implémenter une relation OneToOne, il faut :

  1. Ajouter dans chaque modèle un champ relationnel, typé par le modèle lié. Ici, il s’agit de profile et user. La relation étant 1-1, les noms de champs sont au singulier.
  2. Ajouter dans un des deux modèles un attribut scalaire (Int @unique) représentant la clé étrangère de la relation. On le nomme du même nom que le modèle qu’il référence, suivi de Id (ici, userId).
  3. Ajouter l’attribut @relation sur le champ relationnel du modèle possédant la clé étrangère. Ce champ possède un argument fields référençant la clé étrangère et un argument references référençant la clé primaire du modèle opposé.

Relation OneToMany / ManyToOne

Voici un exemple de relation OneToMany / ManyToOne :

prisma.schema
copié !
model User {
	id     Int     @id @default(autoincrement())
	// ...
	posts  Post[]
}

model Post {
	id        Int   @id @default(autoincrement())
	// ...
	author    User  @relation(fields: [authorId], references: [id])
	authorId  Int
}

Pour implémenter une relation OneToMany / ManyToOne, il faut :

  1. Ajouter dans chaque modèle un champ relationnel, typé par le modèle lié. Ici, il s’agit de author et posts. La relation étant 1-n, un nom de champ est au singulier et l’autre au pluriel (suivi du modificateur de type de liste []).
  2. Ajouter dans le modèle se situant côté Many de la relation, un attribut scalaire (Int) représentant la clé étrangère de la relation. On le nomme du même nom que le modèle qu’il référence, suivi de Id (ici, authorId).
  3. Ajouter l’attribut @relation sur le champ relationnel du modèle possédant la clé étrangère. Ce champ possède un argument fields référençant la clé étrangère et un argument references référençant la clé primaire du modèle opposé.

Relation ManyToMany

Au sein d’un schéma Prisma, on distingue 2 types de relations ManyToMany :

  • Les relations implicites : constituées de 2 modèles ; aucun modèle n’est créé pour la table de jointure qui est alors implicite.
  • Les relations explicites : constituées de 3 modèles ; on créé explicitement un modèle relationnel supplémentaire correspondant à la table de jointure. Cette approche est nécessaire si des champs additionnels sont à créer dans la table de jointure, comme par exemple une date associée à la relation.

Voici un exemple de relation ManyToMany implicite :

prisma.schema
copié !
model Post {
	id         Int        @id @default(autoincrement())
	// ...
	categories Category[]
}

model Category {
	id    Int    @id @default(autoincrement())
	// ...
	posts Post[]
}

Pour implémenter une relation ManyToMany implicite, il faut simplement ajouter dans chaque modèle un champ relationnel, typé par l’entité liée. Ici, il s’agit de categories et posts. La relation étant n-n, les noms de champs sont au pluriel (suivi du modificateur de type de liste []).

Voici un exemple de relation ManyToMany explicite :

prisma.schema
copié !
model Post {
	id          Int                  @id @default(autoincrement())
	// ...
	categories  CategoriesOnPosts[]
}

model Category {
	id     Int                  @id @default(autoincrement())
	// ...
	posts  CategoriesOnPosts[]
}

model CategoriesOnPosts {
	post        Post      @relation(fields: [postId], references: [id])
	postId      Int
	category    Category  @relation(fields: [categoryId], references: [id])
	categoryId  Int
	assignedAt  DateTime  @default(now())
	@@id([postId, categoryId])
}

Pour implémenter une relation ManyToMany explicite, il faut :

  1. Créer un modèle relationnel représentant la table de jointure. Ce modèle se nomme par convention : {Modèle A au pluriel}On{Modèle B au pluriel}. Ici, il s’agit de CategoriesOnPosts.
  2. Ajouter au sein du modèle relationnel 2 champs relationnels typés par les modèles liés. Il s’agit de post et category. Au sein d’une table de jointure, les relations vers les tables liées sont 1-1, les noms de champs sont donc au singulier.
  3. Ajouter dans le modèle relationnel, 2 attributs scalaire (Int @unique) pour les clés étrangères de la relation. On le nomme du même nom que le modèle qu’il référence, suivi de Id (ici, categoryId et postId).
  4. Définir ces modèles comme formant l’identifiant du modèle avec @@id([postId, categoryId]).
  5. Ajouter des champs au modèle relationnel si besoin. Ici nous avons ajouté une date d’assignation de catégorie à un post via assignedAt.
  6. Ajouter l’attribut @relation sur les champ relationnels du modèle. Ces champs possèdent un argument fields référençant une clé étrangère et un argument references référençant la clé primaire du modèle opposé.
  7. Ajouter au sein des 2 modèles en relation, des attributs relationnels typés par le modèle relationnel. Il s’agit ici de categories et posts. Les relations vers la table de jointure sont n-n, les noms de champs sont donc au pluriel (suivi du modificateur de type de liste []).

Voici comment nous avons intégré des relations dans notre fichier 📄 schema.prisma initial :

prisma.schema
copié !
model User {
	id         Int      @id @default(autoincrement())
	email      String   @unique
	firstName  String?
	lastName   String?
	role       Role     @default(USER)
	posts      Post[]
	profile    Profile?
}

model Profile {
	id      Int     @id @default(autoincrement())
	bio     String
	user    User    @relation(fields: [userId], references: [id])
	userId  Int     @unique
}

model Post {
	id           Int         @id @default(autoincrement())
	createdAt    DateTime    @default(now())
	title        String
	description  String
	content      String
	published    Boolean     @default(false)
	author       User        @relation(fields: [authorId], references: [id])
	authorId     Int
	categories   Category[]
}

model Category {
	id     Int     @id @default(autoincrement())
	name   String
	posts  Post[]
}

enum Role {
	USER
	ADMIN
}
Cas particulier : les associations réflexives

Parfois il est utile de réaliser une relation d’un modèle vers lui-même. Par exemple sur un réseau social, un utilisateur peut « suivre » un autre utilisateur. On parle d’associations réflexives.

Vous trouverez sur la documentation officielle toutes les informations pour créer une association réflexive.