Passer au contenu

Prisma

Prisma est un ORM open-source pour Node.js et TypeScript. Il est utilisé comme une alternative à l’écriture de SQL brut, ou à l’utilisation d’un autre outil d’accès à la base de données tel que les constructeurs de requêtes SQL (comme knex.js) ou les ORM (comme TypeORM et Sequelize). Prisma prend actuellement en charge PostgreSQL, MySQL, SQL Server, SQLite, MongoDB et CockroachDB (Aperçu).

Bien que Prisma puisse être utilisé avec du JavaScript brut, il adopte TypeScript et fournit un niveau de sécurité de type qui va au-delà des garanties d’autres ORM dans l’écosystème TypeScript. Vous pouvez trouver une comparaison approfondie des garanties de sécurité de type de Prisma et TypeORM ici.

Prendre le départ

Dans cette recette, vous apprendrez à démarrer avec NestJS et Prisma à partir de zéro. Vous allez créer une application NestJS d’exemple avec une API REST qui peut lire et écrire des données dans une base de données.

Pour les besoins de ce guide, vous allez utiliser une base de données SQLite pour éviter la surcharge de la configuration d’un serveur de base de données. Notez que vous pouvez toujours suivre ce guide, même si vous utilisez PostgreSQL ou MySQL – vous recevrez des instructions supplémentaires pour utiliser ces bases de données aux bons endroits.

Créer votre projet NestJS

Pour commencer, installez le CLI NestJS et créez votre squelette d’application avec les commandes suivantes :

Fenêtre de terminal
$ 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 également que vous pouvez maintenant exécuter npm start pour démarrer votre application. L’API REST fonctionnant à l’adresse 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 les utilisateurs et les publications.

Configurer Prisma

Commencez par installer le CLI Prisma en tant que dépendance de développement dans votre projet :

Fenêtre de terminal
$ cd hello-prisma
$ npm install prisma --save-dev

Dans les étapes suivantes, nous allons utiliser le CLI Prisma. Comme meilleure pratique, il est recommandé d’invoquer le CLI localement en le préfixant avec npx :

Fenêtre de terminal
$ npx prisma

Si vous utilisez Yarn, vous pouvez installer le CLI Prisma comme suit :

Fenêtre de terminal
$ yarn add prisma --dev

Une fois installé, vous pouvez l’invoquer en le préfixant avec yarn :

Fenêtre de terminal
$ yarn prisma

Maintenant créez votre configuration initiale de Prisma en utilisant la commande init du CLI Prisma :

Fenêtre de terminal
$ npx prisma init

Cette commande crée un nouveau répertoire prisma avec les éléments suivants :

  • schema.prisma: Spécifie votre connexion de 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 vos identifiants de base de données dans un groupe de variables d’environnement

Définir la connexion de base de données

Votre connexion de base de données est configurée dans le bloc datasource dans votre fichier schema.prisma. Par défaut, elle est réglée sur postgresql, mais comme 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 le fichier .env et ajustez la variable d’environnement DATABASE_URL pour qu’elle ressemble à ceci :

Fenêtre de terminal
DATABASE_URL="file:./dev.db"

Assurez-vous que vous avez un ConfigModule configuré, sinon la variable DATABASE_URL ne sera pas récupérée depuis .env.

Les bases de données SQLite sont des fichiers simples ; aucun serveur n’est requis pour utiliser une base de données SQLite. Ainsi, au lieu de configurer une URL de connexion avec un hôte et un port, vous pouvez simplement pointer vers un fichier local qui, dans ce cas, est appelé dev.db. Ce fichier sera créé dans l’étape suivante.

Avec PostgreSQL et MySQL, vous devez configurer l’URL de connexion pour pointer vers le serveur de base de données. Vous pouvez en savoir plus sur le format d’URL de connexion requis 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

Fenêtre de terminal
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA"

Remplacez les espaces réservés écrits en lettres majuscules par vos identifiants de base de données. Notez que si vous n’êtes pas sûr de ce qu’il faut fournir pour l’espace réservé SCHEMA, c’est probablement la valeur par défaut public :

Fenêtre de terminal
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"

