برنامه نویسی

درس 04 NestJS – مدل و الگوی مخزن

کد منبع

درس 04

  1. معرفی مختصر یکی از ORM های محبوب – Sequelize
  2. با الگوی ActiveRecord آشنا شوید
  3. نحوه ایجاد طرح واره پایگاه داده با Sequelize در NestJS

1. NestJS را برای کار با MySQL از طریق Sequelize پیکربندی کنید

ابتدا باید پایگاه داده را ایجاد کنید
نام پایگاه داده : nestjs_tutorial_2023

CREATE DATABASE `nestjs_tutorial_2023` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin';
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تصویر

  • با استفاده از Sequelize به MYSQL متصل شوید
  • با استفاده از TypeORM به PostgreSQL متصل شوید
  • با استفاده از Mongoose به MongoDB متصل شوید

1.1. با استفاده از Sequelize به MYSQL متصل شوید

بسته ها را نصب کنید

npm install --save @nestjs/sequelize sequelize sequelize-typescript mysql2
npm install --save-dev @types/sequelize
npm install --save-dev sequelize-cli
npx sequelize-cli init
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این قسمت می خواهم 3 مرحله اصلی کار با بانک های اطلاعاتی را به شما معرفی کنم:

  1. [x] ارتباط برقرار کنید
  2. [x] مدل را اعلام کنید
  3. [x] ساختار دایرکتوری مهاجرت پایگاه داده – مقداردهی اولیه، به روز رسانی طرح پایگاه داده

اتصال را تنظیم کنید

// src/app.module.ts

import { Module } from "@nestjs/common";
import { ServeStaticModule } from "@nestjs/serve-static";
import { join } from "path";
import { PetModule } from "./pet/pet.module";
import { SequelizeModule } from "@nestjs/sequelize";

@Module({
   imports: [
     // public folder
     ServeStaticModule.forRoot({
       rootPath: join(process.cwd(), "public"),
       serveRoot: "/public",
     }),
     PetModule,
     SequelizeModule.forRoot({
       dialect: "mysql",
       host: "localhost",
       port: 3306,
       username: "root",
       password: "123456",
       database: "nestjs_tutorial_2023",
       models: [],
     }),
   ],
   providers: [],
})
export class AppModule {}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اظهارنامه مدل

مدل در ORM:

  • این نگاشت یک موجودیت در پایگاه داده است، از طریق ORM فقط باید با متدهای Model کار کنیم، بقیه عبارات SQL/NoSQL هستند که توسط ORM مدیریت می شوند. مزیت این است که برنامه نویسی آسان تر و سازگارتر می شود. نکته منفی این است که گاهی اوقات اجرای برخی پرس و جوها کند و دشوار است. با این حال، در آن صورت ORM همچنان از آنها برای انجام پرس و جوهای SQL/NoSQL سنتی پشتیبانی می کند.
"use strict";

import { PetCategory } from "src/pet/models/pet-category.model";

/** @type {import('sequelize-cli').Migration} */

