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é.
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.
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
.
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 :
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éesPASSWORD
: le mot de passe de votre utilisateur de base de donnéesPORT
: le port sur lequel s’exécute votre serveur de base de données (3306
pour MySQL et3307
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 :
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).
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.
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 :
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
.
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
.
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
:
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 :
- Les types scalaires (scalar) : regroupant tous les types usuels de la programmation et des bases de données (
String
,Int
,Boolean
…). - Les types de modèles (relations) : indiquant qu’un modèle est lié à un autre.
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
.
enum Role {
USER
ADMIN
}
Désormais, on peut l’utiliser pour typer un champ depuis un modèle avec Role
.
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.
model Post {
// ...
keywords String[]
}
?
: Rend un champ facultatif.
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).
datasource db {
provider = "mysql",
url = env("DATABASE_URL")
}
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.
Attribut | Description | Exemple |
---|---|---|
@id | Définit un champ comme identifiant. | id Int @id |
@default | Définit une valeur par défaut pour un champ. | published Boolean @default(false) |
@unique | Définit une contrainte unique pour ce champ. | email String @unique |
@relation | Dé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]) |
@map | Mappe un champ Prisma avec une colonne de BDD nommée différemment. | firstName String @map("first_name") |
@updatedAt | Stocke automatiquement l’heure à laquelle un enregistrement a été mis à jour pour la dernière fois | updatedAt DateTime @updatedAt |
@ignore | Exclue 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).
Attribut | Description | Exemple |
---|---|---|
@@id | Définit un identifiant à partir de plusieurs champs (on parle d’identifiant composite). | @@id([field1, field2]) |
@@unique | Définit une contrainte unique pour un groupe de champs. | @@unique([field1, field2]) |
@@index | Définit un index sur un groupe de champs. | @@index([field1, field2]) |
@@map | Mappe un modèle Prisma avec une table de BDD nommée différemment. | @@map("users") |
@@ignore | Exclue 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 :
Fonction | Description | Exemple |
---|---|---|
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()) |
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
:
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 :
- Ajouter dans chaque modèle un champ relationnel, typé par le modèle lié. Ici, il s’agit de
profile
etuser
. La relation étant 1-1, les noms de champs sont au singulier. - 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 deId
(ici,userId
). - Ajouter l’attribut
@relation
sur le champ relationnel du modèle possédant la clé étrangère. Ce champ possède un argumentfields
référençant la clé étrangère et un argumentreferences
référençant la clé primaire du modèle opposé.
Relation OneToMany
/ ManyToOne
Voici un exemple de relation OneToMany
/ ManyToOne
:
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 :
- Ajouter dans chaque modèle un champ relationnel, typé par le modèle lié. Ici, il s’agit de
author
etposts
. La relation étant 1-n, un nom de champ est au singulier et l’autre au pluriel (suivi du modificateur de type de liste[]
). - 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 deId
(ici,authorId
). - Ajouter l’attribut
@relation
sur le champ relationnel du modèle possédant la clé étrangère. Ce champ possède un argumentfields
référençant la clé étrangère et un argumentreferences
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 :
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 :
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 :
- 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 deCategoriesOnPosts
. - Ajouter au sein du modèle relationnel 2 champs relationnels typés par les modèles liés. Il s’agit de
post
etcategory
. 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. - 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 deId
(ici,categoryId
etpostId
). - Définir ces modèles comme formant l’identifiant du modèle avec
@@id([postId, categoryId])
. - Ajouter des champs au modèle relationnel si besoin. Ici nous avons ajouté une date d’assignation de catégorie à un post via
assignedAt
. - Ajouter l’attribut
@relation
sur les champ relationnels du modèle. Ces champs possèdent un argumentfields
référençant une clé étrangère et un argumentreferences
référençant la clé primaire du modèle opposé. - 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
etposts
. 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 :
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.