Si vous voulez apprendre comment configurer une base de données PostgreSQL, vous pouvez suivre ce guide sur la mise en place 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

Fenêtre de terminal
DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"

Remplacez les espaces réservés écrits en lettres majuscules par vos identifiants de base de données.

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 afin que vous puissiez configurer des fonctionnalités supplémentaires de la base de données sous-jacente ou inclure des commandes supplémentaires, par exemple pour le peuplement.

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 :

Fenêtre de terminal
$ npx prisma migrate dev --name init

Cette commande prisma migrate dev génère des fichiers SQL et les exécute directement contre la base de données. Dans ce cas, les fichiers de migration suivants ont été créés dans le répertoire prisma existant :

Fenêtre de terminal
$ tree prisma
prisma
├── dev.db
├── migrations
└── 20201207100915_init
└── migration.sql
└── schema.prisma

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 Prisma Client

Prisma Client est un client de base de données sécurisé par typage 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 adaptées spécifiquement à vos modèles.

Pour installer Prisma Client dans votre projet, exécutez la commande suivante dans votre terminal :

Fenêtre de terminal
$ npm install @prisma/client

Notez que lors de l’installation, Prisma invoque automatiquement la commande prisma generate pour vous. À l’avenir, vous devez exécuter cette commande après chaque changement apporté à vos modèles Prisma pour mettre à jour votre Prisma Client généré.

Utiliser Prisma Client dans vos services NestJS

Vous pouvez maintenant 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 de l’API.

Lors de la configuration de votre application NestJS, vous souhaiterez masquer l’API de Prisma Client 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 prend en charge l’instanciation de PrismaClient et la connexion à votre base de données.

À l’intérieur du répertoire src, créez un nouveau fichier appelé prisma.service.ts et ajoutez le code suivant :

PrismaService
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}

Ensuite, vous pouvez écrire des services que vous pouvez utiliser pour effectuer des appels à la base de données pour les modèles User et Post à partir de votre schéma Prisma.

Toujours dans le répertoire src, créez un nouveau fichier appelé user.service.ts et ajoutez le code suivant :

UserService
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 comment 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 économisez donc le code répétitif de typage de vos modèles et de création d’interfaces ou de fichiers DTO supplémentaires.

Faites de même pour le modèle Post.

Toujours à l’intérieur du répertoire src, créez un nouveau fichier appelé post.service.ts et ajoutez le code suivant :

PostService
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 { where, data } = 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 enveloppent actuellement les requêtes CRUD disponibles dans Prisma Client. Dans une application réelle, le service serait également l’endroit pour ajouter la logique métier à votre application. Par exemple, vous pourriez avoir une méthode appelée updatePassword à l’intérieur du 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 de l’application.

Implémenter vos routes API REST dans le contrôleur principal de l’application

Enfin, vous allez utiliser 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 allez mettre toutes vos routes dans la classe AppController déjà existante.

Remplacez le contenu du fichier app.controller.ts par le code suivant :

AppController
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 implémente les routes suivantes :

GET

  • /post/:id: Récupérer une seule publication par son id
  • /feed: Récupérer toutes les publications publiées
  • /filtered-posts/:searchString: Filtrer les publications par title ou content

POST

  • /post: Créer une nouvelle publication
    • Body:
      • title: String (obligatoire): Le titre de la publication
      • content: String (optionnel): Le contenu de la publication
      • authorEmail: String (obligatoire): L’email de l’utilisateur qui crée la publication
  • /user: Créer un nouvel utilisateur
    • Body:
      • email: String (obligatoire): L’adresse email de l’utilisateur
      • name: String (optionnel): Le nom de l’utilisateur

PUT

  • /publish/:id: Publier une publication par son id

DELETE

  • /post/:id: Supprimer une publication par son id

Résumé

Dans cette recette, vous avez appris à utiliser Prisma avec NestJS pour mettre en œuvre 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 afin de satisfaire les besoins de données des requêtes entrantes.

Si vous souhaitez en savoir plus sur l’utilisation de NestJS avec Prisma, n’oubliez pas de consulter les ressources suivantes :