module.exports = {
   async up(queryInterface, Sequelize) {
     /**
      * Add altering commands here.
      *
      * Example:
      * await queryInterface.createTable('users', { id: Sequelize.INTEGER });
      */
     await queryInterface.createTable("pet_categories", {
       id: {
         type: Sequelize.UUID,
         defaultValue: Sequelize.UUIDV4,
         primaryKey: true,
       },
       name: {
         type: Sequelize.STRING(60),
         allowNull: false,
       },
       createAt: {
         type: Sequelize.DATE,
         defaultValue: Sequelize.fn("NOW"),
       },
       updatedAt: {
         type: Sequelize.DATE,
         defaultValue: Sequelize.fn("NOW"),
       },
     });
   },

   async down(queryInterface, Sequelize) {
     /**
      * Add reverting commands here.
      *
      * Example:
      * await queryInterface.dropTable('users');
      */
     await queryInterface.dropTable("pet_categories");
   },
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیکربندی را برای استفاده از مدل و استفاده از پایگاه داده uri تنظیم کنید

// src/app.module.ts

import { Module } from "@nestjs/common";
import { ServeStaticModule } from "@nestjs/serve-static";
import { join } from "path";
import { PetModule } from "./pet/pet.module";
import { SequelizeModule } from "@nestjs/sequelize";
import models from "./pet/models";

@Module({
   imports: [
     // public folder
     ServeStaticModule.forRoot({
       rootPath: join(process.cwd(), "public"),
       serveRoot: "/public",
     }),
     PetModule,
     SequelizeModule.forRoot({
       uri: "mysql://root:123456@localhost/nestjs_tutorial_2023",
       dialect: "mysql",
       models: models,
     }),
   ],
   providers: [],
})
export class AppModule {}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ساختار دایرکتوری مهاجرت پایگاه داده – مقداردهی اولیه، به روز رسانی طرح پایگاه داده

توضیحی درباره اینکه چرا باید از sequelize-cli برای به روز رسانی پایگاه داده استفاده کنیم.
در طول توسعه پروژه، زمانی که یک جدول داده وجود دارد که نیاز به افزودن/حذف یا به روز رسانی ستون ها در جدول دارد. مستقیم‌ترین راه عبارت‌های SQL است، اگرچه هنوز می‌توانیم مدیریت کنیم که کدام عبارت اجرا شده یا نه و با اعضای تیم یا افرادی که مسئول استقرار محصولات در محیط تولید هستند به اشتراک گذاشته شود.

در ORM های مدرن امروزی، برای یکسان سازی نحوه و استانداردسازی، معمولاً از cli این ORM ها مربوط به ایجاد/ویرایش پایگاه داده ها، جداول استفاده می کنیم.
و مهاجرت را از طریق خطوط فرمان انجام دهید. ساختار جداول داده از طریق کد پیاده سازی می شود – برای سازگاری با مدل مدیریت شده توسط ORM.

پس از اجرای دستور پیش فرض sequelize-cli، ساختار دایرکتوری پیش فرض زیر را خواهیم داشت.

npx sequelize-cli init
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

config
   database.json
db
   models
   seeders
   migrations
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

sequelizerc

// .sequelizerc

const path = require("path");

module.exports = {
   config: path.resolve("config", "database.json"),
   "models-path": path.resolve("db", "models"),
   "seeders-path": path.resolve("db", "seeders"),
   "migrations-path": path.resolve("db", "migrations"),
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با این حال، برای مطابقت با ساختار فعلی پروژه، باید کمی به صورت زیر تنظیم کنیم

src
     database
         config
             config.ts
     migrations
         *.ts
     seeds
         *.ts
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

sequelizerc

const path = require("path");

module.exports = {
   config: path.resolve("./dist/database/config/config.js"),
   "seeders-path": path.resolve("./dist/database/seeders"),
   "migrations-path": path.resolve("./dist/database/migrations"),
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا به جای اجرای مستقیم نسخه منبع، بیلد آنها را اجرا می کنیم

// config.ts
module.exports = {
   production: {
     url: "mysql://root:123456@localhost/nestjs_tutorial_2023",
     dialect: "mysql",
   },
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

// src/pet/models/pet-category.model
import { Column, DataType, Model, Table } from "sequelize-typescript";

@Table({
   tableName: "pet_categories",
})
export class PetCategory extends Model {
   @Column({
     primaryKey: true,
     type: DataType.UUID,
     defaultValue: DataType.UUIDV4,
   })
   id?: string;

   @Column({
     type: DataType.STRING(60),
     allowNull: false,
   })
   name: string;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

// src\database\migrations\20230704043449-create-pet-category-table.ts
"use strict";

import { PetCategory } from "src/pet/models/pet-category.model";

/** @type {import('sequelize-cli').Migration} */

module.exports = {
   async up(queryInterface, Sequelize) {
     /**
      * Add altering commands here.
      *
      * Example:
      * await queryInterface.createTable('users', { id: Sequelize.INTEGER });
      */
     await queryInterface.createTable("pet_categories", {
       id: {
         type: Sequelize.UUID,
         defaultValue: Sequelize.UUIDV4,
         primaryKey: true,
       },
       name: {
         type: Sequelize.STRING(60),
         allowNull: false,
       },
     });
   },

   async down(queryInterface, Sequelize) {
     /**
      * Add reverting commands here.
      *
      * Example:
      * await queryInterface.dropTable('users');
      */
     await queryInterface.dropTable("pet_categories");
   },
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توجه ویژه برای مدل در اینجا

از آنجایی که مدل PetCategory در مثال از Model از sequelise به ارث می برد، برخی از فیلدهای از پیش تعریف شده را به ارث می برد، اگرچه در کد مدل PetCategory ما آنها را مشاهده نمی کنیم.
بنابراین، هنگام ایجاد یک اسکریپت مهاجرت برای PetCategory، باید به این دو ستون پیش فرض توجه کنید.

export declare abstract class Model<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes> extends OriginModel<TModelAttributes, TCreationAttributes> {
     id?: number | any;
     createAt?: Date | any;
     updatedAt?: Date | any;
     deletedAt?: Date | any;
     version?: number | any;
     static isInitialized: boolean;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برخی از دستورات رایج

$ npx sequelize-cli --help

Sequelize CLI [Node: 16.19.1, CLI: 6.6.1, ORM: 6.32.1]

sequelize <command>

Commands:
   sequelize db:migrate Run pending migrations
   sequelize db:migrate:schema:timestamps:add Update migration table to have timestamps
   sequelize db:migrate:status List the status of all migrations
   sequelize db:migrate:undo Reverts a migration
   sequelize db:migrate:undo:all Revert all migrations ran
   sequelize db:seed Run specified seeder
   sequelize db:seed:undo Deletes data from the database
   sequelize db:seed:all Run every seeder
   sequelize db:seed:undo:all Deletes data from the database
   sequelize db:create Create database specified by configuration
   sequelize db:drop Drop database specified by configuration
   sequelize init Initializes project
   sequelize init:config Initializes configuration
   sequelize init:migrations Initializes migrations
   sequelize init:models Initialize models
   sequelize init:seeders Initializes seeders
   sequelize migration:generate Generates a new migration file
   sequelize migration:create Generates a new migration file
   sequelize model:generate Generates a model and its migration
   sequelize model:create Generates a model and its migration
   sequelize seed:generate Generates a new seed file
   sequelize seed:create Generates a new seed file
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من می خواهم یک دستور مثال برای ایجاد یک فایل مهاجرت، سپس ساخت و اجرا

# create migration
npx sequelize-cli migration:create --name create-pet-category-table --migrations-path ./src/database/migrations
# build
npm run build
# run migration
npx sequelize-cli db:migrate --env production
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توجه داشته باشید که اگر env مشخص نشده باشد، پیش فرض توسعه است. همانطور که در فایل کانفیگ بالا ما فقط یک محیط را به عنوان تولید تنظیم کردیم. و تنظیمات با متغیرهای محیطی جایگزین خواهند شد.

پس از اجرای migrate، اکنون دیتابیس را که خواهیم دید بررسی کنید

تصویر
تصویر

در مرحله بعد، ما شروع به آزمایش برخی از روش های اصلی مدل می کنیم: افزودن، به روز رسانی، حذف، جستجو

یک نکته کوچک هنگام ادامه درس با مثال Pet Category فعلی، باید مدل PetCategory را کمی به روز کنیم، به جای ستون عنوان -> برای سازگاری با پایگاه داده در این زمان به نام ستون تغییر می کند.

PetCategory را اضافه کنید

import { PetCategory } from "src/pet/models/pet-category.model";
await PetCategory.create({ ...object });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

@Controller("admin/pet-categories")
export class ManagePetCategoryController {
   @Post("create")
   @Render("pet/admin/manage-pet-category/create")
   @FormDataRequest()
   async create(@Body() createPetCategoryDto: CreatePetCategoryDto) {
     const data = {
       mode: "create",
     };
     // validation
     const object = plainToInstance(CreatePetCategoryDto, createPetCategoryDto);

     // ...

     // set value and show success message
     Reflect.set(data, "values", object);

     // create PetCategory
     const newPetCategory = await PetCategory.create({ ...object });

     Reflect.set(
       data,
       "success",
       `Pet Category : ${newPetCategory.id} - ${newPetCategory.name} has been created!`
     );
     // success
     return { data };
   }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پس از اجرای http://localhost:3000/admin/pet-categories/create نتایج زیر را دریافت می کنیم

تصویر

علاوه بر این، Sequelize همچنین از شما برای پیکربندی پشتیبانی می‌کند تا بتوانید جزئیات عبارات sql تولید شده توسط ORM را مشاهده کنید. مقداری در تنظیمات مربوط به اتصال پایگاه داده در ماژول برنامه را تغییر دهید.

SequelizeModule.forRoot({
   uri: 'mysql://root:123456@localhost/nestjs_tutorial_2023',
   dialect: 'mysql',
   models: models,
   logging: console.log,
}),
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تصویر

جستجوی PetCategory – لیست

@Controller('admin/pet-categories')
export class ManagePetCategoryController {
   @Get('')
   @Render('pet/admin/manage-pet-category/list')
   async getList() {
     const petCategories = await PetCategory.findAll();
     return {
       petCategories,
     };
   }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

<%- include('layouts/admin/header'); %>
<section class="col-12">
   <div class="card">
     <div class="card-body">
       <h5 class="card-title">List Pet Categories</h5>
       <div class="table-responsive">
         <table class="table table-light table-striped">
           <thead>
             <tr>
               <th scope="col" style="width: 360px">ID</th>
               <th scope="col">Name</th>
             </tr>
           </thead>
           <tbody>
             <% petCategories.forEach(petCategory => { %>
             <tr class="">
               <td><%= petCategory.id %></td>
               <td><%= petCategory.name %></td>
             </tr>
             <% }) %>
           </tbody>
         </table>
       </div>
     </div>
   </div>
</section>
<%- include('layouts/admin/footer'); %>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تصویر

// find all
const petCategories = await PetCategory.findAll();
// delete
await PetCategory.destroy({ where: { id } });
// create
const newPetCategory = await PetCategory.create({ ...object });
// find by primary key
const petCategory = await PetCategory.findByPk(id);
// update
await petCategory.update(object);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کد منبع کنترلرها و نماهای ManagePetCategory را به روز کنید

import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Redirect,
  Render,
} from '@nestjs/common';
import { CreatePetCategoryDto } from 'src/pet/dtos/pet-dto';
import { plainToInstance } from 'class-transformer';
import { validate, ValidationError } from 'class-validator';
import { FormDataRequest } from 'nestjs-form-data';
import { PetCategory } from 'src/pet/models/pet-category.model';
import { Response } from 'express';

const transformError = (error: ValidationError) => {
  const { property, constraints } = error;
  return {
    property,
    constraints,
  };
};
@Controller('admin/pet-categories')
export class ManagePetCategoryController {
  @Get('')
  @Render('pet/admin/manage-pet-category/list')
  async getList() {
    const petCategories = await PetCategory.findAll();
    return {
      petCategories,
    };
  }

  @Post('delete/:id')
  @Redirect('/admin/pet-categories/')
  async deleteOne(@Param() { id }: { id: string }) {
    await PetCategory.destroy({ where: { id } });
  }

  @Get('create')
  @Render('pet/admin/manage-pet-category/create')
  view_create() {
    // a form
    return {
      data: {
        mode: 'create',
      },
    };
  }

  @Post('create')
  @Render('pet/admin/manage-pet-category/create')
  @FormDataRequest()
  async create(@Body() createPetCategoryDto: CreatePetCategoryDto) {
    const data = {
      mode: 'create',
    };
    // validation
    const object = plainToInstance(CreatePetCategoryDto, createPetCategoryDto);
    const errors = await validate(object, {
      stopAtFirstError: true,
    });
    if (errors.length > 0) {
      Reflect.set(data, 'error', 'Please correct all fields!');
      const responseError = {};
      errors.map((error) => {
        const rawError = transformError(error);
        Reflect.set(
          responseError,
          rawError.property,
          Object.values(rawError.constraints)[0],
        );
      });
      Reflect.set(data, 'errors', responseError);
      return { data };
    }
    // set value and show success message
    Reflect.set(data, 'values', object);

    // create PetCategory
    const newPetCategory = await PetCategory.create({ ...object });

    Reflect.set(
      data,
      'success',
      `Pet Category : ${newPetCategory.id} - ${newPetCategory.name} has been created!`,
    );
    // success
    return { data };
  }

  @Get(':id')
  @Render('pet/admin/manage-pet-category/create')
  async getDetail(@Param() { id }: { id: string }) {
    const data = {
      mode: 'edit',
    };
    const petCategory = await PetCategory.findByPk(id);
    Reflect.set(data, 'values', petCategory);
    return { data };
  }

  @Post(':id')
  @Render('pet/admin/manage-pet-category/create')
  @FormDataRequest()
  async updateOne(
    @Param() { id }: { id: string },
    @Body() createPetCategoryDto: CreatePetCategoryDto,
  ) {
    const data = {
      mode: 'edit',
    };
    const petCategory = await PetCategory.findByPk(id);
    // validation
    const object = plainToInstance(CreatePetCategoryDto, createPetCategoryDto);
    const errors = await validate(object, {
      stopAtFirstError: true,
    });
    if (errors.length > 0) {
      Reflect.set(data, 'error', 'Please correct all fields!');
      const responseError = {};
      errors.map((error) => {
        const rawError = transformError(error);
        Reflect.set(
          responseError,
          rawError.property,
          Object.values(rawError.constraints)[0],
        );
      });
      Reflect.set(data, 'errors', responseError);
      return { data };
    }
    // set value and show success message
    await petCategory.update(object);

    Reflect.set(data, 'values', petCategory);
    return { data };
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بازدیدها – list.html

<%- include('layouts/admin/header'); %>
<section class="col-12">
  <div class="card">
    <div class="card-body">
      <h5 class="card-title">List Pet Categories</h5>
      <div class="pb-4">
        <a
          class="btn btn-primary"
          href="/admin/pet-categories/create"
          role="button"
          >New Pet Category</a
        >
      </div>
      <div class="table-responsive">
        <table class="table table-light table-striped">
          <thead>
            <tr>
              <th scope="col" style="width: 360px">ID</th>
              <th scope="col">Name</th>
              <th scope="col">Action</th>
            </tr>
          </thead>
          <tbody>
            <% petCategories.forEach(petCategory => { %>
            <tr class="">
              <td><%= petCategory.id %></td>
              <td><%= petCategory.name %></td>
              <td>
                <a
                  href="/admin/pet-categories/<%= petCategory.id %>"
                  title="Edit"
                  >Edit</a
                >
                <form
                  action="/admin/pet-categories/delete/<%= petCategory.id %>"
                  method="post"
                >
                  <button type="submit">Delete</button>
                </form>
              </td>
            </tr>
            <% }) %>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</section>
<%- include('layouts/admin/footer'); %>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نماها – ایجاد/ویرایش

<%- include('layouts/admin/header'); %>
<section class="col-6">
  <form method="post" enctype="multipart/form-data">
    <div class="card">
      <div class="card-body">
        <h5 class="card-title">
          <% if (data.mode === 'create') { %> New Pet Category <% } %> <% if
          (data.mode === 'edit') { %> Edit Pet Category <% } %>
        </h5>
        <!-- error -->
        <% if (data.error){ %>
        <div class="alert alert-danger" role="alert"><%= data.error %></div>
        <% } %>
        <!-- success -->
        <% if (data.success){ %>
        <div class="alert alert-success" role="alert"><%= data.success %></div>
        <script type="text/javascript">
          setTimeout(() => {
            window.location.href = "/admin/pet-categories/";
          }, 2000);
        </script>
        <% } %>
        <div class="mb-3">
          <label for="title" class="form-label">Name</label>
          <div class="input-group has-validation">
            <input
              type="text"
              class="form-control <%= data.errors && data.errors['name'] ? 'is-invalid': '' %>"
              id="name"
              name="name"
              value="<%= data.values && data.values['name'] %>"
              placeholder="Pet Category name"
            />
            <% if (data.errors && data.errors['name']) { %>
            <div id="validationServerUsernameFeedback" class="invalid-feedback">
              <%= data.errors['name'] %>
            </div>
            <% } %>
          </div>
        </div>
      </div>
      <% if(!data.success) { %>
      <div class="mb-3 col-12 text-center">
        <button type="submit" class="btn btn-primary">Save</button>
      </div>
      <% } %>
    </div>
  </form>
</section>
<%- include('layouts/admin/footer'); %>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای خواندن دوره های کامل در NestJS درس 04 – مدل و الگوی مخزن، راحت باشید

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا