Fermer

novembre 21, 2024

Un guide pour créer des API GraphQL avec NestJS

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.

Options du gestionnaire de packages

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.

structure du répertoire src

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.

Configuration de la base de données Postgres

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 GraphQLModulenous 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 :

Démarrage du serveur réussi

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.

Interface de terrain de jeu GraphQL

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.

Réponse createMovie réussie

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 :

Mutation et réponse réussies de la mise à jour du film

Essayons ensuite de récupérer un seul film.

Requête et réponse movieById réussies

Enfin, essayons de supprimer un film.

Mutation et réponse réussies de RemoveMovie

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