Un guide pour créer des API GraphQL avec NestJS

Vous apprendrez comment fonctionne GraphQL, comment utiliser GraphQL avec NestJS et comment utiliser TypeORM avec NestJS pour notre base de données Postgres.
Dans cet article, nous allons créer une API GraphQL en utilisant NestJS et un PostgreSQL base de données. Vous apprendrez comment fonctionne GraphQL, comment utiliser GraphQL avec NestJS et comment utiliser TypeORM avec NestJS pour notre base de données Postgres.
Nous allons créer une API CRUD de film simple pour faire tout cela. Cependant, commençons par comprendre les technologies que nous utiliserons.
Qu’est-ce que GraphQL ?
Il s’agit d’un langage de requête et de manipulation de données pour les API qui permet au frontend de demander exactement ce dont il a besoin.
Grâce au système de types de GraphQL, nous pouvons facilement décrire un schéma pour nos données sur le backend. Pour cette raison, le frontend décrit simplement les données qu’il souhaite dans un format simple qui reflète la réponse JSON attendue. En tant qu’alternative à l’API REST, GraphQL est plus efficace.
Avec GraphQL, nous n’avons pas besoin de plusieurs URL pour récupérer différentes données ; nous décrivons simplement ce que nous voulons dans une seule demande.
Qu’est-ce que NestJS ?
NestJS est un framework Node.js qui utilise une architecture modulaire pour créer des applications côté serveur évolutives.
NestJS est livré avec des composants et des modèles prédéfinis que nous pouvons simplement brancher pour créer notre application. L’un de ces composants prédéfinis est le @nestjs/graphql
module que nous utiliserons. Avec NestJS, les développeurs bénéficient d’une prise en charge intégrée de diverses fonctionnalités afin de ne pas constamment réinventer la roue.
Configuration du projet
Exécutez la commande suivante pour installer la CLI NestJS si vous ne l’avez pas déjà fait.
npm i -g @nestjs/cli
Ensuite, exécutez la commande suivante pour utiliser la CLI NestJS afin de créer un nouveau projet :
nest new movies-graphql-api
Ensuite, vous serez invité à choisir un gestionnaire de packages. je vais sélectionner npm
.
Une fois l’installation terminée, vous aurez un nouveau dossier nommé movies-graphql-api
. Ce dossier est notre répertoire de projet ; vous pouvez y naviguer en exécutant cette commande :
cd movies-graphql-api
Créons ensuite les fichiers nécessaires pour notre module vidéo, y compris le résolveur, le service, l’entité et le modèle. Exécutez la commande suivante pour ce faire :
nest generate module movie
nest generate service movie
nest generate resolver movie
Vous devriez voir un dossier appelé movie
dans le src
répertoire lorsque vous avez terminé. Ouvrez-le et créez deux autres fichiers appelés movie.model.ts
et movie.entity.ts
.
Ton src
Le répertoire devrait maintenant ressembler à ceci.
Cela fait, exécutez cette commande pour installer les dépendances dont nous avons besoin :
npm install @nestjs/graphql @nestjs/apollo @apollo/server graphql @nestjs/typeorm typeorm pg
Nous utiliserons @nestjs/graphql
, graphql-tools
et graphql
pour l’intégration GraphQL. Alors que @nestjs/typeorm
, typeorm
et pg
sera utilisé pour l’intégration de la base de données TypeORM et Postgres.
Configurer TypeORM et PostgreSQL
Vous devrez télécharger et installer Postgres si vous ne l’avez pas déjà fait. Ouvrez le psql
shell, remplissez les invites et créez une base de données appelée movies_db
.
Pour configurer TypeORM pour qu’il se connecte à notre base de données Postgres locale, ajoutez le code suivant au tableau importations dans le fichier AppModule
au sein de votre app.module.ts
déposer:
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'movies_db',
autoLoadEntities: true,
synchronize: true,
}),
Assurez-vous de renseigner votre nom d’utilisateur et votre mot de passe réels pour la base de données.
Configurer GraphQLModule
Ensuite, nous devons importer le GraphQL
module dans notre AppModule
. Ajoutez le code suivant au tableau importations :
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
Pour configurer le GraphQLModule
nous passons un objet options au forRoot()
méthode statique. Ici, le driver
L’option est utilisée pour indiquer l’implémentation du serveur GraphQL que nous utiliserons, et le autoSchemaFile
L’option est utilisée pour indiquer où notre généré automatiquement GraphQL
un schéma doit être créé.
Ton AppModule
devrait maintenant ressembler à ceci :
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { MovieModule } from "./movie/movie.module";
import { TypeOrmModule } from "@nestjs/typeorm";
import { GraphQLModule } from "@nestjs/graphql";
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
import { join } from "path";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "movies_db",
autoLoadEntities: true,
synchronize: true,
}),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), "src/schema.gql"),
}),
MovieModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Code d’abord vs schéma d’abord
NestJS propose deux manières de créer des applications GraphQL : les méthodes axées sur le code et celles axées sur le schéma.
Avec l’approche code first, nous utilisons des décorateurs et des classes TypeScript pour générer automatiquement le schéma GraphQL correspondant. Avec l’approche schématique d’abord, vous écrivez le schéma directement et NestJS génère les définitions TypeScript pour vous.
Dans cet article, nous nous concentrerons sur l’approche code-first et permettra à NestJS de générer le schéma GraphQL pour nous.
Types d’objets
Dans GraphQL, un type d’objet représente un objet de domaine avec lequel le frontend pourrait vouloir interagir. Il définit la façon dont les données sont structurées dans le schéma GraphQL. Les types d’objet déterminent quels champs finissent par être exposés au client GraphQL.
Ajoutez le code suivant au movie.model.ts
déposer:
import { Field, ObjectType, Int, Float } from "@nestjs/graphql";
@ObjectType()
export class MovieModel {
@Field(() => Int)
id: number;
@Field()
title: string;
@Field()
description: string;
@Field(() => Int)
year: number;
@Field(() => Float)
rating: number;
}
Nous définissons et annotons une classe avec le @ObjectType
et @Field
décorateurs pour décrire notre type d’objet et ses champs correspondants.
Maintenant, le @Field
decorator prend deux arguments facultatifs, un type
fonction et un options
objet. Nous utilisons le type
fonction pour dissiper tout flou entre le système de types TypeScript et le système de types GraphQL.
Bien qu’il ne soit pas requis pour les types chaîne et booléen, il est nécessaire pour les nombres, que nous devons mapper soit Int
ou Float
. D’un autre côté, le options
L’objet a trois paires clé-valeur facultatives :
nullable
: Cela prend un booléen.description
: Cela prend une chaîne.deprecationReason
: Cela prend également une chaîne.
Ensuite, nous devons créer des objets de transfert de données (DTO) pour créer et mettre à jour des films. Dans le dossier du film, créez un dossier appelé dto
. Puis dans le dto
dossier créer deux fichiers : create-movie.dto.ts
et update-movie.dto.ts
.
Maintenant, ajoutez le code suivant au create-movie.dto.ts
déposer:
import { InputType, Field, Float, Int } from "@nestjs/graphql";
@InputType()
export class CreateMovieInput {
@Field()
title: string;
@Field()
description: string;
@Field(() => Int)
year: number;
@Field(() => Float)
rating: number;
}
Ajoutez ce qui suit au update-movie.dto.ts
déposer:
import { Field, InputType, Int, PartialType } from "@nestjs/graphql";
import { CreateMovieInput } from "./create-movie.dto";
@InputType()
export class UpdateMovieInput extends PartialType(CreateMovieInput) {
@Field((type) => Int)
id: number;
}
Pour le contexte, le InputType
est un type particulier de type d’objet.
Entités et services
Créons ensuite la couche de données pour notre application. Dans certains contextes, une entité est essentiellement une classe qui correspond à une table de base de données. Nous allons avoir un Movie
entité et un correspondant MovieService
qui interagit avec le Movie
tableau dans notre base de données.
Ajoutez ce qui suit au movie.entity.ts
déposer:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
@Entity()
export class Movie {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
description: string;
@Column()
year: number;
@Column("float")
rating: number;
}
Mettez également à jour le movie.service.ts
fichier avec les éléments suivants :
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Movie } from "./movie.entity";
import { Repository } from "typeorm";
import { CreateMovieInput } from "./dto/create-movie.dto";
import { UpdateMovieInput } from "./dto/update-movie.dto";
@Injectable()
export class MovieService {
constructor(
@InjectRepository(Movie)
private readonly movieRepository: Repository<Movie>
) {}
findAll(): Promise<Movie[]> {
return this.movieRepository.find();
}
async findOne(id: number): Promise<Movie> {
const movie = await this.movieRepository.findOneBy({ id });
if (!movie) {
throw new Error("Movie not found");
}
return movie;
}
async create(createMovieInput: CreateMovieInput): Promise<Movie> {
const movie = this.movieRepository.create(createMovieInput);
return this.movieRepository.save(movie);
}
async update(updateMovieInput: UpdateMovieInput): Promise<Movie> {
const movie = await this.movieRepository.findOneBy({
id: updateMovieInput.id,
});
if (!movie) {
throw new Error("Movie not found");
}
if (updateMovieInput.title) movie.title = updateMovieInput.title;
if (updateMovieInput.description)
movie.description = updateMovieInput.description;
if (updateMovieInput.year) movie.year = updateMovieInput.year;
if (updateMovieInput.rating !== undefined)
movie.rating = updateMovieInput.rating;
return this.movieRepository.save(movie);
}
async remove(id: number) {
const movie = await this.movieRepository.findOneBy({ id });
if (!movie) {
throw new Error("Movie not found");
}
await this.movieRepository.delete(id);
return movie;
}
}
Enfin, mettez à jour votre MovieModule
dans le movie.module.ts
fichier pour utiliser le TypeOrmModule
.
import { Module } from "@nestjs/common";
import { MovieService } from "./movie.service";
import { MovieResolver } from "./movie.resolver";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Movie } from "./movie.entity";
@Module({
imports: [TypeOrmModule.forFeature([Movie])],
providers: [MovieService, MovieResolver],
})
export class MovieModule {}
Résolveurs
Les résolveurs servent d’intermédiaires entre les opérations GraphQL entrantes (par exemple, les requêtes, les mutations et les abonnements) et la couche de données de notre application (les entités et les services).
Pour configurer notre résolveur, nous devons définir une classe avec des fonctions de résolveur comme méthodes et annoter cette classe avec le @Resolver
décorateur.
Maintenant, mettez à jour le movies.resolver.ts
fichier avec les éléments suivants :
import { Args, Int, Mutation, Query, Resolver } from "@nestjs/graphql";
import { MovieService } from "./movie.service";
import { MovieModel } from "./movie.model";
import { CreateMovieInput } from "./dto/create-movie.dto";
import { UpdateMovieInput } from "./dto/update-movie.dto";
@Resolver()
export class MovieResolver {
constructor(private readonly movieService: MovieService) {}
@Query(() => [MovieModel])
async movies() {
return this.movieService.findAll();
}
@Query(() => MovieModel)
async movieById(@Args("id", { type: () => Int }) id: number) {
return this.movieService.findOne(id);
}
@Mutation(() => MovieModel)
async createMovie(
@Args("createMovieInput") createMovieInput: CreateMovieInput
) {
return this.movieService.create(createMovieInput);
}
@Mutation(() => MovieModel)
async updateMovie(
@Args("updateMovieInput") updateMovieInput: UpdateMovieInput
) {
return this.movieService.update(updateMovieInput);
}
@Mutation(() => MovieModel)
async removeMovie(@Args("id", { type: () => Int }) id: number) {
return this.movieService.remove(id);
}
}
Dans le code ci-dessus, nous annotons les méthodes de notre MoviesResolver
classe avec des décorateurs indiquant quelle opération GraphQL ils effectuent.
- @Requête est utilisé lorsque vous essayez simplement de récupérer des données. Il peut accepter deux arguments : une fonction renvoyant le type d’objet attendu de l’opération de requête et un objet d’options facultatif.
- @Args est utilisé pour extraire les arguments d’une requête pour l’utiliser dans notre méthode. Il accepte deux arguments : le nom du champ à extraire en argument et un argument d’options optionnel.
- @Mutation est utilisé lorsque nous voulons créer, mettre à jour ou supprimer des données. Il accepte une fonction renvoyant le type d’objet attendu de l’opération de mutation.
Une fois la configuration terminée, nous pouvons maintenant démarrer le serveur. Enregistrez tous les fichiers et démarrez le service NestJS en exécutant la commande suivante :
npm run start
Vous devriez voir ceci :
Tests avec GraphQL Playground
Le terrain de jeu GraphQL est un IDE intégré au navigateur, que nous obtenons par défaut en utilisant la même URL que le serveur GraphQL. Pour y accéder, accédez à http://localhost:3000/graphql dans votre navigateur.
Pour ajouter un film, utilisez la mutation suivante :
mutation {
createMovie(
createMovieInput: {
title: "Wolfs"
description: "Two rival fixers cross paths when they're both called in to help cover up a prominent New York official's misstep."
year: 2024,
rating: 6.5
}
) {
id
title
description
year
}
}
Cela devrait ressembler à ceci.
Maintenant, ajoutons deux autres films :
mutation {
createMovie(
createMovieInput: {
title: "Deadpool & Wolverine",
description: "A movie, that shows sacrifice for love, contains a lot of humor, funny, has a lot of sexual language and violence",
year: 2024,
rating: 7.9
}
) {
id
title
description
year
}
}
mutation {
createMovie(
createMovieInput: {
title: "The God Father",
description: "Italian mafia and jokes",
year: 1972,
rating: 9.2
}
) {
id
title
description
year
}
}
Essayons maintenant de récupérer tous les films en utilisant la requête suivante :
{
movies {
id
title
year
rating
description
}
}
Et vous devriez obtenir la réponse suivante :
{
"data": {
"movies": [
{
"id": 1,
"title": "Wolfs",
"year": 2024,
"rating": 6.5,
"description": "Two rival fixers cross paths when they're both called in to help cover up a prominent New York official's misstep."
},
{
"id": 2,
"title": "Deadpool & Wolverine",
"year": 2024,
"rating": 7.9,
"description": "A movie, that shows sacrifice for love, contains a lot of humor, funny, has a lot of sexual language and violence"
},
{
"id": 3,
"title": "The God Father",
"year": 1972,
"rating": 9.2,
"description": "Italian mafia and jokes"
}
]
}
}
Essayons ensuite de mettre à jour un film en utilisant la mutation suivante :
mutation {
updateMovie(updateMovieInput: {
id: 2,
rating: 9,
description: "marvel movie"
}) {
id
title
rating
}
}
Vous devriez obtenir la réponse suivante :
Essayons ensuite de récupérer un seul film.
Enfin, essayons de supprimer un film.
Conclusion
Vous comprenez désormais comment fonctionne GraphQL et pouvez créer une API GraphQL à l’aide de NestJS et d’une base de données Postgres.
Source link