Prisma
Prisma est un ORM open-source pour Node.js et TypeScript. Il est utilisé comme alternative à l'écriture de SQL simple, ou à l'utilisation d'un autre outil d'accès à la base de données comme les constructeurs de requêtes SQL (comme knex.js) ou les ORM (comme TypeORM et Sequelize). Prisma supporte actuellement PostgreSQL, MySQL, SQL Server, SQLite, MongoDB et CockroachDB (Voir les bases de données supportées).
Bien que Prisma puisse être utilisé avec du JavaScript simple, il embrasse TypeScript et fournit un niveau de sécurité de type qui va au-delà des garanties d'autres ORMs dans l'écosystème TypeScript. Vous pouvez trouver une comparaison approfondie des garanties de sécurité de type de Prisma et TypeORM ici.
Note Si vous voulez avoir un aperçu rapide du fonctionnement de Prisma, vous pouvez suivre le Quickstart ou lire l'Introduction dans la documentation. Il existe également des exemples prêts à l'emploi pour REST et GraphQL dans le répertoire prisma-examples
.
Pour commencer#
Dans cette recette, vous apprendrez à démarrer avec NestJS et Prisma à partir de zéro. Vous allez construire un exemple d'application NestJS avec une API REST qui peut lire et écrire des données dans une base de données.
Dans le cadre de ce guide, vous utiliserez une base de données SQLite pour éviter de devoir configurer un serveur de base de données. Notez que vous pouvez toujours suivre ce guide, même si vous utilisez PostgreSQL ou MySQL - vous trouverez des instructions supplémentaires pour l'utilisation de ces bases de données aux bons endroits.
Note Si vous avez déjà un projet existant et que vous envisagez de migrer vers Prisma, vous pouvez suivre le guide pour ajouter Prisma à un projet existant. Si vous migrez de TypeORM, vous pouvez lire le guide Migrating from TypeORM to Prisma.
Créez votre projet NestJS#
Pour commencer, installez la CLI NestJS et créez votre squelette d'application avec les commandes suivantes :
$ npm install -g @nestjs/cli
$ nest new hello-prisma
Voir la page Premiers pas pour en savoir plus sur les fichiers de projet créés par cette commande. Notez aussi que vous pouvez maintenant lancer npm start
pour démarrer votre application. L'API REST fonctionnant à http://localhost:3000/
sert actuellement une seule route qui est implémentée dans src/app.controller.ts
. Au cours de ce guide, vous allez implémenter des routes supplémentaires pour stocker et récupérer des données sur users et posts.
Mise en place de Prisma#
Commencez par installer la CLI Prisma en tant que dépendance de développement dans votre projet :
$ cd hello-prisma
$ npm install prisma --save-dev
Dans les étapes suivantes, nous utiliserons la CLI Prisma. Comme meilleure pratique, il est recommandé d'invoquer la CLI localement en la préfixant avec npx
:
$ npx prisma
Développer si vous utilisez Yarn
Si vous utilisez Yarn, vous pouvez installer la CLI Prisma comme suit :
$ yarn add prisma --dev
Une fois installé, vous pouvez l'invoquer en le préfixant avec yarn
:
$ yarn prisma
Maintenant, créez votre configuration initiale de Prisma en utilisant la commande init
de la CLI de Prisma :
$ npx prisma init
Cette commande crée un nouveau répertoire prisma
avec le contenu suivant :
schema.prisma
: Spécifie la connexion à la base de données et contient le schéma de la base de données..env
: Un fichier dotenv, généralement utilisé pour stocker les informations d'identification de la base de données dans un groupe de variables d'environnement.
Définir la connexion à la base de données#
Votre connexion à la base de données est configurée dans le bloc datasource
de votre fichier schema.prisma
. Par défaut, elle est définie à postgresql
, mais puisque vous utilisez une base de données SQLite dans ce guide, vous devez ajuster le champ provider
du bloc datasource
à sqlite
:
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
Maintenant, ouvrez .env
et ajustez la variable d'environnement DATABASE_URL
pour qu'elle ressemble à ce qui suit :
DATABASE_URL="file:./dev.db"
Assurez-vous d'avoir un ConfigModule configuré, sinon la variable DATABASE_URL
ne sera pas récupérée dans .env
.
Les bases de données SQLite sont de simples fichiers ; aucun serveur n'est nécessaire pour utiliser une base de données SQLite. Ainsi, au lieu de configurer une URL de connexion avec un host et un port, vous pouvez simplement la faire pointer vers un fichier local qui, dans ce cas, s'appelle dev.db
. Ce fichier sera créé dans l'étape suivante.
Développez si vous utilisez PostgreSQL, MySQL, MsSQL ou Azure SQL
Avec PostgreSQL et MySQL, vous devez configurer l'URL de connexion pour qu'elle pointe vers le serveur de base de données. Vous pouvez en savoir plus sur le format requis de l'URL de connexion ici.
PostgreSQL
Si vous utilisez PostgreSQL, vous devez ajuster les fichiers schema.prisma
et .env
comme suit :
schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
.env
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA"
Remplacez les caractères de remplacement en majuscules par les informations d'identification de votre base de données. Notez que si vous n'êtes pas sûr de ce que vous devez fournir pour l'espace réservé SCHEMA
, c'est très probablement la valeur par défaut public
:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"
Si vous souhaitez apprendre à configurer une base de données PostgreSQL, vous pouvez suivre ce guide sur la configuration d'une base de données PostgreSQL gratuite sur Heroku.
MySQL
Si vous utilisez MySQL, vous devez ajuster les fichiers schema.prisma
et .env
comme suit :
schema.prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
.env
DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"
Remplacez les caractères de remplacement écrits en majuscules par les informations d'identification de votre base de données.
Microsoft SQL Server / Azure SQL Server
Si vous utilisez Microsoft SQL Server ou Azure SQL Server, vous devez ajuster les fichiers schema.prisma
et .env
comme suit :
schema.prisma
datasource db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
.env
Remplacez les caractères de remplacement en majuscules par les informations d'identification de votre base de données. Notez que si vous n'êtes pas sûr de ce que vous devez fournir pour l'espace réservé encrypt
, c'est très probablement la valeur par défaut true
:
DATABASE_URL="sqlserver://HOST:PORT;database=DATABASE;user=USER;password=PASSWORD;encrypt=true"
Créer deux tables de base de données avec Prisma Migrate#
Dans cette section, vous allez créer deux nouvelles tables dans votre base de données en utilisant Prisma Migrate. Prisma Migrate génère des fichiers de migration SQL pour votre définition de modèle de données déclaratif dans le schéma Prisma. Ces fichiers de migration sont entièrement personnalisables, ce qui vous permet de configurer toutes les fonctionnalités supplémentaires de la base de données sous-jacente ou d'inclure des commandes supplémentaires, par exemple pour l'ensemencement.
Ajoutez les deux modèles suivants à votre fichier schema.prisma
:
model User {
id Int @default(autoincrement()) @id
email String @unique
name String?
posts Post[]
}
model Post {
id Int @default(autoincrement()) @id
title String
content String?
published Boolean? @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
Avec vos modèles Prisma en place, vous pouvez générer vos fichiers de migration SQL et les exécuter contre la base de données. Exécutez les commandes suivantes dans votre terminal :
$ npx prisma migrate dev --name init
Cette commande prisma migrate dev
génère des fichiers SQL et les exécute directement sur la base de données. Dans ce cas, les fichiers de migration suivants ont été créés dans le répertoire prisma
existant :
$ tree prisma
prisma
├── dev.db
├── migrations
│ └── 20201207100915_init
│ └── migration.sql
└── schema.prisma
Développer pour visualiser les instructions SQL générées
Les tables suivantes ont été créées dans votre base de données SQLite :
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"name" TEXT
);
-- CreateTable
CREATE TABLE "Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"content" TEXT,
"published" BOOLEAN DEFAULT false,
"authorId" INTEGER,
FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
Installer et générer le Prisma Client#
Prisma Client est un client de base de données à sécurité de type qui est généré à partir de votre définition de modèle Prisma. Grâce à cette approche, Prisma Client peut exposer des opérations CRUD qui sont taillées spécifiquement pour vos modèles.
Pour installer Prisma Client dans votre projet, lancez la commande suivante dans votre terminal :
$ npm install @prisma/client
Notez que pendant l'installation, Prisma invoque automatiquement la commande prisma generate
pour vous. Dans le futur, vous devrez lancer cette commande après chaque changement dans vos modèles Prisma pour mettre à jour votre client Prisma généré.
Note La commandeprisma generate
lit votre schéma Prisma et met à jour la bibliothèque Prisma Client générée dansnode_modules/@prisma/client
.
Utiliser Prisma Client dans vos services NestJS#
Vous êtes maintenant en mesure d'envoyer des requêtes de base de données avec Prisma Client. Si vous souhaitez en savoir plus sur la création de requêtes avec Prisma Client, consultez la documentation API.
Lors de la mise en place de votre application NestJS, vous voudrez abstraire l'API du client Prisma pour les requêtes de base de données à l'intérieur d'un service. Pour commencer, vous pouvez créer un nouveau PrismaService
qui se charge d'instancier PrismaClient
et de se connecter à votre base de données.
Dans le répertoire src
, créez un nouveau fichier appelé prisma.service.ts
et ajoutez-y le code suivant :
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
Note Le onModuleInit
est optionnel - si vous l'omettez, Prisma se connectera paresseusement lors de son premier appel à la base de données.
Ensuite, vous pouvez écrire des services que vous pouvez utiliser pour faire des appels à la base de données pour les modèles User
et Post
de votre schéma Prisma.
Toujours dans le répertoire src
, créez un nouveau fichier appelé user.service.ts
et ajoutez-y le code suivant :
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User, Prisma } from '@prisma/client';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async user(
userWhereUniqueInput: Prisma.UserWhereUniqueInput,
): Promise<User | null> {
return this.prisma.user.findUnique({
where: userWhereUniqueInput,
});
}
async users(params: {
skip?: number;
take?: number;
cursor?: Prisma.UserWhereUniqueInput;
where?: Prisma.UserWhereInput;
orderBy?: Prisma.UserOrderByWithRelationInput;
}): Promise<User[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.user.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async createUser(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({
data,
});
}
async updateUser(params: {
where: Prisma.UserWhereUniqueInput;
data: Prisma.UserUpdateInput;
}): Promise<User> {
const { where, data } = params;
return this.prisma.user.update({
data,
where,
});
}
async deleteUser(where: Prisma.UserWhereUniqueInput): Promise<User> {
return this.prisma.user.delete({
where,
});
}
}
Remarquez que vous utilisez les types générés par Prisma Client pour vous assurer que les méthodes exposées par votre service sont correctement typées. Vous évitez ainsi de taper vos modèles et de créer des fichiers d'interface ou de DTO supplémentaires.
Maintenant faites la même chose pour le modèle Post
.
Toujours dans le répertoire src
, créez un nouveau fichier appelé post.service.ts
et ajoutez-y le code suivant :
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { Post, Prisma } from '@prisma/client';
@Injectable()
export class PostService {
constructor(private prisma: PrismaService) {}
async post(
postWhereUniqueInput: Prisma.PostWhereUniqueInput,
): Promise<Post | null> {
return this.prisma.post.findUnique({
where: postWhereUniqueInput,
});
}
async posts(params: {
skip?: number;
take?: number;
cursor?: Prisma.PostWhereUniqueInput;
where?: Prisma.PostWhereInput;
orderBy?: Prisma.PostOrderByWithRelationInput;
}): Promise<Post[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.post.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async createPost(data: Prisma.PostCreateInput): Promise<Post> {
return this.prisma.post.create({
data,
});
}
async updatePost(params: {
where: Prisma.PostWhereUniqueInput;
data: Prisma.PostUpdateInput;
}): Promise<Post> {
const { data, where } = params;
return this.prisma.post.update({
data,
where,
});
}
async deletePost(where: Prisma.PostWhereUniqueInput): Promise<Post> {
return this.prisma.post.delete({
where,
});
}
}
Vos UserService
et PostService
contiennent actuellement les requêtes CRUD disponibles dans Prisma Client. Dans une application réelle, le service serait également l'endroit où ajouter de la logique métier à votre application. Par exemple, vous pourriez avoir une méthode appelée updatePassword
dans le UserService
qui serait responsable de la mise à jour du mot de passe d'un utilisateur.
N'oubliez pas d'enregistrer les nouveaux services dans le module d'application.
Implémenter les routes de l'API REST dans le contrôleur principal de l'application
Enfin, vous utiliserez les services que vous avez créés dans les sections précédentes pour implémenter les différentes routes de votre application. Pour les besoins de ce guide, vous placerez toutes vos routes dans la classe AppController
déjà existante.
Remplacez le contenu du fichier app.controller.ts
par le code suivant :
import {
Controller,
Get,
Param,
Post,
Body,
Put,
Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { PostService } from './post.service';
import { User as UserModel, Post as PostModel } from '@prisma/client';
@Controller()
export class AppController {
constructor(
private readonly userService: UserService,
private readonly postService: PostService,
) {}
@Get('post/:id')
async getPostById(@Param('id') id: string): Promise<PostModel> {
return this.postService.post({ id: Number(id) });
}
@Get('feed')
async getPublishedPosts(): Promise<PostModel[]> {
return this.postService.posts({
where: { published: true },
});
}
@Get('filtered-posts/:searchString')
async getFilteredPosts(
@Param('searchString') searchString: string,
): Promise<PostModel[]> {
return this.postService.posts({
where: {
OR: [
{
title: { contains: searchString },
},
{
content: { contains: searchString },
},
],
},
});
}
@Post('post')
async createDraft(
@Body() postData: { title: string; content?: string; authorEmail: string },
): Promise<PostModel> {
const { title, content, authorEmail } = postData;
return this.postService.createPost({
title,
content,
author: {
connect: { email: authorEmail },
},
});
}
@Post('user')
async signupUser(
@Body() userData: { name?: string; email: string },
): Promise<UserModel> {
return this.userService.createUser(userData);
}
@Put('publish/:id')
async publishPost(@Param('id') id: string): Promise<PostModel> {
return this.postService.updatePost({
where: { id: Number(id) },
data: { published: true },
});
}
@Delete('post/:id')
async deletePost(@Param('id') id: string): Promise<PostModel> {
return this.postService.deletePost({ id: Number(id) });
}
}
Ce contrôleur met en œuvre les routes suivantes :
GET
/post/:id
: Récupèrer un seul message par sonid
./feed
: Récupèrer tous les articles _publiés/filter-posts/:searchString
: Filtrer les messages partitle
oucontent
POST
/post
: Créer un nouveau message- Corps :
title: String
(obligatoire) : Le titre de l'articlecontent: String
(facultatif) : Le contenu du messageauthorEmail: String
(obligatoire) : L'adresse électronique de l'utilisateur qui crée le message
- Corps :
/user
: Créer un nouvel utilisateur- Corps :
email: String
(obligatoire) : L'adresse électronique de l'utilisateurname: String
(facultatif) : Le nom de l'utilisateur
- Corps :
PUT
/publish/:id
: Publier un message par sonid
DELETE
/post/:id
: Supprimer un message par sonid
Résumé#
Dans cette recette, vous avez appris à utiliser Prisma avec NestJS pour implémenter une API REST. Le contrôleur qui implémente les routes de l'API appelle un PrismaService
qui à son tour utilise Prisma Client pour envoyer des requêtes à une base de données pour répondre aux besoins de données des requêtes entrantes.
Si vous souhaitez en savoir plus sur l'utilisation de NestJS avec Prisma, n'hésitez pas à consulter les ressources suivantes (en anglais) :