Aller au contenu

Maîtriser NestJS : connecter NestJS à une base PostgreSQL

6 septembre 2023 · 5 min read · Read in English

Sommaire

Introduction

Dans le monde en constante évolution du développement web, une connexion à la base de données fiable et efficace est cruciale. Dans notre exploration continue de NestJS, on a vu ses composants essentiels comme les modules, contrôleurs et services. Il est temps d'aborder un élément vital pour développer des applications backend solides : utiliser TypeORM pour connecter NestJS à une base PostgreSQL.

Dans cet article, on parcourt ensemble les étapes pour connecter ton application NestJS à une base PostgreSQL. On verra tout : configuration de la base, définition des entités, exécution des requêtes, opérations sur la base. À la fin, tu auras les connaissances et compétences pour exploiter pleinement les capacités de NestJS avec PostgreSQL dans ton projet.

J'ai créé un dépôt GitHub pour cette série, accessible à l' adresse suivante.

Installer les packages requis

NestJS fournit le package @nestjs/typeorm pour se connecter aux bases SQL. TypeORM est l'ORM le plus avancé pour TypeScript. Il fonctionne bien avec le framework Nest parce qu'il est écrit en TypeScript. L'approche présentée dans ce chapitre s'applique à toute base supportée par TypeORM. Tu n'as qu'à installer les bibliothèques client API pour la base choisie.

Pour commencer, on doit installer les dépendances requises :

bash
    npm install --save @nestjs/typeorm typeorm pg

Une fois l'installation terminée, on peut importer le TypeOrmModule dans le module racine AppModule.

app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ProductsModule,
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'root',
      password: '',
      database: 'awesome-nestjs',
      autoLoadEntities: true,
      synchronize: true, // avoid doing this in production please and use migrations instead
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
}

Voilà ce que propose la doc officielle. On y reviendra un peu plus tard pour quelques améliorations.

Entités

Dans l'article précédent on a parlé des entités. Cette fois, j'aimerais y revenir parce qu'il y a quelques petits ajustements à faire. Je te montre de quoi je parle :

app.module.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

// @Entity annotation is used to define our Product class as an entity
// products simply represent the table name (it's optional).
@Entity('products')
export class Product {
  // this anotation is used to define a property as the primary key.
  // it will also make sure the PK is auto increment! pretty cool hien ??? 
  @PrimaryGeneratedColumn()
  id: number;

  @Column() // simply define a property as a table column
  name: string;

  @Column({ nullable: true }) // simply means that the field will can be nullable in the table
  description?: string;

  @Column()
  category: string;

  @Column()
  price: number;

  @Column()
  image: string;
}

Pattern Repository

Le pattern Repository est un design pattern courant qui aide à isoler la logique métier de ton application des opérations sous-jacentes sur la base de données. Dans NestJS, tu peux appliquer cette approche en douceur avec TypeORM, en obtenant une manière propre et organisée de te connecter à ta base.

Mettons à jour le fichier du module produit en y important l'entité Product :

products.module.ts
import { Module } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ProductsController } from './products.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Product } from './entities/product.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Product])],
  controllers: [ProductsController],
  providers: [ProductsService],
})
export class ProductsModule {
}

Ce module utilise la méthode forFeature() pour préciser quels repositories sont enregistrés dans le scope courant. Avec ça en place, on peut utiliser le décorateur @InjectRepository() pour injecter le UsersRepository dans le ProductsService :

products.service.ts
import { Injectable } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { Product } from './entities/product.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(Product)
    private productRepository: Repository<Product>,
  ) {
  }

  async create(createProductDto: CreateProductDto) {
    // The save method is used to persist an entity, either by creating a new record in the database or updating an existing one
    return await this.productRepository.save(createProductDto);
  }

  async findAll(): Promise<Product[]> {
    // with the find method, we can laod all the products (an array of products)
    return this.productRepository.find();
  }

  async findOne(id: number): Promise<Product | null> {
    // the findoneBy method allow to retrieve the first row that matches with the provided id
    // it could be name, or product reference, but you have to make sure the field is unique
    return await this.productRepository.findOneBy({ id });
  }

  async update(id: number, updateProductDto: UpdateProductDto) {
    // the update method modify an existing record
    return await this.productRepository.update(id, updateProductDto);
  }

  async remove(id: number) {
    // to delete a row by it's id we use delete method
    return await this.productRepository.delete(id);
  }
}

Variables d'environnement

Les applications tournent fréquemment dans différents contextes. Différentes options de configuration doivent être utilisées selon l'environnement. Par exemple, l'environnement local repose typiquement sur des credentials de base de données spécifiques à l'instance locale. L'environnement de production a ses propres credentials. Comme ces variables changent, il est recommandé de les conserver dans l'environnement.

Trêve de bavardages, installons les modules Node nécessaires :

bash
    npm i --save @nestjs/config

Maintenant, on crée un fichier .env qui sera chargé automatiquement au démarrage du projet :

.env
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=root
POSTGRES_PASSWORD=password
POSTGRES_DB=awesome-nestjs

N'oublie pas de mettre à jour avec tes vraies infos de base de données.

Ok, mettons à jour notre app.module.ts pour profiter des fonctionnalités de la config Nest :

app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ cache: true }),
    ProductsModule,
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get<string>('POSTGRES_HOST'),
        port: configService.get<number>('POSTGRES_PORT'),
        username: configService.get<string>('POSTGRES_USER'),
        password: configService.get<string>('POSTGRES_PASSWORD'),
        database: configService.get<string>('POSTGRES_DB'),
        autoLoadEntities: true,
        synchronize: true,
      }),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
}

Voilà, on s'est facilité la vie avec le package nest/configuration. Mais je ne sais pas si tu es aussi maniaque que moi de la validation des données. Bref, je te montre comment valider les valeurs d'un fichier .env.

Validation de schéma

Si les variables d'environnement requises ne sont pas fournies ou ne respectent pas certaines règles de validation, l'usage est de lever une exception au démarrage du programme.

Gérons ça rapidement :

bash
    npm install --save joi

On peut définir un schéma de validation Joi et le passer via la propriété validationSchema de l'objet d'options de forRoot(), comme ci-dessous :

app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';

@Module({
  imports: [
    ProductsModule,
    ConfigModule.forRoot({
      cache: true,
      validationSchema: Joi.object({
        POSTGRES_HOST: Joi.string().required(),
        POSTGRES_PORT: Joi.number().required(),
        POSTGRES_USER: Joi.string().required(),
        POSTGRES_PASSWORD: Joi.string().required(),
        POSTGRES_DB: Joi.string().required(),
      }),
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get<string>('POSTGRES_HOST'),
        port: configService.get<number>('POSTGRES_PORT'),
        username: configService.get<string>('POSTGRES_USER'),
        password: configService.get<string>('POSTGRES_PASSWORD'),
        database: configService.get<string>('POSTGRES_DB'),
        autoLoadEntities: true,
        synchronize: true,
      }),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
}

Crois-moi : prends l'habitude de valider le fichier .env pour éviter les soucis en production.

Résumé

Dans cet article, on a relié NestJS à PostgreSQL, une base de données relationnelle open source. On a appris à créer une connexion fluide entre notre application NestJS et la base via TypeORM, posant les bases d'applications data-driven efficaces et maintenables.

On a couvert des tâches essentielles : créer la connexion à la base, définir les entités, et exécuter des requêtes pour les opérations CRUD. On en a aussi appris davantage sur la façon dont NestJS et TypeORM coopèrent pour simplifier les interactions avec la base, rendant la conception d'APIs scalables et performantes plus simple que jamais.

Dans le prochain article, on verra ensemble comment implémenter les relations OneToOne, OneToMany et ManyToMany avec TypeORM et PostgreSQL.

À suivre !

S'abonner aux prochains articles

Recevez les nouveaux articles par e-mail. Pas de spam, désinscription à tout moment.

Propulsé par Buttondown.

Related posts

© 2026 < Denis AKPAGNONITE /> | N1BBzerLZXT