Enoch Barcenas

Enoch Barcenas

1668670503

Upload Files to Azure with NestJS

Learn how to upload files to Azure cloud distributed services and delete them from Azure using NestJS, a popular Node framework. Learn the fundamentals of Azure Blob storage and how to use it in our NestJS API. Upload Files to Azure using NestJS and typeORM with MySQL

Images and videos are examples of huge files that might be stored in your database. And this might impact the performance of your applications because of the amount of space that those files can take up.

Also, it expands the database, which makes backups larger and slower. Because of this, it's not regarded as a best practice. Instead, using a distributed system to save files and adding a reference to such files to our database is a good choice.

In this article you will learn how to upload these files to Azure cloud distributed services and delete them from Azure using NestJS, a popular Node framework.

Table of Contents

  • What is a Cloud Distributed System?
  • What is Azure?
  • Getting Started
  • How to Connect to Azure
  • How to Set up NestJS and MySQL DB
  • How to Connect to Azure Blob through SDK
  • How to Upload the Image through the API
  • How to Create an Endpoint for Uploading Images
  • How to Delete Files
  • Summary

What is a Cloud Distributed System?

A distributed system is made up of a number of independent parts that are spread across several devices. They communicate with one another via messages to accomplish shared objectives.

As a result, to the end user the distributed system will look like a single interface or computer. Together, the system is expected to maximize information and resource usage while preventing errors because, even if one system fails, the service will still be available.

What is Azure?

Azure is a platform for public cloud computing that offers solutions for analytics, virtual computing, storage, networking, and much more.

These solutions include Infrastructure as a Service (IaaS), Platform as a Service (PaaS), and Software as a Service (SaaS). You can supplement or replace your on-premise servers with it.

Blobs, tables, and queues are the three main data services provided by Azure. These services are all widely distributed, highly scalable, and reliable. We will be utilizing one of this services in this article.

Getting Started

Before you start following along with this tutorial, make sure you have the following things ready:

  • An Azure Subscription – You can sign up for a free Azure account if you don’t already have one.
  • Basic Knowledge and installations of NestJS and MySQL server database. Learn more from the NestJS documentation.

How to Connect to Azure

One of Microsoft's Cloud Storage's cloud-based object storage solution is called a Blob. Large-scale unstructured data storage is best suited for blob storage. Unstructured data, such as text or binary data, is data that does not follow a specific data model or description.

How to create a blob storage

Step 1: The Azure dashboard is visible once you create an account and log in to the Azure site. Select Storage Accounts from the menu or use the search bar.‌

creating blob storage step 1‌

Creating blob storage step 1‌

Step 2: Choose create or Create storage account if you do not have an existing storage account from the menu in the following box.

creating blob storage step 2

Creating blob storage step 2

Step 3: In this window, we need to fill in subscription, resource group, storage account name, region, performance, and redundancy.

creating blob storage step 3

Creating blob storage step 3

  • The Subscription allows Azure keep track of where to charge for the resource used. You can use your free subscription here.
  • A resource group is a central grouping for your resource(s). It helps you structure and organize your Azure resources based on your wants.
  • The Storage account name must be a unique name globally.
  • Performance gives you different storage types such as HDD and SSD. Here, we are using Standard.
  • Redundancy helps protect your storage from data center or region failures by duplicating your resource to other regions.

Step 4: Then click review to validate your options. After completing validation, you can click the Create button to create the Storage account. (Note here that we left every other option as default.)

creating blob storage step 4

Creating blob storage step 4

‌When successfully created, the following windows should show up:

creating blob storage step 5

Creating blob storage step 5

Step 5: Next, select Go to resource to be sent to the storage account dashboard. The left sidebar is then visible and has a number of options. Choose the containers option there, which is in the Data Storage section.

creating blob storage step 5

Creating blob storage step 5

Step 6: in the Containers dashboard, now click on + Container, then a form will appear on the right side. Fill out the form by giving the name and public access level (you can use any option according to your requirements) for the container. You can create any number of containers under one storage account

creating blob storage step 6

Creating blob storage step 6creating blob storage step 6Creating blob storage step 6

Click the create button once you've finished filling out the form.

Step 7: Copy the Credentials from the Azure portal.

You should have authorization before sending requests to Azure storage. Azure offers two keys for that purpose, each of which contains a connection string. As a result, you will need these credentials as a connection string to the NestJS application.

One of the connection strings is available for copying in the Access keys area of the Security + networking menu on the left. (We will add these to our NestJS .env file.)

creating blob storage step 7

Creating blob storage step 7

Cool! You've configured your Azure blob storage. The next step is to setup and link your NestJS application with the blob storage.

How to Set Up NestJS and MySQL DB

As mentioned earlier, we will be using NestJS as our server and the MySQL database to save a reference to the file saved on the Azure distributed system.

Firstly you need to have NestJS and MySQL server installed on your system. Then run the following NestJS command to start a new project. Let's call our project nestjs-file-upload-azure:

nest new nestjs-file-upload-azure

How to create a new project in nestjs‌

How to create a new project in Nestjs‌

Before we get started in creating out resources, let's install the necessary dependencies needed:

yarn add mysql2 @nestjs/typeorm @nestjs/config typeorm

To setup your MySQL database with NestJS, open the app.module.ts inside the src folder and add the following code:


import { Module } from '@nestjs/common'; 
import { ConfigModule, ConfigService } from '@nestjs/config'; 
import { TypeOrmModule } from '@nestjs/typeorm'; 
import { FileModule } from './modules/files/file.module'; 
import { UserModule } from './modules/users/user.module'; 

@Module({ 
    imports: [ 
        ConfigModule.forRoot({ 
            envFilePath: '.env', 
            isGlobal: true, 
        }), 
        TypeOrmModule.forRootAsync({ 
            imports: [ConfigModule], 
            inject: [ConfigService], 
            useFactory: (config: ConfigService) => ({ 
                type: 'mysql', 
                host: 'localhost', 
                port: 3306, 
                username: config.get('DB_USERNAME'), 
                password: config.get('DB_PASSWORD'), 
                database: 'azure_upload', 
                entities: [__dirname + '/**/*.entity{.ts,.js}'], 
                synchronize: true, 
            }), 
        }), 
        UserModule, 
        FileModule, 
    ], 
    controllers: [], 
    providers: [], 
}) 

export class AppModule {}          

App module

Inside the imports, configure a simple MySQL database with the TypeOrmModule.forRootAsync() called azure_upload locally. It injects ConfigService to allow us to use environment variables (in this case your database name and password).

For a production-based application, you should set synchronize to false and use migration so as to keep your database data safe.

Now the database is connected successfully, thanks to the TypeORM package we installed. You can check by running yarn start:dev on your terminal or npm run start:dev if you are using npm as your package manager.

How to Connect to the Azure Blob through SDK

Using the @azure/storage-blob storage we can connect to Azure. We are still going to need the Multer package for managing file handling operations, and we'll use UUID to generate a unique name for each blob.

Let’s install them first.

  • yarn add @azure/storage-blob uuidv4 @types/multer or
  • npm install  @azure/storage-blob uuidv4 @types/multer

Now, let's add the connection string we saved earlier to our .env file

Screenshot-from-2022-10-15-22-06-39-2

How to Upload the Image through the API

Since we’ve got the Azure connection set up, we can proceed with uploading our files. For starters, let’s create a file entity and service.

How to Create an Azure Service

The Azure service will help us in mapping out the logic to upload, download, and delete files from our Azure storage account we created.

To generate a service, we will use the NestJS CLI. Open the terminal and run nest g service files modules/files --no-spec --flat.

Add the following to the service files generated:

src/modules/files/files.service.ts


import { BlobServiceClient, BlockBlobClient } from '@azure/storage-blob'; 
import { Injectable } from '@nestjs/common'; 
import { ConfigService } from '@nestjs/config'; 
import { uuid } from 'uuidv4'; 

@Injectable() export class FilesAzureService { 
    constructor(private readonly configService: ConfigService) {} 
    private containerName: string; 

	private async getBlobServiceInstance() { 
        const connectionString = this.configService.get('CONNECTION_STRING'); 
        const blobClientService = await BlobServiceClient.fromConnectionString( connectionString, ); 
        return blobClientService; 
    } 

	private async getBlobClient(imageName: string): Promise<BlockBlobClient> {
        const blobService = await this.getBlobServiceInstance(); 
		const containerName = this.containerName; 
		const containerClient = blobService.getContainerClient(containerName); 
		const blockBlobClient = containerClient.getBlockBlobClient(imageName); 

		return blockBlobClient; 
	} 
    
    public async uploadFile(file: Express.Multer.File, containerName: string) { 
        this.containerName = containerName; 
        const extension = file.originalname.split('.').pop(); 
        const file_name = uuid() + '.' + extension; 
        const blockBlobClient = await this.getBlobClient(file_name);
        const fileUrl = blockBlobClient.url; 
        await blockBlobClient.uploadData(file.buffer); 
        
        return fileUrl; 
    } 
} 

Creating file service

The private functions create an instance of our Azure blob storage with the connection strings using the azure-sdk methods BlobServiceClient.fromConnectionString(). It also expects the container name we gave our blob container earlier during azure storage creation using  getContainerClient and getBlockBlobClient().

The uploadFile() is a public function that user service can call to make image upload to azure. This function uses the azure instance and the private functions to upload the file and returns the file url.

How to Create an Endpoint for Uploading Images

It's time to create our user resources that provide an endpoint for us to create (upload) images to Azure, view the image, and delete the image.

How to Create a User Resource

The NestJS CLI is a powerful tool that helps scaffold our resource by creating basic component as you know them. To easily create a resource, on the terminal type the following command and follow the prompt for REST APIs:

nest generate resource users modules/users --no-spec --flat

The --no-spec ignores test files and the --flat creates the resource directly in a modules/users folder.

The above command added the folders dto and entities and files user.controller.ts, user.module.ts and user.service.ts inside the src file. It also performed all necessary updates to sync with app.module.ts.

user resources created

User resources created

How to Create a User Entity

By saving the image URL directly in the database, we can access the public file very quickly.

src/modules/users/users.entity.ts


import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 

@Entity() export class User { 
	@PrimaryGeneratedColumn() 
    id: number; 
    
    @Column() 
    image_url: string; 
 }

How to Create a User Service

The user service helps connect with the database and save the upload image_url, hence the saveUrl() function.

src/modules/users/users.service.ts

import { Injectable } from '@nestjs/common'; 
import { InjectRepository } from '@nestjs/typeorm'; 
import { Repository } from 'typeorm'; 
import { User } from './entities/user.entity'; 

@Injectable() export class UserService { 
    constructor( 
    	@InjectRepository(User) private readonly userRepository: Repository<User>, 
    ) {} 
    
    async saveUrl(file_url: string) { 
        await this.userRepository.save({ image_url: file_url }); 
    } 
}

Creating user service

How to Create a User Controller

This defines the endpoint for a public user to make a file upload. To do that, we follow the NestJS documentation and use the  FileInterceptor that utilizes multer under the hood.

src/modules/users/users.controllers.ts


import { Controller, Post, UseInterceptors, UploadedFile, } from '@nestjs/common'; 
import { UserService } from './user.service'; 
import { FilesAzureService } from '../files/file.azure.service'; 
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('user') export class UserController { 
	constructor( 
    	private readonly userService: UserService, 
        private readonly fileService: FilesAzureService 
    ) {} 
    
    @Post('upload') 
    @UseInterceptors(FileInterceptor('image')) 
    async create(@UploadedFile() file: Express.Multer.File) { 
    	const containerName = 'fileupload'; 
        const upload = await this.fileService.uploadFile(file, containerName) 
        this.userService.saveUrl(upload); 
        return { upload, message: 'uploaded successfully' } 
    } 
}   

Creating users controller‌

We can test with Postman:

Test upload endpoint from postman

Test upload endpoint from Postman

And confirm on Azure:

Confirm image on the azure portal

Confirm image on the Azure portal

How to Delete Files

We also need a means to delete files after submitting them. We'll remove the files from both locations to maintain consistency between our database and Azure storage. Adding the method to the Files Service first:

src/modules/files/files.service.ts

async deleteFile(file_name: string, containerName: string) { 
	try { 
    	this.containerName = containerName; 
        const blockBlobClient = await this.getBlobClient(file_name);
        await blockBlobClient.deleteIfExists(); 
    } catch (error) { 
    	console.log(error); 
    } 
} 

Create deleteFile service

We must now apply it to our Users Service. A crucial addition is that we remove the previous file when a user uploads one while already having one and making an upload endpoint by user id.

src/modules/users/users.service.ts            

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FilesAzureService } from '../files/file.azure.service';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private readonly userRepository: Repository<User>,
    private readonly fileService: FilesAzureService,
  ) {}

  async saveUrl(id, file_url: string, containerName: string): Promise<void> {
    const user = await this.userRepository.findOne({
      where: { id },
    });
    const file_image = user?.image_url;
    let getfile = '';

    if (file_image) {
      getfile = file_image.split('/').pop();
    }
    await this.userRepository.save({
      ...user,
      image_url: file_url,
    });
    await this.fileService.deleteFile(getfile, containerName);
  }

  async getimage(id: number): Promise<any> {
    const user = await this.userRepository.findOne({
      where: { id },
    });

    return user;
  }

  async remove(id: number, containerName: string): Promise<User> {
    try {
      const user = await this.userRepository.findOne({
        where: { id },
      });
      const file_url = user?.image_url;
      if (file_url) {
        await this.userRepository.update(id, {
          ...user,
          image_url: '',
        });

        const file_ = file_url.split('/').pop();

        await this.fileService.deleteFile(file_, containerName);
      }

      return user;
    } catch (error) {
      console.log(error);
      return error;
    }
  }
}

Creating user remove method and update saveUrl method

Including an endpoint where users can send an image is the final component. To accomplish that, we use the FileInterceptor, which internally makes use of multer, in accordance with the NestJS documentation.

src/modules/users/users.controller.ts

Controller('user')
export class UserController {
  constructor(
    private readonly userService: UserService,
    private readonly fileService: FilesAzureService
  ) {}

  @Post('/:id/upload')
  @UseInterceptors(FileInterceptor('image'))
  async create(@UploadedFile() file: Express.Multer.File, @Param('id') id) {
    const containerName = 'fileupload';
    const upload = await this.fileService.uploadFile(file, containerName)
    this.userService.saveUrl(id, upload, containerName);
    return {
      upload,
      message: 'uploaded successfully'
    }
  }

  @Get('/:id')
  async getimage(@Param('id') id) {
    const image = await this.userService.getimage(id);
    return {
      image,
      message: 'get image successfully'
    }
  }

  @Delete('remove/:id')
  @UseInterceptors(FileInterceptor('image'))
  async remove(@UploadedFile() file: Express.Multer.File , @Param('id') id) {
    const containerName = 'fileupload';
    const user = await this.userService.remove(id, containerName);
    return {
      user,
      message: 'deleted successfully'
    }
  }
}

These sync with your Azure storage to delete unused file uploads.

You can find the full code repository Link on GitHub.

Cleaning Up

To avoid paying for the underlying Azure storage cost, you should clean up your resources if they're not being used.

To clean up your Azure resources, in the Azure portal go to or search for resources group, find the one you just created, and delete it. This will delete all resources in the resource group.

Summary

In this article, we've learnt the fundamentals of Azure Blob storage and how to use it in our NestJS API.

To accomplish that, we've given the required credentials to Azure SDK, and as a result, we've been able to upload and remove files to Azure.

To track our files, we've also kept our MySQL database synchronized with an Azure blob container. We used the FileInterceptor, which is powered by Multer, to upload files using the API.

As always, I hope you enjoyed the article and learned something new. 

Original article source at https://www.freecodecamp.org

#azure #nestjs #typeorm #mysql #node 

Upload Files to Azure with NestJS
Laura  Fox

Laura Fox

1661585642

Nestjs Typeorm Paginate: A Simple Function & Interfaces for Pagination

Nestjs Typeorm paginate

Pagination helper method for TypeORM repositories or queryBuilders with strict typings

Install

$ yarn add nestjs-typeorm-paginate

or

$ npm i nestjs-typeorm-paginate

If you're using typeorm^0.2.6 please use nestjs-typeorm-paginate^3.2.0 For typeorm^0.3.0 please use nestjs-typeorm-paginate^4.0.0

Usage

Service

Repository

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CatEntity } from './entities';
import {
  paginate,
  Pagination,
  IPaginationOptions,
} from 'nestjs-typeorm-paginate';

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(CatEntity)
    private readonly repository: Repository<CatEntity>,
  ) {}

  async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
    return paginate<CatEntity>(this.repository, options);
  }
}

QueryBuilder

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CatEntity } from './entities';
import {
  paginate,
  Pagination,
  IPaginationOptions,
} from 'nestjs-typeorm-paginate';

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(CatEntity)
    private readonly repository: Repository<CatEntity>,
  ) {}

  async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
    const queryBuilder = this.repository.createQueryBuilder('c');
    queryBuilder.orderBy('c.name', 'DESC'); // Or whatever you need to do

    return paginate<CatEntity>(queryBuilder, options);
  }
}

Controller

import { Controller, DefaultValuePipe, Get, ParseIntPipe, Query } from '@nestjs/common';
import { CatService } from './cat.service';
import { CatEntity } from './cat.entity';
import { Pagination } from 'nestjs-typeorm-paginate';

@Controller('cats')
export class CatsController {
  constructor(private readonly catService: CatService) {}
  @Get('')
  async index(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1,
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 10,
  ): Promise<Pagination<CatEntity>> {
    limit = limit > 100 ? 100 : limit;
    return this.catService.paginate({
      page,
      limit,
      route: 'http://cats.com/cats',
    });
  }
}

If you use ParseIntPipe on the query params (as in the example), don't forget to also add DefaultValuePipe. See issue 517 for more info.

the route property of the paginate options can also be the short version of an absolute path , In this case, it would be /cats instead of http://cats.com/cats

Example Response

{
  "items": [
    {
      "lives": 9,
      "type": "tabby",
      "name": "Bobby"
    },
    {
      "lives": 2,
      "type": "Ginger",
      "name": "Garfield"
    },
    {
      "lives": 6,
      "type": "Black",
      "name": "Witch's mate"
    },
    {
      "lives": 7,
      "type": "Purssian Grey",
      "name": "Alisdaya"
    },
    {
      "lives": 1,
      "type": "Alistair",
      "name": "ali"
    },
    ...
  ],
  "meta": {
    "itemCount": 10,
    "totalItems": 20,
    "itemsPerPage": 10,
    "totalPages": 5,
    "currentPage": 2
  },
  "links" : {
    "first": "http://cats.com/cats?limit=10",
    "previous": "http://cats.com/cats?page=1&limit=10",
    "next": "http://cats.com/cats?page=3&limit=10",
    "last": "http://cats.com/cats?page=5&limit=10"
  }
}

items: An array of SomeEntity

meta.itemCount: The length of items array (i.e., the amount of items on this page) meta.totalItems: The total amount of SomeEntity matching the filter conditions meta.itemsPerPage: The requested items per page (i.e., the limit parameter)

meta.totalPages: The total amount of pages (based on the limit) meta.currentPage: The current page this paginator "points" to

links.first: A URL for the first page to call | "" (blank) if no route is defined links.previous: A URL for the previous page to call | "" (blank) if no previous to call links.next: A URL for the next page to call | "" (blank) if no page to call links.last: A URL for the last page to call | "" (blank) if no route is defined

Do note that links.first may not have the 'page' query param defined

Find Parameters

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(CatEntity)
    private readonly repository: Repository<CatEntity>,
  ) {}

  async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
    return paginate<CatEntity>(this.repository, options, {
      lives: 9,
    });
  }
}

Eager loading

Eager loading should work with typeorm's eager property out the box. Like so

import { Entity, OneToMany } from 'typeorm';

@Entity()
export class CatEntity {
  @OneToMany(t => TigerKingEntity, tigerKing.cats, {
    eager: true,
  })
  tigerKings: TigerKingEntity[];
}

// service
class CatService {
  constructor(private readonly repository: Repository<CatEntity>) {}

  async paginate(page: number, limit: number): Promise<Pagination<CatEntity>> {
    return paginate(this.repository, { page, limit });
  }
}

QueryBuilder

However, when using the query builder you'll have to hydrate the entities yourself. Here is a crude example that I've used in the past. It's not great but this is partially what typeORM will do.

const results = paginate(queryBuilder, { page, limit });

return new Pagination(
  await Promise.all(
    results.items.map(async (item: SomeEntity) => {
      const hydrate = await this.someRepository.findByEntity(item);
      item.hydrated = hydrate;

      return item;
    }),
  ),
  results.meta,
  results.links,
);

Raw queries

const queryBuilder = this.repository
  .createQueryBuilder<{ type: string; totalLives: string }>('c')
  .select('c.type', 'type')
  .addSelect('SUM(c.lives)', 'totalLives')
  .groupBy('c.type')
  .orderBy('c.type', 'DESC'); // Or whatever you need to do

return paginateRaw(queryBuilder, options);

Raw and Entities

A similar approach is used for TypeORM's getRawAndEntities

Let's assume there's a joined table that matches each cat with its cat toys. And we want to bring how many toys each cat has.


const queryBuilder = this.repository
  .createQueryBuilder<{ type: string; totalLives: string }>('cat')
    .leftJoinAndSelect('cat.toys', 'toys')
    .addSelect('COUNT(toys)::INTEGER', 'toyCount')
    .groupBy('cat.name');

This will allow us to get the paginated cats information with the additional raw query to build our actual response value. The return pagination object will be the same, but you're now able to handle or map the results and the raw objects as needed.

const [pagination, rawResults] = await paginateRawAndEntities(query, options);
pagination.items.map((item, index) => {
  // we can do what we need with the items and raw results here
  // change your items using rawResults.find(raw => raw.id === item.id)
});
return pagination;

Note about joined tables and raw values

Since the values of the raw results will include all the joined table items as queried, you must make sure to handle the items as needed for your use case. Refer to TypeORM's getRawAndEntities implementation as needed.

The rawResults array will look something like this:

[
    { // Bobby appears 3 times due to the joined query
      "cat_lives": 9,
      "cat_type": "tabby",
      "cat_name": "Bobby",
      "toyCount": 3
    },
    {
      "cat_lives": 9,
      "cat_type": "tabby",
      "cat_name": "Bobby",
      "toyCount": 3
    },
    {
      "cat_lives": 9,
      "cat_type": "tabby",
      "cat_name": "Bobby",
      "toyCount": 3
    },
    {
      "cat_lives": 2,
      "cat_type": "Ginger",
      "cat_name": "Garfield",
      "toyCount": 1
    },
    ...
]

Custom meta data transformer

If you wanted to alter the meta data that is returned from the pagination object. Then use the metaTransformer in the options like so


class CustomPaginationMeta {
  constructor(
    public readonly count: number,
    public readonly total: number,
  ) {}
}

return paginate<MyEntity, CustomPaginationMeta>(this.repository, { 
  page,
  limit,
  metaTransformer: (meta: IPaginationMeta): CustomPaginationMeta => new CustomPaginationMeta(
    meta.itemCount,
    meta.totalItems,
  ),
 });

This will result in the above returning CustomPaginationMeta in the meta property instead of the default IPaginationMeta.

Custom links query params labels

If you want to alter the limit and/or page labels in meta links, then use routingLabels in the options like so


return paginate<MyEntity>(this.repository, { 
  page,
  limit,
  routingLabels: {
    limitLabel: 'page-size', // default: limit
    pageLabel: 'current-page', //default: page
  }
 });

This will result links like http://example.com/something?current-page=1&page-size=3.

Download details:

Author: nestjsx
Source code: https://github.com/nestjsx/nestjs-typeorm-paginate 
License: MIT license

#nest #nestjs #node #typeorm

Nestjs Typeorm Paginate: A Simple Function & Interfaces for Pagination
Hoang  Kim

Hoang Kim

1659533643

Cách Xử Lý Tải Lên Tệp Bằng NestJS Và MySQL

Trong blog này, chúng tôi sẽ hướng dẫn bạn cách xây dựng chức năng tải tệp lên bằng NestJS và MySQL.

Giới thiệu

Nhiều nhà phát triển coi thường việc tải tệp lên. Điều này có thể là do thiếu kiến ​​thức về cách tiếp cận tốt nhất để thực hiện hoặc khó xác định cách định cấu hình ứng dụng Nest.js của họ để xử lý các tệp tải lên. Nhiều người có thể muốn lưu tệp của họ trực tiếp vào cơ sở dữ liệu MySQL hoặc lưu tên hình ảnh và lưu hình ảnh trên ổ lưu trữ: tất cả phụ thuộc vào sở thích của họ và mục tiêu họ muốn đạt được. Hướng dẫn này sẽ dạy bạn cách xây dựng chức năng tải tệp lên bằng Nestjs và MySQL.

Điều kiện tiên quyết

Trước khi bạn bắt đầu làm theo hướng dẫn này, hãy đảm bảo hệ thống của bạn đáp ứng các yêu cầu sau:

Thiết lập NestJS

Khi các yêu cầu nêu trên được đáp ứng, hãy tiến hành cài đặt Nestjs CLI và tạo một dự án mới bằng cách chạy các lệnh sau:

$ npm i -g @nestjs/cli
$ nest new file-upload

Các lệnh này sẽ cài đặt Nestjs CLI và tạo một dự án Nestjs mới với cấu trúc thư mục bên dưới.

📦file-upload
┣ 📂src
┃ ┣ 📜app.controller.spec.ts
┃ ┣ 📜app.controller.ts
┃ ┣ 📜app.module.ts
┃ ┣ 📜app.service.ts
┃ ┣ 📜image.entity.ts
┃ ┗ 📜main.ts
┣ 📂test
┃ ┣ 📜app.e2e-spec.ts
┃ ┗ 📜jest-e2e.json
┣ 📜.eslintrc.js
┣ 📜.gitignore
┣ 📜.prettierrc
┣ 📜README.md
┣ 📜nest-cli.json
┣ 📜package-lock.json
┣ 📜package.json
┣ 📜tsconfig.build.json
┗ 📜tsconfig.json

Sau khi dự án Nestjs đã được tạo, hãy chuyển sang bước tiếp theo - cài đặt các phần phụ thuộc cần thiết cho ứng dụng của bạn bằng cách chạy lệnh sau:

npm install --save @nestjs/typeorm typeorm mysql2

Trong lệnh trên, bạn đã cài đặt các mô-đun TypeORMmysql2 : chúng sẽ cho phép bạn kết nối ứng dụng của mình với cơ sở dữ liệu MySQL và thực hiện các hoạt động trên đó.

Thiết lập Cơ sở dữ liệu MySQL

Với các phần phụ thuộc đã được cài đặt ở trên, hãy tiến hành thiết lập và kết nối với cơ sở dữ liệu MySQL của bạn. Để bắt đầu, hãy thêm mã vào app.module.tstệp với đoạn mã bên dưới.

...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Image } from './image.entity';

@Module({
  imports: [TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: '1234',
    database: 'blog',
    entities: [Image],
    synchronize: true,
  }),
  TypeOrmModule.forFeature([Image])
  ],
  ...
})
...

Trong đoạn mã trên, chúng tôi đã nhập TypeOrmModuletừ mô-đun typeorm mà chúng tôi đã cài đặt trước đó. Chúng tôi đã sử dụng forRootphương pháp để kết nối ứng dụng với cơ sở dữ liệu MySQL và chuyển thông tin đăng nhập cơ sở dữ liệu. Một điều khác cần chỉ ra ở đây là các entitiesthuộc tính, cho phép chúng tôi chỉ định các thực thể trong mô-đun của chúng tôi và sẽ cung cấp cho chúng tôi quyền truy cập vào Imagethực thể mà bạn sẽ tạo trong thời gian ngắn: chúng tôi cũng có thuộc synchronizetính được đặt trueđể tự động di chuyển cơ sở dữ liệu.

Tạo thực thể hình ảnh

Tiếp theo, hãy tạo thực thể Image mà chúng ta đã đề cập trước đó. Để bắt đầu, hãy tạo tệp image.entity.ts trong thư mục src và thêm đoạn mã bên dưới.

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class Image {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @CreateDateColumn()
    dateCreated: Date;

    @UpdateDateColumn()
    dateUpdated: Date;
}

Trong đoạn mã trên, chúng tôi đã nhập các trình trang trí mà chúng tôi cần để tạo một thực thể. Sử dụng các trình trang trí này, chúng tôi đã xác định các thuộc tính của thực thể. Chúng tôi có idtrường để tạo id ngẫu nhiên cho mỗi bản ghi trong cơ sở dữ liệu bằng cách sử dụng trình @PrimaryGeneratedColumn()trang trí, nametrường để lưu trữ tên của các hình ảnh sẽ được tải lên bằng trình @Columntrang trí, trường dateCreate và dateUpdate để lưu ngày bản ghi được tạo và cập nhật bằng cách sử dụng @CreateDateColumn()@UpdateDateColumn().

Tạo dịch vụ tải lên

Với thực thể Image đã được tạo, hãy tạo một dịch vụ để thực hiện các thao tác CRUD để xử lý việc tải lên tệp. Trong app.service.tstệp, hãy thêm đoạn mã bên dưới.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

Trong đoạn mã trên, chúng tôi đã nhập trình injectRepositorytrang trí để đưa imageRepositoryvào AppServicevà trình trang trí Repositorycung cấp cho bạn các phương pháp cần thiết để thực hiện một số thao tác trên cơ sở dữ liệu của bạn. Vì vậy, đối với createImagedịch vụ hình ảnh, chúng tôi đang lưu tên của hình ảnh được tải lên sẽ được chuyển qua bộ điều khiển.

Tạo bộ điều khiển tải lên

Bây giờ chúng ta hãy tạo bộ điều khiển để sử dụng các dịch vụ. Trong app.controller.tstệp và thêm đoạn mã bên dưới.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

Trong đoạn mã trên, chúng tôi đã nhập một số trình trang trí như FileInterceptor, UploadedFileUseInterceptors. Trình FileInterceptor()đánh chặn đối với trình xử lý tuyến đường trích xuất tệp từ yêu cầu bằng trình @UploadedFile()trang trí. Trình FileInterceptor()trang trí được xuất từ @nestjs/platform-express​​gói. Trình @UploadedFile()trang trí được xuất từ @nestjs/common. Trình FileInterceptor()trang trí nhận hai đối số, fieldNamelà chuỗi cung cấp tên của trường từ biểu mẫu HTML chứa tệp và optionslà đối tượng tùy chọn của kiểu MulterOptions. Đây là cùng một đối tượng được sử dụng bởi phương thức khởi tạo multer.

Về createImagechức năng, chúng tôi đã sử dụng các trình trang trí nói trên để xử lý việc tải lên tệp bằng cách FileInterceptor()chuyển tên trường cho hình ảnh và chúng tôi đã sửa đổi FileInterceptor()chức năng tải hình ảnh lên đĩa bằng cách chỉ định thuộc storagetính bằng cách sử dụng diskStoragehàm có sẵn trong multer. Sau đó, chúng tôi chỉ định vị trí cho các hình ảnh và tạo tên ngẫu nhiên cho các hình ảnh. Ngoài ra, chúng tôi đã thêm một filterthuộc tính để hạn chế tải lên các định dạng hình ảnh nhất định. Bây giờ chúng ta lấy tệp được giải nén bằng cách sử dụng @UploadedFile()decorator và lấy tên và lưu nó vào cơ sở dữ liệu. Bằng cách này, chúng ta có thể sử dụng tên của từng hình ảnh để lấy hình ảnh từ vị trí lưu trữ.

Để mã trên hoạt động, bạn cần cài đặt multer bằng cách chạy lệnh bên dưới trong thiết bị đầu cuối của bạn:

npm i -D @types/multer


Sau đó, bạn cần đăng ký mô-đun multer trong mảng nhập trong app.module.tstệp:

...
import { MulterModule } from '@nestjs/platform-express';


@Module({
  ...
  MulterModule.register({
    dest: './files',
  }),],
  ...

Cấu hình trên yêu cầu multer xử lý việc tải lên tệp và vị trí để tải tệp lên. Cuối cùng nhưng không kém phần quan trọng, chúng ta nên tạo một filesthư mục trong thư mục srcđể thực sự lưu trữ các tệp.

Cung cấp Tệp

Để thực sự cung cấp hình ảnh được tải lên ứng dụng của bạn cho người dùng, bạn cần cài đặt serve-staticmô-đun bằng cách chạy lệnh bên dưới.

npm install --save @nestjs/serve-static

Sau đó, đăng ký ServeStaticModulemảng nhập trong app.module.tstệp bằng đoạn mã bên dưới.

...
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  ...
  ServeStaticModule.forRoot({
    rootPath: join(__dirname, '..', 'files')
  }),],
  ...


Trong đoạn mã trên, bạn đã chỉ định vị trí chứa các tệp và có thể được phân phát từ đó.

Kiểm tra API

Bây giờ, hãy mở Postman và kiểm tra ứng dụng bằng cách gửi một yêu cầu POST đến điểm cuối localhost:4000/imagesvà chuyển tải trọng trong phần thân yêu cầu dưới dạng dữ liệu biểu mẫu.

Tệp của chúng tôi

Nếu bây giờ bạn nhìn vào thư mục tệp, bạn sẽ thấy tệp bạn đã tải lên. Hãy tiếp tục: thử nghiệm và chơi xung quanh các tuyến đường khác.

Sự kết luận

Thông qua hướng dẫn này, bạn đã học cách xử lý tải lên tệp bằng NestJS và MySQL. Bạn đã học cách kết nối với cơ sở dữ liệu MySQL bằng TypeORM và bạn cũng đã tạo một thực thể và tải hình ảnh lên ứng dụng Nestjs.

Liên kết: https://arctype.com/blog/nestjs-mysql-tutorial-fileuploads/

#nestjs #node #mysql #typeorm #database 

Cách Xử Lý Tải Lên Tệp Bằng NestJS Và MySQL

Как обрабатывать загрузку файлов с помощью NestJS и MySQL

В этом блоге мы научим вас, как создать функцию загрузки файлов с помощью NestJS и MySQL.

Введение

Многие разработчики презирают загрузку файлов. Это может быть связано с отсутствием знаний о наилучшем подходе или с трудностями при определении того, как настроить приложение Nest.js для обработки загрузки файлов. Многие люди могут захотеть сохранить свои файлы непосредственно в базе данных MySQL или сохранить имена изображений и сохранить изображение на диске: все зависит от их предпочтений и целей, которых они хотят достичь. В этом руководстве вы узнаете, как создать функцию загрузки файлов с помощью Nestjs и MySQL.

Предпосылки

Прежде чем приступить к выполнению этого руководства, убедитесь, что ваша система соответствует следующим требованиям:

Настройка NestJS

Как только вышеупомянутые требования будут выполнены, перейдите к установке интерфейса командной строки Nestjs и создайте новый проект, выполнив следующие команды:

$ npm i -g @nestjs/cli
$ nest new file-upload

Эти команды установят интерфейс командной строки Nestjs и создадут новый проект Nestjs со структурой папок, указанной ниже.

📦file-upload
┣ 📂src
┃ ┣ 📜app.controller.spec.ts
┃ ┣ 📜app.controller.ts
┃ ┣ 📜app.module.ts
┃ ┣ 📜app.service.ts
┃ ┣ 📜image.entity.ts
┃ ┗ 📜main.ts
┣ 📂test
┃ ┣ 📜app.e2e-spec.ts
┃ ┗ 📜jest-e2e.json
┣ 📜.eslintrc.js
┣ 📜.gitignore
┣ 📜.prettierrc
┣ 📜README.md
┣ 📜nest-cli.json
┣ 📜package-lock.json
┣ 📜package.json
┣ 📜tsconfig.build.json
┗ 📜tsconfig.json

После создания проекта Nestjs перейдите к следующему шагу — установите необходимые зависимости для вашего приложения, выполнив следующую команду:

npm install --save @nestjs/typeorm typeorm mysql2

В приведенной выше команде вы установили модули TypeORM и mysql2 : они позволят вам подключить ваше приложение к базе данных MySQL и выполнять над ней операции.

Настройте базу данных MySQL

Установив вышеуказанные зависимости, приступайте к настройке и подключению к базе данных MySQL. Для начала добавьте код в app.module.tsфайл с приведенным ниже фрагментом кода.

...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Image } from './image.entity';

@Module({
  imports: [TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: '1234',
    database: 'blog',
    entities: [Image],
    synchronize: true,
  }),
  TypeOrmModule.forFeature([Image])
  ],
  ...
})
...

В приведенном выше фрагменте кода мы импортировали TypeOrmModuleиз модуля typeorm, который мы установили ранее. Мы использовали этот forRootметод для подключения приложения к базе данных MySQL и передачи учетных данных базы данных. Еще одна вещь, на которую следует обратить внимание, это entitiesсвойства, которые позволили нам указать объекты в нашем модуле и которые дадут нам доступ к Imageобъекту, который вы вскоре создадите: у нас также установлено synchronizeсвойство trueдля автоматической миграции базы данных.

Создать объект изображения

Далее давайте создадим объект Image, о котором мы упоминали ранее. Для начала создайте файл image.entity.ts в каталоге src и добавьте приведенный ниже фрагмент кода.

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class Image {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @CreateDateColumn()
    dateCreated: Date;

    @UpdateDateColumn()
    dateUpdated: Date;
}

В приведенном выше фрагменте кода мы импортировали декораторы, необходимые для создания объекта. С помощью этих декораторов мы определили свойства сущности. У нас есть idполе для генерации случайных идентификаторов для каждой записи в базе данных с помощью @PrimaryGeneratedColumn()декоратора, nameполе для хранения имен изображений, которые будут загружены с помощью @Columnдекоратора, поля dateCreated и dateUpdate для сохранения даты создания и обновления записи. используя @CreateDateColumn()и @UpdateDateColumn().

Создание службы загрузки

Создав объект Image, давайте создадим службу для выполнения операций CRUD для обработки загрузки файлов. В app.service.tsфайл добавьте приведенный ниже фрагмент кода.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

В приведенном выше фрагменте кода мы импортировали injectRepositoryдекоратор для внедрения imageRepositoryв AppServiceи Repositoryкоторый предоставляет вам методы, необходимые для выполнения некоторых операций в вашей базе данных. Итак, для createImageслужбы изображений мы сохраняем имя загруженного изображения, которое будет передано через контроллер.

Создание контроллера загрузки

Теперь давайте создадим контроллеры для использования сервисов. В app.controller.tsфайл и добавьте фрагмент кода ниже.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

В приведенном выше фрагменте кода мы импортировали пару декораторов, таких как FileInterceptor, UploadedFileи UseInterceptors. Перехватчик FileInterceptor()обработчика маршрута извлекает файл из запроса с помощью @UploadedFile()декоратора. Декоратор FileInterceptor()экспортируется из @nestjs/platform-expressпакета. Декоратор @UploadedFile()экспортируется из @nestjs/common. FileInterceptor()Декоратор принимает два аргумента : fieldNameстроку, предоставляющую имя поля из HTML-формы, содержащей файл, и optionsнеобязательный объект типа MulterOptions. Это тот же объект, который используется конструктором multer.

Что касается createImageфункции, мы использовали вышеупомянутые декораторы для обработки загрузки файла, используя FileInterceptor()передачу имени поля для изображения, и мы изменили FileInterceptor()функцию для загрузки изображения на диск, указав storageсвойство с помощью diskStorageфункции, доступной в multer. Затем мы указали расположение изображений и сгенерировали случайные имена для изображений. Кроме того, мы добавили filterсвойство для ограничения загрузки определенных форматов изображений. Теперь мы получаем файл, извлеченный с помощью @UploadedFile()декоратора, получаем имя и сохраняем его в базе данных. Таким образом, мы можем использовать имя каждого изображения, чтобы получить изображение из места хранения.

Чтобы приведенный выше код работал, вам необходимо установить multer, выполнив следующую команду в своем терминале:

npm i -D @types/multer


Затем нужно прописать модуль мультера в массиве импортов в app.module.tsфайле:

...
import { MulterModule } from '@nestjs/platform-express';


@Module({
  ...
  MulterModule.register({
    dest: './files',
  }),],
  ...

Приведенная выше конфигурация указывает multer обрабатывать загрузку файла и место для загрузки файла. И последнее, но не менее важное: мы должны создать filesпапку в srcкаталоге для фактического хранения файлов.

Обслуживание файлов

Чтобы на самом деле показывать изображения, загруженные в ваше приложение, пользователю, вам необходимо установить serve-staticмодуль, выполнив приведенную ниже команду.

npm install --save @nestjs/serve-static

Затем зарегистрируйте ServeStaticModuleв массиве импорта app.module.tsфайл с фрагментом кода ниже.

...
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  ...
  ServeStaticModule.forRoot({
    rootPath: join(__dirname, '..', 'files')
  }),],
  ...


В приведенном выше фрагменте кода вы указали место, где находятся файлы и откуда они могут быть отправлены.

Тестирование API

Теперь откройте Postman и протестируйте приложение, отправив POST-запрос на конечную точку localhost:4000/images, и передайте полезную нагрузку в теле запроса в виде данных формы.

Наш файл

Если вы сейчас посмотрите на папку с файлами, вы должны увидеть файл, который вы загрузили. Не стесняйтесь идти вперед: тестируйте и экспериментируйте с другими маршрутами.

Вывод

Из этого руководства вы узнали, как обрабатывать загрузку файлов с помощью NestJS и MySQL. Вы узнали, как подключиться к базе данных MySQL с помощью TypeORM, а также создали объект и загрузили изображения в приложение Nestjs.

Ссылка: https://arctype.com/blog/nestjs-mysql-tutorial-fileuploads/

#nestjs #node  #mysql #typeorm #database 

Как обрабатывать загрузку файлов с помощью NestJS и MySQL
田辺  亮介

田辺 亮介

1659511980

如何使用 NestJS 和 MySQL 處理文件上傳

在這篇博客中,我們將教你如何使用 NestJS 和 MySQL 構建文件上傳功能。

介紹

許多開發人員鄙視處理文件上傳。這可以歸因於缺乏對最佳方法的了解,或者難以確定如何配置他們的 Nest.js 應用程序來處理文件上傳。許多人可能希望將他們的文件直接保存到 MySQL 數據庫,或者保存圖像名稱並將圖像保存在磁盤存儲中:這完全取決於他們的偏好和他們想要實現的目標。本教程將教您如何使用 Nestjs 和 MySQL 構建文件上傳功能。

先決條件

在開始學習本教程之前,請確保您的系統滿足以下要求:

設置 NestJS

滿足上述要求後,繼續安裝 Nestjs CLI 並通過運行以下命令創建一個新項目:

$ npm i -g @nestjs/cli
$ nest new file-upload

這些命令將安裝 Nestjs CLI 並使用以下文件夾結構創建一個新的 Nestjs 項目。

📦file-upload
┣ 📂src
┃ ┣ 📜app.controller.spec.ts
┃ ┣ 📜app.controller.ts
┃ ┣ 📜app.module.ts
┃ ┣ 📜app.service.ts
┃ ┣ 📜image.entity.ts
┃ ┗ 📜main.ts
┣ 📂test
┃ ┣ 📜app.e2e-spec.ts
┃ ┗ 📜jest-e2e.json
┣ 📜.eslintrc.js
┣ 📜.gitignore
┣ 📜.prettierrc
┣ 📜README.md
┣ 📜nest-cli.json
┣ 📜package-lock.json
┣ 📜package.json
┣ 📜tsconfig.build.json
┗ 📜tsconfig.json

創建 Nestjs 項目後,繼續下一步 - 通過運行以下命令為您的應用程序安裝所需的依賴項:

npm install --save @nestjs/typeorm typeorm mysql2

在上面的命令中,您已經安裝了TypeORMmysql2模塊:它們將使您能夠將應用程序連接到 MySQL 數據庫並對其執行操作。

設置 MySQL 數據庫

安裝上述依賴項後,繼續設置並連接到您的 MySQL 數據庫。要開始使用,請在app.module.ts文件中添加代碼以及下面的代碼片段。

...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Image } from './image.entity';

@Module({
  imports: [TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: '1234',
    database: 'blog',
    entities: [Image],
    synchronize: true,
  }),
  TypeOrmModule.forFeature([Image])
  ],
  ...
})
...

在上面的代碼片段中,我們TypeOrmModule從之前安裝的 typeorm 模塊導入。我們使用該forRoot方法將應用程序連接到 MySQL 數據庫並傳入數據庫憑據。這裡要指出的另一件事是entities屬性,它允許我們指定模塊中的實體,並允許我們訪問Image您將很快創建的實體:我們還將synchronize屬性設置true為自動遷移數據庫。

創建圖像實體

接下來,讓我們創建我們之前提到的 Image 實體。首先,在 src 目錄中創建一個 image.entity.ts 文件並添加下面的代碼片段。

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class Image {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @CreateDateColumn()
    dateCreated: Date;

    @UpdateDateColumn()
    dateUpdated: Date;
}

在上面的代碼片段中,我們導入了創建實體所需的裝飾器。使用這些裝飾器,我們定義了實體的屬性。我們有使用裝飾器id為數據庫中的每條記錄生成隨機 id 的字段,用於存儲將使用裝飾器上傳的圖像名稱的字段,用於保存記錄創建和更新日期的 dateCreated 和 dateUpdate 字段使用和。@PrimaryGeneratedColumn()name@Column@CreateDateColumn()@UpdateDateColumn()

創建上傳服務

創建 Image 實體後,讓我們創建一個服務來執行 CRUD 操作來處理文件上傳。在app.service.ts文件中,添加下面的代碼片段。

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

在上面的代碼片段中,我們已經導入了injectRepository裝飾器來注入,imageRepositoryAppServiceRepository您提供了對數據庫執行某些操作所需的方法。因此,對於createImage圖像服務,我們保存了上傳的圖像的名稱,該名稱將通過控制器傳遞。

創建上傳控制器

現在讓我們創建控制器以使用服務。在app.controller.ts文件中並添加下面的代碼片段。

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

在上面的代碼片段中,我們導入了幾個裝飾器,如FileInterceptorUploadedFileUseInterceptors。路由處理程序的攔截器使用裝飾器FileInterceptor()從請求中提取文件。裝飾器是從包中導出的@UploadedFile()。裝飾器是從. 裝飾器有兩個參數,一個是提供包含文件的 HTML 表單中的字段名稱的字符串,另一個是 MulterOptions 類型的可選對象。這與 multer 構造函數使用的對象相同。FileInterceptor()@nestjs/platform-express@UploadedFile()@nestjs/commonFileInterceptor()fieldNameoptions

關於createImage函數,我們使用上述裝飾器FileInterceptor()通過傳遞圖像的字段名稱來處理文件上傳,並且我們修改了函數以通過使用中可用的函數指定屬性來FileInterceptor()將圖像上傳到磁盤。然後我們指定圖像的位置並為圖像生成隨機名稱。此外,我們添加了一個屬性來限制某些圖像格式的上傳。現在我們使用裝飾器提取文件並獲取名稱並將其保存到數據庫中。這樣我們就可以使用每個圖像的名稱從存儲位置獲取圖像。storagediskStoragemulterfilter@UploadedFile()

要使上述代碼正常工作,您需要通過在終端中運行以下命令來安裝 multer:

npm i -D @types/multer


然後,您需要在app.module.ts文件的導入數組中註冊 multer 模塊:

...
import { MulterModule } from '@nestjs/platform-express';


@Module({
  ...
  MulterModule.register({
    dest: './files',
  }),],
  ...

上面的配置告訴 multer 處理文件上傳和上傳文件的位置。最後但同樣重要的是,我們應該files在目錄中創建一個文件夾src來實際存儲文件。

服務文件

要將應用程序上上傳的圖像實際提供給用戶,您需要serve-static通過運行以下命令來安裝模塊。

npm install --save @nestjs/serve-static

然後,使用下面的代碼片段ServeStaticModule在文件中的導入數組中註冊 。app.module.ts

...
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  ...
  ServeStaticModule.forRoot({
    rootPath: join(__dirname, '..', 'files')
  }),],
  ...


在上面的代碼片段中,您已經指定了文件所在的位置並且可以從中提供服務。

測試 API

現在打開 Postman 並通過向端點發送 POST 請求來測試應用程序,並將localhost:4000/images請求正文中的有效負載作為表單數據傳遞。

我們的檔案

如果您現在查看文件文件夾,您應該會看到已上傳的文件。隨意繼續:測試並嘗試其他路線。

結論

通過本教程,您學習瞭如何使用 NestJS 和 MySQL 處理文件上傳。您已經學習瞭如何使用 TypeORM 連接到 MySQL 數據庫,並且您還創建了一個實體並將圖像上傳到 Nestjs 應用程序。

鏈接:https ://arctype.com/blog/nestjs-mysql-tutorial-fileuploads/

#nestjs #node #mysql #typeorm #database 

如何使用 NestJS 和 MySQL 處理文件上傳
Hans  Marvin

Hans Marvin

1659504720

How to Handle File Uploads with NestJS and MySQL

Many developers despise dealing with file uploads. This can be attributed to a lack of knowledge about the best approach to take or difficulties determining how to configure their Nest.js application to handle file uploads. Many people may want to save their files directly to a MySQL database, or save image names and have the image saved on disk storage: it all depends on their preferences and the goals they want to achieve. This tutorial will teach you how to build a file uploading functionality using Nestjs and MySQL.

In this blog, we will teach you how to build a file uploading functionality using NestJS and MySQL.

See more at: https://arctype.com/blog/nestjs-mysql-tutorial-fileuploads/

#nestjs #node  #mysql #typeorm #database 

How to Handle File Uploads with NestJS and MySQL
Thierry  Perret

Thierry Perret

1659497335

Comment Gérer Les Téléchargements De Fichiers Avec NestJS Et MySQL

Dans ce blog, nous allons vous apprendre à créer une fonctionnalité de téléchargement de fichiers à l'aide de NestJS et MySQL.

Introduction

De nombreux développeurs méprisent la gestion des téléchargements de fichiers. Cela peut être attribué à un manque de connaissances sur la meilleure approche à adopter ou à des difficultés à déterminer comment configurer leur application Nest.js pour gérer les téléchargements de fichiers. De nombreuses personnes peuvent souhaiter enregistrer leurs fichiers directement dans une base de données MySQL, ou enregistrer des noms d'image et enregistrer l'image sur un stockage sur disque : tout dépend de leurs préférences et des objectifs qu'ils souhaitent atteindre. Ce didacticiel vous apprendra à créer une fonctionnalité de téléchargement de fichiers à l'aide de Nestjs et MySQL.

Conditions préalables

Avant de commencer à suivre ce didacticiel, assurez-vous que votre système répond aux exigences suivantes :

  • Votre système exécute Node.js avec la version 14 ou ultérieure.
  • Votre système a une base de données MySQL installée.
  • Vous avez installé Postman .

Configurer NestJS

Une fois les exigences mentionnées ci-dessus remplies, procédez à l'installation de la CLI Nestjs et créez un nouveau projet en exécutant les commandes suivantes :

$ npm i -g @nestjs/cli
$ nest new file-upload

Ces commandes installeront la CLI Nestjs et créeront un nouveau projet Nestjs avec la structure de dossiers ci-dessous.

📦file-upload
┣ 📂src
┃ ┣ 📜app.controller.spec.ts
┃ ┣ 📜app.controller.ts
┃ ┣ 📜app.module.ts
┃ ┣ 📜app.service.ts
┃ ┣ 📜image.entity.ts
┃ ┗ 📜main.ts
┣ 📂test
┃ ┣ 📜app.e2e-spec.ts
┃ ┗ 📜jest-e2e.json
┣ 📜.eslintrc.js
┣ 📜.gitignore
┣ 📜.prettierrc
┣ 📜README.md
┣ 📜nest-cli.json
┣ 📜package-lock.json
┣ 📜package.json
┣ 📜tsconfig.build.json
┗ 📜tsconfig.json

Une fois le projet Nestjs créé, passez à l'étape suivante : installez les dépendances requises pour votre application en exécutant la commande suivante :

npm install --save @nestjs/typeorm typeorm mysql2

Dans la commande ci-dessus, vous avez installé les modules TypeORM et mysql2 : ils vont vous permettre de connecter votre application à une base de données MySQL et d'y effectuer des opérations.

Configurer la base de données MySQL

Une fois les dépendances ci-dessus installées, procédez à la configuration et à la connexion à votre base de données MySQL. Pour commencer, ajoutez le code dans le app.module.tsfichier avec l'extrait de code ci-dessous.

...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Image } from './image.entity';

@Module({
  imports: [TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: '1234',
    database: 'blog',
    entities: [Image],
    synchronize: true,
  }),
  TypeOrmModule.forFeature([Image])
  ],
  ...
})
...

Dans l'extrait de code ci-dessus, nous avons importé TypeOrmModuledu module typeorm que nous avons installé précédemment. Nous avons utilisé la forRootméthode pour connecter l'application à une base de données MySQL et transmettre les informations d'identification de la base de données. Une autre chose à souligner ici est que entitiesles propriétés, qui nous ont permis de spécifier les entités dans notre module et qui nous donneront accès à l' Imageentité que vous allez créer prochainement : nous avons également la synchronizepropriété définie sur truepour migrer automatiquement la base de données.

Créer une entité image

Ensuite, créons l'entité Image que nous avons mentionnée précédemment. Pour commencer, créez un fichier image.entity.ts dans le répertoire src et ajoutez l'extrait de code ci-dessous.

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class Image {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @CreateDateColumn()
    dateCreated: Date;

    @UpdateDateColumn()
    dateUpdated: Date;
}

Dans l'extrait de code ci-dessus, nous avons importé les décorateurs dont nous avons besoin pour créer une entité. En utilisant ces décorateurs, nous avons défini les propriétés de l'entité. Nous avons le idchamp pour générer des identifiants aléatoires pour chaque enregistrement dans la base de données à l'aide du @PrimaryGeneratedColumn()décorateur, le namechamp pour stocker les noms des images qui seront téléchargées à l'aide du @Columndécorateur, les champs dateCreated et dateUpdate pour enregistrer la date à laquelle un enregistrement a été créé et mis à jour en utilisant @CreateDateColumn()et @UpdateDateColumn().

Création du service de téléchargement

Une fois l'entité Image créée, créons un service pour effectuer les opérations CRUD afin de gérer les téléchargements de fichiers. Dans le app.service.tsfichier, ajoutez l'extrait de code ci-dessous.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

Dans l'extrait de code ci-dessus, nous avons importé le injectRepositorydécorateur à injecter imageRepositorydans AppServiceet Repositoryqui vous fournit les méthodes nécessaires pour effectuer certaines opérations sur votre base de données. Donc, pour le createImageservice d'image, nous enregistrons le nom de l'image qui a été téléchargée et qui sera transmise au contrôleur.

Création du contrôleur de téléchargement

Créons maintenant les contrôleurs pour utiliser les services. Dans le app.controller.tsfichier et ajoutez l'extrait de code ci-dessous.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}

Dans l'extrait de code ci-dessus, nous avons importé quelques décorateurs comme FileInterceptor, UploadedFileet UseInterceptors. L' FileInterceptor()intercepteur du gestionnaire de route extrait le fichier de la requête à l'aide du @UploadedFile()décorateur. Le FileInterceptor()décorateur est exporté depuis le @nestjs/platform-expresspackage. Le @UploadedFile()décorateur est exporté depuis @nestjs/common. Le FileInterceptor()décorateur prend deux arguments, fieldNamequi est la chaîne qui fournit le nom du champ du formulaire HTML qui contient un fichier, et optionsqui est un objet facultatif de type MulterOptions. C'est le même objet utilisé par le constructeur multer.

En ce qui concerne la createImagefonction, nous avons utilisé les décorateurs susmentionnés pour gérer le téléchargement du fichier en FileInterceptor()passant le nom du champ pour l'image et nous avons modifié la FileInterceptor()fonction pour télécharger l'image sur le disque en spécifiant la storagepropriété à l'aide de la diskStoragefonction disponible dans multer. Ensuite, nous avons spécifié l'emplacement des images et généré des noms aléatoires pour les images. De plus, nous avons ajouté une filterpropriété pour restreindre le téléchargement de certains formats d'image. Maintenant, nous obtenons le fichier extrait à l'aide du @UploadedFile()décorateur et obtenons le nom et l'enregistrons dans la base de données. De cette façon, nous pouvons utiliser le nom de chaque image pour obtenir l'image de l'emplacement de stockage.

Pour que le code ci-dessus fonctionne, vous devez installer multer en exécutant la commande ci-dessous dans votre terminal :

npm i -D @types/multer


Ensuite, vous devez enregistrer le module multer dans le tableau des importations dans le app.module.tsfichier :

...
import { MulterModule } from '@nestjs/platform-express';


@Module({
  ...
  MulterModule.register({
    dest: './files',
  }),],
  ...

La configuration ci-dessus indique à multer de gérer le téléchargement du fichier et l'emplacement vers lequel télécharger le fichier. Enfin, nous devons créer un filesdossier dans le srcrépertoire pour stocker les fichiers.

Servir des fichiers

Pour réellement servir les images téléchargées sur votre application à l'utilisateur, vous devez installer le serve-staticmodule en exécutant la commande ci-dessous.

npm install --save @nestjs/serve-static

Ensuite, enregistrez le ServeStaticModuledans le tableau des importations dans le app.module.tsfichier avec l'extrait de code ci-dessous.

...
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  ...
  ServeStaticModule.forRoot({
    rootPath: join(__dirname, '..', 'files')
  }),],
  ...


Dans l'extrait de code ci-dessus, vous avez spécifié l'emplacement où se trouvent les fichiers et à partir duquel ils peuvent être servis.

Tester l'API

Ouvrez maintenant Postman et testez l'application en envoyant une requête POST au point de terminaison localhost:4000/imageset transmettez la charge utile dans le corps de la requête en tant que données de formulaire.

Notre dossier

Si vous regardez maintenant le dossier des fichiers, vous devriez voir le fichier que vous avez téléchargé. N'hésitez pas à aller de l'avant : testez et jouez également avec d'autres itinéraires.

Conclusion

Grâce à ce didacticiel, vous avez appris à gérer le téléchargement de fichiers avec NestJS et MySQL. Vous avez appris à vous connecter à une base de données MySQL à l'aide de TypeORM et vous avez également créé une entité et téléchargé des images dans l'application Nestjs.

Lien : https://arctype.com/blog/nestjs-mysql-tutorial-fileuploads/

#nestjs #node #mysql #typeorm #database 

Comment Gérer Les Téléchargements De Fichiers Avec NestJS Et MySQL
Laura  Fox

Laura Fox

1658372400

Nestjs Typeorm | A Simple Function and interfaces for Pagination

Nestjs Typeorm paginate

Pagination helper method for TypeORM repositories or queryBuilders with strict typings

Install

$ yarn add nestjs-typeorm-paginate

or

$ npm i nestjs-typeorm-paginate

If you're using typeorm^0.2.6 please use nestjs-typeorm-paginate^3.2.0 For typeorm^0.3.0 please use nestjs-typeorm-paginate^4.0.0

Usage

Service

Repository

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CatEntity } from './entities';
import {
  paginate,
  Pagination,
  IPaginationOptions,
} from 'nestjs-typeorm-paginate';

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(CatEntity)
    private readonly repository: Repository<CatEntity>,
  ) {}

  async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
    return paginate<CatEntity>(this.repository, options);
  }
}

QueryBuilder

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CatEntity } from './entities';
import {
  paginate,
  Pagination,
  IPaginationOptions,
} from 'nestjs-typeorm-paginate';

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(CatEntity)
    private readonly repository: Repository<CatEntity>,
  ) {}

  async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
    const queryBuilder = this.repository.createQueryBuilder('c');
    queryBuilder.orderBy('c.name', 'DESC'); // Or whatever you need to do

    return paginate<CatEntity>(queryBuilder, options);
  }
}

Controller

import { Controller, DefaultValuePipe, Get, ParseIntPipe, Query } from '@nestjs/common';
import { CatService } from './cat.service';
import { CatEntity } from './cat.entity';
import { Pagination } from 'nestjs-typeorm-paginate';

@Controller('cats')
export class CatsController {
  constructor(private readonly catService: CatService) {}
  @Get('')
  async index(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1,
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 10,
  ): Promise<Pagination<CatEntity>> {
    limit = limit > 100 ? 100 : limit;
    return this.catService.paginate({
      page,
      limit,
      route: 'http://cats.com/cats',
    });
  }
}

If you use ParseIntPipe on the query params (as in the example), don't forget to also add DefaultValuePipe. See issue 517 for more info.

the route property of the paginate options can also be the short version of an absolute path , In this case, it would be /cats instead of http://cats.com/cats

Example Response

{
  "items": [
    {
      "lives": 9,
      "type": "tabby",
      "name": "Bobby"
    },
    {
      "lives": 2,
      "type": "Ginger",
      "name": "Garfield"
    },
    {
      "lives": 6,
      "type": "Black",
      "name": "Witch's mate"
    },
    {
      "lives": 7,
      "type": "Purssian Grey",
      "name": "Alisdaya"
    },
    {
      "lives": 1,
      "type": "Alistair",
      "name": "ali"
    },
    ...
  ],
  "meta": {
    "itemCount": 10,
    "totalItems": 20,
    "itemsPerPage": 10,
    "totalPages": 5,
    "currentPage": 2
  },
  "links" : {
    "first": "http://cats.com/cats?limit=10",
    "previous": "http://cats.com/cats?page=1&limit=10",
    "next": "http://cats.com/cats?page=3&limit=10",
    "last": "http://cats.com/cats?page=5&limit=10"
  }
}

items: An array of SomeEntity

meta.itemCount: The length of items array (i.e., the amount of items on this page) meta.totalItems: The total amount of SomeEntity matching the filter conditions meta.itemsPerPage: The requested items per page (i.e., the limit parameter)

meta.totalPages: The total amount of pages (based on the limit) meta.currentPage: The current page this paginator "points" to

links.first: A URL for the first page to call | "" (blank) if no route is defined links.previous: A URL for the previous page to call | "" (blank) if no previous to call links.next: A URL for the next page to call | "" (blank) if no page to call links.last: A URL for the last page to call | "" (blank) if no route is defined

Do note that links.first may not have the 'page' query param defined

Find Parameters

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(CatEntity)
    private readonly repository: Repository<CatEntity>,
  ) {}

  async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
    return paginate<CatEntity>(this.repository, options, {
      lives: 9,
    });
  }
}

Eager loading

Eager loading should work with typeorm's eager property out the box. Like so

import { Entity, OneToMany } from 'typeorm';

@Entity()
export class CatEntity {
  @OneToMany(t => TigerKingEntity, tigerKing.cats, {
    eager: true,
  })
  tigerKings: TigerKingEntity[];
}

// service
class CatService {
  constructor(private readonly repository: Repository<CatEntity>) {}

  async paginate(page: number, limit: number): Promise<Pagination<CatEntity>> {
    return paginate(this.repository, { page, limit });
  }
}

QueryBuilder

However, when using the query builder you'll have to hydrate the entities yourself. Here is a crude example that I've used in the past. It's not great but this is partially what typeORM will do.

const results = paginate(queryBuilder, { page, limit });

return new Pagination(
  await Promise.all(
    results.items.map(async (item: SomeEntity) => {
      const hydrate = await this.someRepository.findByEntity(item);
      item.hydrated = hydrate;

      return item;
    }),
  ),
  results.meta,
  results.links,
);

Raw queries

const queryBuilder = this.repository
  .createQueryBuilder<{ type: string; totalLives: string }>('c')
  .select('c.type', 'type')
  .addSelect('SUM(c.lives)', 'totalLives')
  .groupBy('c.type')
  .orderBy('c.type', 'DESC'); // Or whatever you need to do

return paginateRaw(queryBuilder, options);

Raw and Entities

A similar approach is used for TypeORM's getRawAndEntities

Let's assume there's a joined table that matches each cat with its cat toys. And we want to bring how many toys each cat has.


const queryBuilder = this.repository
  .createQueryBuilder<{ type: string; totalLives: string }>('cat')
    .leftJoinAndSelect('cat.toys', 'toys')
    .addSelect('COUNT(toys)::INTEGER', 'toyCount')
    .groupBy('cat.name');

This will allow us to get the paginated cats information with the additional raw query to build our actual response value. The return pagination object will be the same, but you're now able to handle or map the results and the raw objects as needed.

const [pagination, rawResults] = await paginateRawAndEntities(query, options);
pagination.items.map((item, index) => {
  // we can do what we need with the items and raw results here
  // change your items using rawResults.find(raw => raw.id === item.id)
});
return pagination;

Note about joined tables and raw values

Since the values of the raw results will include all the joined table items as queried, you must make sure to handle the items as needed for your use case. Refer to TypeORM's getRawAndEntities implementation as needed.

The rawResults array will look something like this:

[
    { // Bobby appears 3 times due to the joined query
      "cat_lives": 9,
      "cat_type": "tabby",
      "cat_name": "Bobby",
      "toyCount": 3
    },
    {
      "cat_lives": 9,
      "cat_type": "tabby",
      "cat_name": "Bobby",
      "toyCount": 3
    },
    {
      "cat_lives": 9,
      "cat_type": "tabby",
      "cat_name": "Bobby",
      "toyCount": 3
    },
    {
      "cat_lives": 2,
      "cat_type": "Ginger",
      "cat_name": "Garfield",
      "toyCount": 1
    },
    ...
]

Custom meta data transformer

If you wanted to alter the meta data that is returned from the pagination object. Then use the metaTransformer in the options like so


class CustomPaginationMeta {
  constructor(
    public readonly count: number,
    public readonly total: number,
  ) {}
}

return paginate<MyEntity, CustomPaginationMeta>(this.repository, { 
  page,
  limit,
  metaTransformer: (meta: IPaginationMeta): CustomPaginationMeta => new CustomPaginationMeta(
    meta.itemCount,
    meta.totalItems,
  ),
 });

This will result in the above returning CustomPaginationMeta in the meta property instead of the default IPaginationMeta.

Custom links query params labels

If you want to alter the limit and/or page labels in meta links, then use routingLabels in the options like so


return paginate<MyEntity>(this.repository, { 
  page,
  limit,
  routingLabels: {
    limitLabel: 'page-size', // default: limit
    pageLabel: 'current-page', //default: page
  }
 });

This will result links like http://example.com/something?current-page=1&page-size=3.

Download Details:
Author: nestjsx
Source Code: https://github.com/nestjsx/nestjs-typeorm-paginate
License: MIT license

#nest #nestjs #node #javascript #typeorm 

Nestjs Typeorm | A Simple Function and interfaces for Pagination
Hoang  Kim

Hoang Kim

1657565580

Xây dựng API thương mại điện tử bằng Nestjs, SQLite và TypeORM

Giới thiệu

Nestjs là một khung Node.js tiên tiến để phát triển các ứng dụng phía máy chủ hiệu quả, đáng tin cậy và có thể mở rộng. Nó đơn giản để tích hợp với các cơ sở dữ liệu NoSQL và SQL như MongoDB, Yugabyte , SQLite , Postgres , MySQL và các cơ sở khác. Nó hỗ trợ các trình ánh xạ quan hệ đối tượng phổ biến như TypeORM Sequelize và Mongoose.

Trong hướng dẫn này, chúng tôi sẽ tạo một ứng dụng thương mại điện tử với SQLite và TypeORM. Chúng ta cũng sẽ xem xét Arctype , một công cụ quản lý cơ sở dữ liệu và ứng dụng khách SQL mạnh mẽ.

Bắt đầu nào!

Điều kiện tiên quyết

Hướng dẫn này là một minh chứng thực hành để giúp bạn bắt đầu. Đảm bảo bạn đã đáp ứng các yêu cầu sau:

  • Bạn đã cài đặt Node (> = 10.13.0, ngoại trừ v13)
  • Bạn có kiến ​​thức nền tảng về Javascript
  • Arctype đã được cài đặt

Thiết lập dự án

Để bắt đầu với Nestjs, hãy cài đặt Nestjs CLI bằng lệnh dưới đây:

npm i -g @nestjs/cli

Cài đặt NestJS CLI

Sau khi cài đặt xong, hãy tạo một dự án Nestjs bằng lệnh bên dưới:

nest new ecommerce

Tạo một dự án mới

Chọn npm của bạn làm trình quản lý gói, nhấn nút enter và đợi Nest cài đặt các gói cần thiết để chạy ứng dụng này.

Sau khi cài đặt hoàn tất, hãy thay đổi thư mục thành thư mục dự án bằng lệnh dưới đây:

cd ecommerce

Thay đổi thư mục

Sau đó, mở thư mục dự án trong trình soạn thảo văn bản hoặc IDE yêu thích của bạn, mở một thiết bị đầu cuối mới và chạy máy chủ ở chế độ phát triển (Điều này sẽ cho phép tải lại nóng và cho phép chúng tôi xem các lỗi có thể xảy ra trên bảng điều khiển) bằng lệnh dưới đây:

npm run start:dev

Khởi động máy chủ

Cài đặt phụ thuộc

Khi máy chủ đang hoạt động, hãy mở một cửa sổ đầu cuối mới để bạn không thoát khỏi máy chủ. Điều này sẽ cho phép bạn thấy ảnh hưởng của những thay đổi được thực hiện đối với cơ sở mã trong suốt hướng dẫn này.

Bây giờ cài đặt các phụ thuộc sau:

Bạn có thể thực hiện việc này bằng lệnh dưới đây:

npm install --save @nestjs/passport passport passport-local @nestjs/jwt passport-jwt @nestjs/typeorm typeorm sqlite3 bcrypt

Cài đặt phụ thuộc

Sau đó, cài đặt các phụ thuộc nhà phát triển bằng lệnh dưới đây:

npm install --save-dev @types/passport-local @types/passport-jwt @types/bcrypt

Cài đặt phụ thuộc nhà phát triển

Bạn có thể lấy một tách cà phê trong khi npm cài đặt các gói. Sau khi cài đặt xong, chúng ta hãy làm bẩn tay.

Tạo mô-đun ứng dụng

Với tất cả các gói cần thiết được yêu cầu để chạy ứng dụng, chúng ta hãy tiến hành tạo các mô-đun ứng dụng. Để xây dựng một ứng dụng sạch sẽ và dễ bảo trì, bạn sẽ tạo các mô-đun riêng biệt cho tất cả các tính năng được triển khai trong ứng dụng này. Vì đây là một ứng dụng Thương mại điện tử, bạn sẽ có xác thực , giỏ hàng , sản phẩmđơn đặt hàng . Tất cả những thứ này sẽ nằm trong các mô-đun riêng biệt của chúng. Hãy bắt đầu với mô-đun xác thực.

Tạo mô-đun xác thực

Tạo mô-đun xác thực bằng lệnh dưới đây:

nest g module auth

Mô-đun xác thực

Lệnh trên tạo một thư mục auth trong thư mục src của dự án với các bảng soạn sẵn cần thiết và đăng ký mô-đun trong mô-đun gốc của dự án ( tệp app.module.ts ).

Tiếp theo, tạo sản phẩm, giỏ hàng, đơn đặt hàng, mô-đun bằng lệnh dưới đây:

#Create a product module
nest g module product

#Create cart module
nest g module cart

#Create cart module
nest g module order

Các mô-đun bổ sung

Ở trên sẽ tạo một sản phẩm, giỏ hàng và thư mục đặt hàng trong thư mục src của dự án với các bảng soạn sẵn cơ bản và đăng ký các mô-đun này trong mô-đun ứng dụng gốc của dự án.

Thiết lập Cơ sở dữ liệu TypeORM và SQLite

Với các mô-đun ứng dụng được cài đặt, hãy thiết lập TypeORM để kết nối ứng dụng của bạn với Cơ sở dữ liệu SQLite và tạo các thực thể mô-đun của bạn. Để bắt đầu, hãy mở app.module.ts và định cấu hình cơ sở dữ liệu SQLite của bạn bằng các đoạn mã bên dưới:

imports: [
 …
 TypeOrmModule.forRoot({
   type :"sqlite",
   database: "shoppingDB",
   entities: [__dirname + "/**/*.entity{.ts,.js}"],
   synchronize: true
 })
],
…

Thiết lập TypeORM

Trong đoạn mã trên, bạn đã kết nối ứng dụng với cơ sở dữ liệu SQLite bằng TypeORM forRoot, chỉ định loại cơ sở dữ liệu, tên cơ sở dữ liệu và vị trí mà Nestjs có thể tìm thấy các thực thể mô hình.

Sau khi máy chủ làm mới, bạn sẽ thấy tệp shoppingDB được tạo trong thư mục gốc của dự án này.

Tạo mô hình thực thể ứng dụng

Với thiết lập cơ sở dữ liệu, hãy tạo các mô hình thực thể cho các mô-đun ứng dụng của chúng tôi. Chúng ta sẽ bắt đầu với mô-đun auth . Tạo tệp thực thể trong thư mục mô-đun auth bằng lệnh dưới đây:

nest generate class auth/user.entity –flat

Tạo tệp thực thể

Sau đó, thêm đoạn mã bên dưới để xác định thuộc tính bảng người dùng với đoạn mã bên dưới:

import { Entity, OneToOne, JoinColumn,Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, OneToMany } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'
import { OrderEntity } from 'src/order/order.entity'

@Entity()
export class Users {
   @PrimaryGeneratedColumn()
   id: number

   @Column()
   username: string

   @Column()
   password: string

   @Column()
   role: string

   @CreateDateColumn()
   createdAt : String

   @UpdateDateColumn()
   updtedAt : String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]

   @OneToOne(type => OrderEntity, order => order.id)
   @JoinColumn()
   order : OrderEntity;
}

Xác định thuộc tính

Trong đoạn mã, bạn đã nhập các trình trang trí cần thiết để thiết lập bảng cơ sở dữ liệu của mình. Bạn cũng đã nhập lớp cartEntityorderEntity mà bạn sẽ sớm tạo. Sử dụng trình trang trí typeorm , chúng tôi đã xác định các thuộc tính cơ sở dữ liệu của mô hình của người dùng. Cuối cùng, chúng tôi đã tạo mối quan hệ 1-1 và một -nhiều giữa thực thể người dùng với cartEntity và orderEntity. Bằng cách này, bạn có thể liên kết một mặt hàng trong giỏ hàng với một người dùng. Điều tương tự cũng áp dụng cho đơn đặt hàng của người dùng.

Tiếp theo, tạo lớp thực thể sản phẩm bằng lệnh dưới đây:

nest generate class product/product.entity –flat

Tạo lớp thực thể sản phẩm

Lệnh trên sẽ tạo tệp product.entity.ts trong thư mục mô-đun sản phẩm.

Bây giờ, hãy định cấu hình các thuộc tính bảng sản phẩm bằng đoạn mã bên dưới:

import { Entity, JoinColumn, OneToMany, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'

@Entity()
export class ProductEntity {
   @PrimaryGeneratedColumn("uuid")
   id!: number

   @Column()
   name: string

   @Column()
   price: number

   @Column()
   quantity: string

   @CreateDateColumn()
   createdAt: String

   @UpdateDateColumn()
   updtedAt: String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]
}

Xác định các thuộc tính

Trong đoạn mã trên, chúng tôi đã định cấu hình các thuộc tính của bảng sản phẩm và tạo mối quan hệ một-nhiều với thực thể giỏ hàng.

Sau đó, tạo thực thể giỏ hàng bằng lệnh dưới đây:

nest generate class cart/cart.entity –flat

Thực thể giỏ hàng

Lệnh trên sẽ tạo tệp cart.entity.ts trong thư mục mô-đun giỏ hàng. Bây giờ, hãy thêm đoạn mã bên dưới vào tệp bạn đã tạo để định cấu hình thuộc tính bảng giỏ hàng.

import { Entity, OneToOne,ManyToOne, JoinColumn, Column, PrimaryGeneratedColumn } from 'typeorm'
import { OrderEntity } from 'src/order/order.entity'
import { ProductEntity } from 'src/product/product.entity'
import { Users } from 'src/auth/user.entity'

@Entity()
export class CartEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @Column()
   total: number

   @Column()
   quantity: number
  
   @ManyToOne(type => ProductEntity, order => order.id)
   @JoinColumn()
   item: ProductEntity

   @ManyToOne(type => Users, user => user.username)
   @JoinColumn()
   user: Users
}

Xác định thuộc tính

Trong đoạn mã trên, bạn đã định cấu hình các thuộc tính của bảng giỏ hàng, tạo mối quan hệ nhiều-một giữa thực thể giỏ hàng và mối quan hệ nhiều-một với thực thể của người dùng.

Cuối cùng, tạo thực thể đơn hàng bằng lệnh dưới đây:

nest generate class order/order.entity –flat

Thực thể đặt hàng

Lệnh trên sẽ tạo tệp order.entity.ts trong thư mục mô-đun đặt hàng. Mở order.entity.ts và định cấu hình bảng cơ sở dữ liệu bằng lệnh dưới đây:

import { Entity, OneToMany, JoinColumn, OneToOne, Column, PrimaryGeneratedColumn } from 'typeorm'
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Entity()
export class OrderEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @OneToMany(type => ProductEntity, item => item.id)
   items: ProductEntity[];

   @OneToOne(type => Users, user => user.username)
   @JoinColumn()
   user : Users;

   @Column()
   subTotal: number

   @Column({ default: false })
   pending: boolean

}

Xác định thuộc tính

Trong đoạn mã trên, bạn đã tạo mối quan hệ 1-1 giữa thực thể người dùng và mối quan hệ một-nhiều với thực thể sản phẩm.

Tại thời điểm này, các thực thể cơ sở dữ liệu của bạn đã được thiết lập và kết nối. Bây giờ, hãy tạo logic nghiệp vụ của bạn để lưu trữ các bản ghi trên các thực thể này.

Tạo dịch vụ ứng dụng

Bây giờ, hãy tạo các dịch vụ cho các mô-đun trong ứng dụng này. Tại đây, bạn sẽ cho phép quản trị viên thêm sản phẩm vào bảng sản phẩm, xác thực người dùng, cho phép người dùng thêm sản phẩm trong cửa hàng vào giỏ hàng và đặt sản phẩm qua giỏ hàng của họ.

Tạo dịch vụ xác thực

Để tạo dịch vụ xác thực, hãy chạy lệnh bên dưới để tạo dịch vụ cho mô-đun xác thực.

nest generate service auth/service/auth --flat

Tạo dịch vụ xác thực

Lệnh trên sẽ tạo tệp auth.service.ts trong thư mục src / auth / service . Bây giờ, hãy mở tệp auth.service.ts và thêm đoạn mã bên dưới:

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from '../user.entity';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(@InjectRepository(Users) private userRepository: Repository<Users>, private jwt: JwtService) { }

   }

Nhập mô-đun

Trong đoạn mã trên, bạn đã nhập các mô-đun InjectRepository, Repository decorator, JwtServicebcrypt . Sau đó, bằng cách sử dụng trình trang trí InjectRepository , bạn đã cung cấp lớp thực thể Người dùng trong dịch vụ xác thực, cung cấp phương thức để thực hiện các hoạt động CRUD trong thực thể Người dùng của bạn.

Sau đó, tạo phương thức đăng ký để cho phép người dùng đăng ký ứng dụng bằng đoạn mã bên dưới:

async signup(user: Users): Promise<Users> {
       const salt = await bcrypt.genSalt();
       const hash = await bcrypt.hash(user.password, salt);
       user.password = hash
       return await this.userRepository.save(user);
   }

Phương thức đăng ký

Bây giờ, hãy tạo phương thức validateUser để xác thực thông tin chi tiết của người dùng và phương thức đăng nhập để tạo mã thông báo jwt cho người dùng đã xác thực.

 …
 async validateUser(username: string, password: string): Promise<any> {
       const foundUser = await this.userRepository.findOne({ username });
       if (foundUser) {
           if (await bcrypt.compare(password, foundUser.password)) {
               const { password, ...result } = foundUser
               return result;
           }

           return null;
       }
       return null

   }
   async login(user: any) {
       const payload = { username: user.username, sub: user.id, role:user.role };

       return {
           access_token: this.jwt.sign(payload),
       };
   }

Xác thực người dùng

Giờ đây, chúng tôi có thể triển khai chiến lược xác thực địa phương Passport của mình . Tạo một tệp có tên local.strategy.ts trong thư mục mô-đun auth và thêm mã sau:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './service/auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
   constructor(private authService: AuthService) {
       super();
   }

   async validate(username: string, password: string): Promise<any> {
  
       const foundUser = await this.authService.validateUser(username, password);
       if (!foundUser) {
           throw new UnauthorizedException();
       }
       return foundUser;
   }
}

Chiến lược xác thực địa phương

Trong đoạn mã trên, bạn đã triển khai chiến lược địa phương hộ chiếu. Không có tùy chọn cấu hình, vì vậy hàm tạo của chúng tôi chỉ gọi super () mà không có đối tượng tùy chọn.

Bạn cũng đã triển khai phương thức validate () . Passport sẽ gọi chức năng xác minh cho từng chiến lược bằng cách sử dụng một bộ thông số phù hợp cho từng chiến lược cụ thể. Đối với chiến lược cục bộ, Passport yêu cầu phương thức validate () với chữ ký sau: validate (tên người dùng: chuỗi, mật khẩu: chuỗi): bất kỳ.

Sau đó, tạo tệp jwt-auth.guard.ts trong thư mục mô-đun auth và xác định trình bảo vệ xác thực tùy chỉnh bằng đoạn mã bên dưới:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

Xác định bảo vệ xác thực tùy chỉnh

Bạn sẽ sử dụng AuthGuard được tạo trong đoạn mã để bảo vệ các tuyến API của mình khỏi những người dùng trái phép.

Bây giờ, hãy tạo tệp jwt-strategy trong thư mục mô-đun auth để xác thực người dùng và tạo mã thông báo jwt cho người dùng đã đăng nhập bằng đoạn mã bên dưới:

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
 constructor() {
   super({
     jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
     ignoreExpiration: false,
     secretOrKey: jwtConstants.secret,
   });
 }

 async validate(payload: any) {
   return { userId: payload.sub, username: payload.username, role: payload.role };
 }
}

Tệp chiến lược

Sau đó, định cấu hình mô-đun jwt trong tệp auth.module.ts trong thư mục mô-đun auth. Trước đó, hãy tạo tệp constants.ts trong cùng một thư mục mô-đun auth để xác định bí mật jwt với đoạn mã bên dưới:

export const jwtConstants = {
   secret: 'wjeld-djeuedw399e3-uejheuii33-4jrjjejei3-rjdjfjf',
}

Bí mật (được sử dụng cho mục đích thử nghiệm)

Bạn có thể tạo bí mật jwt an toàn hơn khi sản xuất, nhưng chúng tôi sẽ sử dụng bí mật đó để trình diễn.

Bây giờ, hãy nhập tất cả các mô-đun bắt buộc trong tệp auth.module.ts của bạn bằng đoạn mã bên dưới:

…
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './user.entity';
…

Nhập mô-đun

Sau đó, trong mảng nhập, hãy định cấu hình jwt bằng đoạn mã bên dưới:

…
imports: [
   PassportModule,
   JwtModule.register({
     secret: jwtConstants.secret,
     signOptions: { expiresIn: '60m' },
   }),
…

Định cấu hình jwt

Trong đoạn mã trên, chúng tôi thêm gói PassModule để cho phép hộ chiếu xử lý xác thực của người dùng và jwt được định cấu hình bằng phương pháp đăng ký JwtModule . Chúng tôi chuyển bí mật mà chúng tôi đã tạo trong tệp hằng số và chỉ định thời gian hết hạn của mã thông báo được tạo (Bạn có thể giảm hoặc tăng thời gian tùy thuộc vào trường hợp sử dụng).

Tạo dịch vụ sản phẩm

Với thiết lập dịch vụ xác thực, hãy tạo dịch vụ sản phẩm bằng lệnh bên dưới:

nest generate service product/service/product

Dịch vụ sản phẩm

Bây giờ, hãy mở tệp product.service.ts được tạo bởi lệnh trên trong mô-đun sản phẩm và thêm các đoạn mã bên dưới:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ProductEntity } from '../product.entity';
import { Repository, UpdateResult, DeleteResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class ProductsService {
   constructor(@InjectRepository(ProductEntity) private productRepository: Repository<ProductEntity>) { }
  
   async getAll(): Promise<ProductEntity[]> {
       return await this.productRepository.find()
   }

   async create(product: ProductEntity, user: Users): Promise<ProductEntity> {
       if (user.role == 'admin') {
           return await this.productRepository.save(product);

       }
       throw new UnauthorizedException();

   }

   async getOne(id: number): Promise<ProductEntity> {
       return this.productRepository.findOne(id);
   }

   async update(id: number, product: ProductEntity, user: Users): Promise<UpdateResult> {
       if (user.role == 'admin') {
           return await this.productRepository.update(id, product);
       }
       throw new UnauthorizedException();
   }

   async delete(id: number, user: Users): Promise<DeleteResult> {
       if (user.role == 'admin') {
           return await this.productRepository.delete(id);
       }
       throw new UnauthorizedException();
   }
  
}

Tạo các dịch vụ CRUD

Trong đoạn mã trên, chúng tôi đã tạo các dịch vụ CRUD của mình. Người dùng hạn chế các phương pháp tạo , cập nhậtxóa . Chỉ quản trị viên mới có thể tạo sản phẩm, xóa hoặc cập nhật sản phẩm.

Bây giờ, hãy mở tệp product.module.ts và làm cho thực thể sản phẩm có thể truy cập được bằng đoạn mã bên dưới:

imports: [TypeOrmModule.forFeature([ProductEntity])],

Làm cho thực thể sản phẩm có thể truy cập được

Tạo dịch vụ giỏ hàng

Tại thời điểm này, quản trị viên có thể thêm sản phẩm vào cơ sở dữ liệu và người dùng đã xác thực có thể xem tất cả các sản phẩm có sẵn. Bây giờ hãy cho phép người dùng thêm các mặt hàng họ thích vào giỏ hàng. Để bắt đầu, hãy tạo một dịch vụ Giỏ hàng bằng lệnh dưới đây:

nest generate service cart/service/cart –flat

Làm dịch vụ xe đẩy

Sau đó, mở tệp cart.service.ts được tạo bởi lệnh và thêm đoạn mã bên dưới:

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartEntity } from '../cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class CartService {
   constructor(
       @InjectRepository(CartEntity)
       private cartRepository: Repository<CartEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private productsService: ProductsService,
   ) { }
   …

Tệp dịch vụ giỏ hàng

Bạn đã nhập các mô-đun cần thiết để tạo dịch vụ Nest.js trong đoạn mã trên. Chúng tôi cũng đã nhập chúng tại đây vì bạn đã tạo mối quan hệ giữa các thực thể giỏ hàng, người dùng và sản phẩm. Sau đó, bạn tạo một phương thức khởi tạo để liên kết các thực thể này với lớp CartService. Bây giờ, hãy tạo một phương thức để thêm một mặt hàng vào giỏ hàng.

async addToCart(productId: number, quantity: number, user: string): Promise<any> {
       const cartItems = await this.cartRepository.find({ relations: ["item",'user'] });
       const product = await this.productsService.getOne(productId);
       const authUser = await this.userRepository.findOne({ username: user })
      
       //Confirm the product exists.
       if (product) {
           //confirm if user has item in cart
           const cart = cartItems.filter(
               (item) => item.item.id === productId && item.user.username === user,
           );
           if (cart.length < 1) {

               const newItem = this.cartRepository.create({ total: product.price * quantity, quantity });
               newItem.user = authUser;
               newItem.item = product;
               this.cartRepository.save(newItem)


               return await this.cartRepository.save(newItem)
           } else {
               //Update the item quantity
               const quantity = (cart[0].quantity += 1);
               const total = cart[0].total * quantity;

               return await this.cartRepository.update(cart[0].id, { quantity, total });
           }
       }
       return null;
   }

Thêm một mặt hàng vào giỏ hàng

Trong đoạn mã trên, bạn đã tạo một phương thức addToCart lấy productId , số lượng và người dùng làm đối số. Sau đó, kiểm tra xem người dùng đã có mặt hàng trong giỏ hàng của họ chưa. Nếu vậy, bạn tăng số lượng và cập nhật tổng giá của mặt hàng đó. Nếu không, bạn thêm mặt hàng vào giỏ hàng của người dùng.

Tiếp theo, làm cho thực thể cartEntity , productEntity UserproductService có thể truy cập được trong cartService bằng cách đăng ký chúng trong tệp cart.module.ts với đoạn mã bên dưới:

…
import { CartEntity } from './cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Module({
 imports: [TypeOrmModule.forFeature([CartEntity, ProductEntity, Users])],
 providers: [CartService, ProductsService],
 …
})

Đăng ký dịch vụ

Cuối cùng, tạo phương thức getItemsInCart lấy người dùng làm đối số để trả về tất cả các giỏ hàng thuộc về một người dùng cụ thể.

async getItemsInCard(user: string): Promise<CartEntity[]> {
       const userCart = await this.cartRepository.find({ relations: ["item",'user'] });
       return (await userCart).filter(item => item.user.username === user)
   }

Nhận các mặt hàng trong giỏ hàng 

Tạo dịch vụ đặt hàng

Khi người dùng mua sắm xong, họ có thể đặt các mặt hàng trong giỏ hàng của mình. Tạo một dịch vụ đặt hàng bằng lệnh dưới đây:

nest generate service order/service/order –flat

Tạo dịch vụ đặt hàng

Bây giờ, hãy mở tệp order.service.ts được tạo từ việc chạy lệnh trên và thêm đoạn mã bên dưới:

import { OrderEntity } from '../order.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartService } from 'src/cart/service/cart.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class OrderService {
   constructor(@InjectRepository(OrderEntity)
   private orderRepository: Repository<OrderEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private cartService: CartService) { }

Dịch vụ đặt hàng

Bạn đã nhập các mô-đun cần thiết để tạo dịch vụ Nest.js trong đoạn mã trên. Chúng tôi cũng đã nhập chúng tại đây vì bạn đã tạo mối quan hệ giữa các thực thể giỏ hàng, người dùng và sản phẩm. Sau đó, bạn đã tạo một phương thức khởi tạo để liên kết các thực thể này với lớp OrderService. Bây giờ, hãy tạo một phương thức để sắp xếp các mặt hàng trong giỏ hàng của người dùng.

…
async order(user: string): Promise<any> {
       //find user existing orders
       const usersOrder = await this.orderRepository.find({ relations: ['user'] });
       const userOrder = usersOrder.filter(order => order.user?.username === user && order.pending === false);
       //find user's cart items
       const cartItems = await this.cartService.getItemsInCard(user)
       const subTotal = cartItems.map(item => item.total).reduce((acc, next) => acc + next);
       //get the authenticated user
       const authUser = await this.userRepository.findOne({ username: user })
       //if users has an pending order - add item to the list of order
       const cart = await cartItems.map(item => item.item);

       if (userOrder.length === 0) {
           const newOrder = await this.orderRepository.create({ subTotal });
           newOrder.items = cart
           newOrder.user = authUser;
           return await this.orderRepository.save(newOrder);


       } else {
           const existingOrder = userOrder.map(item => item)
           await this.orderRepository.update(existingOrder[0].id, { subTotal: existingOrder[0].subTotal + cart[0].price });
           return { message: "order modified" }
       }
   }
…

Đặt hàng các mặt hàng trong giỏ hàng của người dùng

Sau đó, tạo một phương thức khác để nhận đơn đặt hàng của người dùng từ mảng đơn đặt hàng từ cơ sở dữ liệu với đoạn mã bên dưới:

…
async getOrders(user: string): Promise<OrderEntity[]> {
       const orders = await this.orderRepository.find({ relations: ['user'] });
       return orders.filter(order => order.user?.username === user)
   }
}

Nhận đơn đặt hàng của người dùng

Cuối cùng, mở tệp order.module.ts và làm cho người dùng, sản phẩm và thực thể giỏ hàng có thể truy cập được trong orderService bằng đoạn mã bên dưới:

import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderEntity } from './order.entity';
import { ProductEntity } from 'src/product/product.entity';
import { CartService } from 'src/cart/service/cart.service';
import { CartEntity } from 'src/cart/cart.entity';
import { Users } from 'src/auth/user.entity';
import { ProductsService } from 'src/product/service/products.service';

@Module({
 imports: [TypeOrmModule.forFeature([OrderEntity, ProductEntity, CartEntity, Users])],
 controllers: [OrderController],
 providers: [OrderService, CartService, ProductsService]
})

Làm cho các thực thể có thể truy cập được

Tạo bộ điều khiển ứng dụng

Với các dịch vụ ứng dụng đã được tạo thành công, hãy tạo các tuyến API cho các dịch vụ ứng dụng.

Tạo Bộ điều khiển xác thực

Tạo bộ điều khiển xác thực bằng lệnh dưới đây:

nest generate controller auth/controller/auth –flat

Bây giờ, hãy mở tệp auth.controller.ts được tạo từ việc chạy lệnh trên và định cấu hình các tuyến auth bằng đoạn mã bên dưới:

import { Controller, Request, Post, UseGuards, Body } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../service/auth.service';
import { Users } from '../user.entity';

@Controller('api/v1/auth/')
export class AuthController {
   constructor(private usersService: AuthService) { }
  
   @Post('signup')
   async signup(@Body() user: Users): Promise<Users> {
       return this.usersService.signup(user);
   }

   @UseGuards(AuthGuard('local'))
   @Post('login')
   async login(@Request() req) {
       return this.usersService.login(req.user)
   }
}

Định cấu hình các tuyến auth

Tạo Bộ điều khiển Sản phẩm

Với các tuyến của bộ điều khiển xác thực được định cấu hình, hãy tạo bộ điều khiển sản phẩm bằng lệnh bên dưới:

nest generate controller product/controller/product –flat

Bộ điều khiển sản phẩm

Mở tệp product.controller.ts được tạo từ việc chạy lệnh trên và định cấu hình các tuyến sản phẩm bằng đoạn mã bên dưới:

import { Controller, Post, Get, Put, Delete, Param, Request, Body, UseGuards } from '@nestjs/common';
import {UpdateResult, DeleteResult} from 'typeorm';
import { ProductsService } from '../service/products.service';
import { ProductEntity } from '../product.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/products')
export class ProductsController {
 constructor(private productsService: ProductsService) { }

 @UseGuards(JwtAuthGuard)
 @Get()
 async GetAll(): Promise<ProductEntity[]> {
   return await this.productsService.getAll();

 }

 @UseGuards(JwtAuthGuard)
 @Post()
 async Create(@Request() req, @Body() product: ProductEntity): Promise<ProductEntity> {
   return await this.productsService.create(product, req.user);
 }


 @UseGuards(JwtAuthGuard)
 @Get(':id')
 async GetOne(@Param() id: number): Promise<ProductEntity> {
   return await this.productsService.getOne(id);

 }

 @UseGuards(JwtAuthGuard)
 @Put(':id')
 async Update(@Param() id: number, @Body() product: ProductEntity, @Request() req): Promise<UpdateResult> {
   return await this.productsService.update(id, product, req.user);

 }

 @UseGuards(JwtAuthGuard)
 @Delete(':id')
 async Delete(@Param() id: number, @Request() req): Promise<DeleteResult> {
   return await this.productsService.delete(id, req.user);

 }
}

Các tuyến sản phẩm

Trong đoạn mã trên, bạn đã xác định các tuyến CRUD cho dịch vụ sản phẩm. Chúng tôi đã sử dụng trình trang trí UseGuard thông qua JwtAuthGaurd của bạn để bảo vệ các tuyến đường khỏi những người dùng trái phép.

Tạo bộ điều khiển giỏ hàng

Bây giờ tạo bộ điều khiển giỏ hàng bằng lệnh dưới đây:

nest generate controller cart/controller/cart –flat

Bộ điều khiển giỏ hàng

Sau đó, mở tệp cart.controller.ts được tạo từ việc chạy lệnh trên và định cấu hình các tuyến giỏ hàng bằng đoạn mã bên dưới:

import { Controller, Post, Get,Request, Delete, Body, UseGuards } from '@nestjs/common';
import { CartService } from '../service/cart.service';
import { CartEntity } from '../cart.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/cart')
export class CartController {
   constructor(private cartService: CartService) { }

   @UseGuards(JwtAuthGuard)
   @Post()
   async AddToCart(@Body() body, @Request() req): Promise<void> {
       const { productId, quantity } = body
       return await this.cartService.addToCart(productId, quantity, req.user.username);
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getItemsInCart(@Request() req): Promise<CartEntity[]> {
       return await this.cartService.getItemsInCard(req.user.username);

   }
}

Các tuyến xe

Tạo bộ điều khiển đơn hàng

Với các tuyến giỏ hàng được định cấu hình, hãy tạo bộ điều khiển đơn hàng bằng lệnh dưới đây:

nest generate controller order/controller/order –flat

Người điều khiển đơn hàng

Sau đó, mở tệp order.controlle.ts được tạo từ việc chạy lệnh trên và định cấu hình các tuyến giỏ hàng bằng đoạn mã bên dưới:

import { Controller, Post, Get, Request, UseGuards } from '@nestjs/common';
import { OrderService } from '../service/order.service'
import { OrderEntity } from '../order.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/order')
export class OrderController {
   constructor(private orderService: OrderService) { }


   @UseGuards(JwtAuthGuard)
   @Post()
   async order(@Request() req): Promise<any> {
       return this.orderService.order(req.user.username)
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getOrders(@Request() req): Promise<OrderEntity[]> {
       return await this.orderService.getOrders(req.user.username)
   }
}

Đặt hàng các tuyến đường

Tại thời điểm này, tất cả các tuyến API của bạn đã được định cấu hình.

Ứng dụng thử nghiệm

Bây giờ chúng ta hãy kiểm tra chúng bằng cách sử dụng Postman. Kết quả của các bài kiểm tra tới được hiển thị bên dưới.

Tuyến người dùng Tuyếnđăng nhập Tuyếnsản phẩm. Sao chép mã thông báo truy cập và thêm nó vào tiêu đề yêu cầu trước khi thêm sản phẩm.

Hãy thử nghiệm các tuyến đường khác và chơi với mã ở đây trên Github.

Kết nối Arctype với Cơ sở dữ liệu SQLite

Arctype là một ứng dụng khách SQL và công cụ quản lý cơ sở dữ liệu rất thú vị khi sử dụng. Nó cho phép bạn thể hiện trực quan các bảng cơ sở dữ liệu của mình và bạn có thể thực hiện thư mục hoạt động CRUD trong cơ sở dữ liệu của mình bằng Arctype. Kết nối Arctype với cơ sở dữ liệu SQLite là một quá trình đơn giản. Để bắt đầu, hãy làm theo các bước bên dưới:

Đầu tiên, khởi chạy Arctype. Bạn sẽ thấy một màn hình như bên dưới, cho phép bạn thêm thông tin đăng nhập của mình.

Bấm vào tab SQLite. Điều này sẽ hiển thị màn hình bên dưới.

Nhấp vào nút Chọn tệp SQLite và điều hướng đến thư mục dự án của bạn. Chọn tệp cơ sở dữ liệu ứng dụng và nhấn nút mở . Nhấn nút lưu và bạn sẽ thấy các thực thể cơ sở dữ liệu của mình, như được hiển thị trong ảnh chụp màn hình bên dưới.

Sự kết luận

Bằng cách xây dựng một dự án demo, chúng tôi đã học cách tạo một ứng dụng thương mại điện tử bằng Nestjs và Cơ sở dữ liệu SQLite. Đầu tiên, chúng tôi bắt đầu với việc giới thiệu NestJS. Sau đó, chúng tôi tạo một ứng dụng NestJS, kết nối ứng dụng với cơ sở dữ liệu SQLite bằng TypeORM và thực hiện các hoạt động CRUD. Bây giờ bạn đã có kiến ​​thức mà bạn tìm kiếm, hãy thoải mái thêm chức năng bổ sung vào ứng dụng.

Liên kết: https://arctype.com/blog/sqlite-nestjs-tutorial/

#nestjs  #typeorm #sqlite 

Xây dựng API thương mại điện tử bằng Nestjs, SQLite và TypeORM
田辺  亮介

田辺 亮介

1657551120

使用 Nestjs、SQLite 和 TypeORM 構建電子商務 API

介紹

Nestjs 是一個尖端的 Node.js 框架,用於開發高效、可靠和可擴展的服務器端應用程序。與 NoSQL 和 SQL 數據庫(如 MongoDB、YugabyteSQLitePostgresMySQL等)集成很簡單。它支持流行的對象關係映射器,例如 TypeORM Sequelize 和 Mongoose。

在本教程中,我們將使用 SQLite 和 TypeORM 創建一個電子商務應用程序。我們還將了解Arctype,一個強大的 SQL 客戶端和數據庫管理工具。

讓我們開始吧!

先決條件

本教程是一個動手演示,可幫助您入門。確保您滿足以下要求:

  • 您已安裝Node(>= 10.13.0,v13 除外)
  • 你有 Javascript 的基礎知識
  • 已安裝Arctype

項目設置

要開始使用 Nestjs,請使用以下命令安裝 Nestjs CLI:

npm i -g @nestjs/cli

安裝 NestJS CLI

安裝完成後,使用以下命令創建一個 Nestjs 項目:

nest new ecommerce

創建一個新項目

選擇你的npm作為包管理器,點擊回車按鈕,然後等待 Nest 安裝所需的包來運行這個應用程序。

安裝完成後,使用以下命令將目錄更改為項目文件夾:

cd ecommerce

更改目錄

然後在您喜歡的文本編輯器或 IDE 中打開項目目錄,打開一個新終端,並在開發模式下運行服務器(這將啟用熱重載並允許我們在控制台上查看可能的錯誤),使用以下命令:

npm run start:dev

啟動服務器

安裝依賴項

在服務器啟動並運行後,打開一個新的終端窗口,這樣您就不會退出服務器。這將允許您查看整個教程中對代碼庫所做的更改的效果。

現在安裝以下依賴項:

您可以使用以下命令執行此操作:

npm install --save @nestjs/passport passport passport-local @nestjs/jwt passport-jwt @nestjs/typeorm typeorm sqlite3 bcrypt

安裝依賴項

然後使用以下命令安裝開發依賴項:

npm install --save-dev @types/passport-local @types/passport-jwt @types/bcrypt

安裝開發依賴項

在 npm 安裝軟件包時,您可以喝杯咖啡。安裝完成後,讓我們動手吧。

創建應用模塊

有了運行應用程序所需的所有必要包,讓我們繼續創建應用程序模塊。要構建一個乾淨且易於維護的應用程序,您將為該應用程序中實現的所有功能生成單獨的模塊。由於這是一個電子商務應用程序,您將擁有authenticationcartproductsorders。所有這些都將在各自獨立的模塊中。讓我們從身份驗證模塊開始。

創建認證模塊

使用以下命令生成身份驗證模塊:

nest g module auth

認證模塊

上面的命令在項目的src目錄中創建了一個auth文件夾,其中包含必要的樣板文件,並將模塊註冊到項目根模塊(app.module.ts文件)中。

接下來,使用以下命令創建產品、購物車、訂單、模塊:

#Create a product module
nest g module product

#Create cart module
nest g module cart

#Create cart module
nest g module order

附加模塊

以上將在項目的 src 文件夾中創建一個產品、購物車和訂單文件夾,其中包含基本的樣板文件,並將這些模塊註冊到項目的根應用程序模塊中。

設置 TypeORM 和 SQLite 數據庫

安裝應用程序模塊後,設置 TypeORM 以將應用程序連接到 SQLite 數據庫並創建模塊實體。首先,打開app.module.ts並使用以下代碼片段配置您的 SQLite 數據庫:

imports: [
 …
 TypeOrmModule.forRoot({
   type :"sqlite",
   database: "shoppingDB",
   entities: [__dirname + "/**/*.entity{.ts,.js}"],
   synchronize: true
 })
],
…

設置 TypeORM

在上面的代碼片段中,您使用 TypeORM forRoot 將應用程序連接到 SQLite 數據庫,指定數據庫類型、數據庫名稱以及 Nestjs 可以找到模型實體的位置。

服務器刷新後,您應該會看到在該項目的根目錄中創建了一個shoppingDB文件。

創建應用實體模型

通過數據庫設置,讓我們為我們的應用模塊創建實體模型。我們將從auth模塊開始。使用以下命令在 auth 模塊文件夾中生成實體文件:

nest generate class auth/user.entity –flat

創建實體文件

然後添加下面的代碼片段以使用下面的代碼片段定義用戶表屬性:

import { Entity, OneToOne, JoinColumn,Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, OneToMany } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'
import { OrderEntity } from 'src/order/order.entity'

@Entity()
export class Users {
   @PrimaryGeneratedColumn()
   id: number

   @Column()
   username: string

   @Column()
   password: string

   @Column()
   role: string

   @CreateDateColumn()
   createdAt : String

   @UpdateDateColumn()
   updtedAt : String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]

   @OneToOne(type => OrderEntity, order => order.id)
   @JoinColumn()
   order : OrderEntity;
}

定義屬性

在代碼片段中,您導入了設置數據庫表所需的裝飾器。您還導入了您將很快創建的cartEntityorderEntity類。使用typeorm裝飾器,我們定義了用戶模型的數據庫屬性。最後,我們在用戶實體與 cartEntity 和 orderEntity 之間創建了一對一一對多的關係。這樣,您可以將購物車項目與用戶相關聯。這同樣適用於用戶的訂單。

接下來,使用以下命令創建產品實體類:

nest generate class product/product.entity –flat

創建產品實體類

上面的命令會在 products 模塊文件夾中生成一個product.entity.ts文件。

現在使用下面的代碼片段配置產品表屬性:

import { Entity, JoinColumn, OneToMany, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'

@Entity()
export class ProductEntity {
   @PrimaryGeneratedColumn("uuid")
   id!: number

   @Column()
   name: string

   @Column()
   price: number

   @Column()
   quantity: string

   @CreateDateColumn()
   createdAt: String

   @UpdateDateColumn()
   updtedAt: String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]
}

定義屬性

在上面的代碼片段中,我們配置了產品表的屬性,並與購物車實體創建了一對多的關係。

然後使用以下命令創建購物車實體:

nest generate class cart/cart.entity –flat

購物車實體

上述命令將在購物車模塊文件夾中生成一個 cart.entity.ts 文件。現在將下面的代碼片段添加到您創建的文件以配置購物車錶屬性。

import { Entity, OneToOne,ManyToOne, JoinColumn, Column, PrimaryGeneratedColumn } from 'typeorm'
import { OrderEntity } from 'src/order/order.entity'
import { ProductEntity } from 'src/product/product.entity'
import { Users } from 'src/auth/user.entity'

@Entity()
export class CartEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @Column()
   total: number

   @Column()
   quantity: number
  
   @ManyToOne(type => ProductEntity, order => order.id)
   @JoinColumn()
   item: ProductEntity

   @ManyToOne(type => Users, user => user.username)
   @JoinColumn()
   user: Users
}

定義屬性

在上面的代碼片段中,您配置了購物車錶的屬性,創建了購物車實體之間的多對一關係以及與用戶實體的多對一關係。

最後,使用以下命令創建訂單實體:

nest generate class order/order.entity –flat

訂單實體

上述命令將在訂單模塊文件夾中生成一個order.entity.ts文件。打開order.entity.ts並使用以下命令配置數據庫表:

import { Entity, OneToMany, JoinColumn, OneToOne, Column, PrimaryGeneratedColumn } from 'typeorm'
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Entity()
export class OrderEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @OneToMany(type => ProductEntity, item => item.id)
   items: ProductEntity[];

   @OneToOne(type => Users, user => user.username)
   @JoinColumn()
   user : Users;

   @Column()
   subTotal: number

   @Column({ default: false })
   pending: boolean

}

定義屬性

在上面的代碼片段中,您創建了用戶實體之間的一對一關係以及與產品實體的一對多關係。

此時,您的數據庫實體已設置並連接。現在創建您的業務邏輯以在這些實體上存儲記錄。

創建應用服務

現在為這個應用程序中的模塊創建服務。在這裡,您將允許管理員將產品添加到產品表、驗證用戶身份、允許用戶將商店中的產品添加到購物車以及通過他們的購物車訂購產品。

創建身份驗證服務

要創建 auth 服務,請運行以下命令為 auth 模塊生成服務。

nest generate service auth/service/auth --flat

創建身份驗證服務

上面的命令會在src/auth/service文件夾中生成一個auth.service.ts文件。現在打開auth.service.ts文件並添加以下代碼片段:

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from '../user.entity';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(@InjectRepository(Users) private userRepository: Repository<Users>, private jwt: JwtService) { }

   }

導入模塊

在上面的代碼片段中,您導入了 InjectRepository、 Repository裝飾器、JwtServicebcrypt模塊。然後,使用InjectRepository裝飾器,使User實體類在 auth 服務中可用,提供在 User 實體中執行 CRUD 操作的方法。

然後創建一個註冊方法,允許用戶使用下面的代碼片段在應用程序中註冊:

async signup(user: Users): Promise<Users> {
       const salt = await bcrypt.genSalt();
       const hash = await bcrypt.hash(user.password, salt);
       user.password = hash
       return await this.userRepository.save(user);
   }

報名方式

現在創建validateUser方法來驗證用戶的詳細信息,並創建login方法來為經過身份驗證的用戶生成 jwt 令牌。

 …
 async validateUser(username: string, password: string): Promise<any> {
       const foundUser = await this.userRepository.findOne({ username });
       if (foundUser) {
           if (await bcrypt.compare(password, foundUser.password)) {
               const { password, ...result } = foundUser
               return result;
           }

           return null;
       }
       return null

   }
   async login(user: any) {
       const payload = { username: user.username, sub: user.id, role:user.role };

       return {
           access_token: this.jwt.sign(payload),
       };
   }

驗證用戶

現在我們可以實現我們的 Passport本地身份驗證策略。在 auth 模塊文件夾中創建一個名為local.strategy.ts的文件,並添加以下代碼:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './service/auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
   constructor(private authService: AuthService) {
       super();
   }

   async validate(username: string, password: string): Promise<any> {
  
       const foundUser = await this.authService.validateUser(username, password);
       if (!foundUser) {
           throw new UnauthorizedException();
       }
       return foundUser;
   }
}

本地認證策略

在上面的代碼片段中,您已經實現了本地護照策略。沒有配置選項,所以我們的構造函數只是調用super()而不帶選項對象。

您還實現了validate()方法。Passport 將使用適當的策略特定參數集為每個策略調用驗證函數。對於本地策略,Passport 需要具有以下簽名的validate()方法: validate(username: string, password:string): any。

然後在 auth 模塊文件夾中創建一個 jwt-auth.guard.ts 文件,並使用下面的代碼片段定義一個自定義 auth 保護:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

定義自定義身份驗證保護

您將使用代碼片段中創建的 AuthGuard 來保護您的 API 路由免受未經授權的用戶的攻擊。

現在在 auth 模塊文件夾中創建一個 jwt-strategy 文件來驗證用戶並使用以下代碼片段為登錄用戶生成 jwt 令牌:

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
 constructor() {
   super({
     jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
     ignoreExpiration: false,
     secretOrKey: jwtConstants.secret,
   });
 }

 async validate(payload: any) {
   return { userId: payload.sub, username: payload.username, role: payload.role };
 }
}

策略文件

然後在auth模塊文件夾的auth.module.ts文件中配置jwt模塊。在此之前,在同一個 auth 模塊文件夾中創建一個 constants.ts 文件來定義一個 jwt 密碼,代碼片段如下:

export const jwtConstants = {
   secret: 'wjeld-djeuedw399e3-uejheuii33-4jrjjejei3-rjdjfjf',
}

秘密(用於測試目的)

您可以在生產環境中生成更安全的 jwt secret,但我們將使用它來進行演示。

現在使用下面的代碼片段在您的 auth.module.ts 文件中導入所有必需的模塊:

…
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './user.entity';
…

導入模塊

然後在導入數組中,使用以下代碼片段配置 jwt:

…
imports: [
   PassportModule,
   JwtModule.register({
     secret: jwtConstants.secret,
     signOptions: { expiresIn: '60m' },
   }),
…

配置 jwt

在上面的代碼片段中,我們添加了PassModule包以允許 Passport 處理用戶的身份驗證並使用JwtModule 註冊方法配置 jwt。我們傳入我們在常量文件中創建的秘密,並指定生成的令牌的過期時間(您可以根據用例減少或增加時間)。

創建產品服務

通過 auth 服務設置,使用以下命令生成產品服務:

nest generate service product/service/product

產品服務

現在在product模塊中打開上述命令生成的product.service.ts文件,添加如下代碼片段:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ProductEntity } from '../product.entity';
import { Repository, UpdateResult, DeleteResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class ProductsService {
   constructor(@InjectRepository(ProductEntity) private productRepository: Repository<ProductEntity>) { }
  
   async getAll(): Promise<ProductEntity[]> {
       return await this.productRepository.find()
   }

   async create(product: ProductEntity, user: Users): Promise<ProductEntity> {
       if (user.role == 'admin') {
           return await this.productRepository.save(product);

       }
       throw new UnauthorizedException();

   }

   async getOne(id: number): Promise<ProductEntity> {
       return this.productRepository.findOne(id);
   }

   async update(id: number, product: ProductEntity, user: Users): Promise<UpdateResult> {
       if (user.role == 'admin') {
           return await this.productRepository.update(id, product);
       }
       throw new UnauthorizedException();
   }

   async delete(id: number, user: Users): Promise<DeleteResult> {
       if (user.role == 'admin') {
           return await this.productRepository.delete(id);
       }
       throw new UnauthorizedException();
   }
  
}

創建 CRUD 服務

在上面的代碼片段中,我們創建了 CRUD 服務。createupdatedelete方法對用戶是有限制的。只有管​​理員可以創建產品、刪除或更新產品。

現在打開 product.module.ts 文件並使用以下代碼片段使產品實體可訪問:

imports: [TypeOrmModule.forFeature([ProductEntity])],

使產品實體可訪問

創建購物車服務

此時,管理員可以將產品添加到數據庫中,並且經過身份驗證的用戶可以看到所有可用的產品。現在讓我們允許用戶將他們喜歡的商品添加到購物車中。首先,使用以下命令生成購物車服務:

nest generate service cart/service/cart –flat

製作購物車服務

然後打開該命令生成的cart.service.ts文件,添加如下代碼片段:

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartEntity } from '../cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class CartService {
   constructor(
       @InjectRepository(CartEntity)
       private cartRepository: Repository<CartEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private productsService: ProductsService,
   ) { }
   …

購物車服務文件

您在上面的代碼片段中導入了創建 Nest.js 服務所需的模塊。由於您在購物車、用戶和產品實體之間創建了關係,因此我們也在此處導入了它們。然後,您創建一個構造方法來將這些實體綁定到 CartService 類。現在創建一個將商品添加到購物車的方法。

async addToCart(productId: number, quantity: number, user: string): Promise<any> {
       const cartItems = await this.cartRepository.find({ relations: ["item",'user'] });
       const product = await this.productsService.getOne(productId);
       const authUser = await this.userRepository.findOne({ username: user })
      
       //Confirm the product exists.
       if (product) {
           //confirm if user has item in cart
           const cart = cartItems.filter(
               (item) => item.item.id === productId && item.user.username === user,
           );
           if (cart.length < 1) {

               const newItem = this.cartRepository.create({ total: product.price * quantity, quantity });
               newItem.user = authUser;
               newItem.item = product;
               this.cartRepository.save(newItem)


               return await this.cartRepository.save(newItem)
           } else {
               //Update the item quantity
               const quantity = (cart[0].quantity += 1);
               const total = cart[0].total * quantity;

               return await this.cartRepository.update(cart[0].id, { quantity, total });
           }
       }
       return null;
   }

將商品添加到購物車

在上面的代碼片段中,您創建了一個addToCart方法,該方法將productId數量和用戶作為參數。然後檢查用戶的購物車中是否已有該商品。如果是這樣,您增加數量並更新該項目的總價。否則,您將商品添加到用戶的購物車。

接下來,通過使用以下代碼片段在cart.module.ts文件中註冊cartEntityproductEntity 用戶實體和productService ,使它們可以在cartService中訪問:

…
import { CartEntity } from './cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Module({
 imports: [TypeOrmModule.forFeature([CartEntity, ProductEntity, Users])],
 providers: [CartService, ProductsService],
 …
})

註冊服務

最後,創建一個 getItemsInCart 方法,該方法將用戶作為參數返回屬於特定用戶的所有購物車。

async getItemsInCard(user: string): Promise<CartEntity[]> {
       const userCart = await this.cartRepository.find({ relations: ["item",'user'] });
       return (await userCart).filter(item => item.user.username === user)
   }

獲取購物車中的物品 

創建訂單服務

當用戶完成購物後,他們可以訂購購物車中的商品。使用以下命令生成訂單服務:

nest generate service order/service/order –flat

創建訂單服務

現在打開運行上述命令生成的 order.service.ts 文件並添加以下代碼片段:

import { OrderEntity } from '../order.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartService } from 'src/cart/service/cart.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class OrderService {
   constructor(@InjectRepository(OrderEntity)
   private orderRepository: Repository<OrderEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private cartService: CartService) { }

訂單服務

您在上面的代碼片段中導入了創建 Nest.js 服務所需的模塊。由於您在購物車、用戶和產品實體之間創建了關係,因此我們也在此處導入了它們。然後,您創建了一個構造方法來將這些實體綁定到 OrderService 類。現在創建一個方法來訂購用戶購物車中的物品。

…
async order(user: string): Promise<any> {
       //find user existing orders
       const usersOrder = await this.orderRepository.find({ relations: ['user'] });
       const userOrder = usersOrder.filter(order => order.user?.username === user && order.pending === false);
       //find user's cart items
       const cartItems = await this.cartService.getItemsInCard(user)
       const subTotal = cartItems.map(item => item.total).reduce((acc, next) => acc + next);
       //get the authenticated user
       const authUser = await this.userRepository.findOne({ username: user })
       //if users has an pending order - add item to the list of order
       const cart = await cartItems.map(item => item.item);

       if (userOrder.length === 0) {
           const newOrder = await this.orderRepository.create({ subTotal });
           newOrder.items = cart
           newOrder.user = authUser;
           return await this.orderRepository.save(newOrder);


       } else {
           const existingOrder = userOrder.map(item => item)
           await this.orderRepository.update(existingOrder[0].id, { subTotal: existingOrder[0].subTotal + cart[0].price });
           return { message: "order modified" }
       }
   }
…

訂購用戶購物車中的商品

然後使用下面的代碼片段創建另一個方法來從數據庫的訂單數組中獲取用戶的訂單:

…
async getOrders(user: string): Promise<OrderEntity[]> {
       const orders = await this.orderRepository.find({ relations: ['user'] });
       return orders.filter(order => order.user?.username === user)
   }
}

獲取用戶訂單

最後,打開 order.module.ts 文件並使用以下代碼片段在 orderService 中訪問用戶、產品和購物車實體:

import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderEntity } from './order.entity';
import { ProductEntity } from 'src/product/product.entity';
import { CartService } from 'src/cart/service/cart.service';
import { CartEntity } from 'src/cart/cart.entity';
import { Users } from 'src/auth/user.entity';
import { ProductsService } from 'src/product/service/products.service';

@Module({
 imports: [TypeOrmModule.forFeature([OrderEntity, ProductEntity, CartEntity, Users])],
 controllers: [OrderController],
 providers: [OrderService, CartService, ProductsService]
})

使實體可訪問

創建應用控制器

成功創建應用服務後,讓我們為應用服務創建 API 路由。

創建身份驗證控制器

使用以下命令生成身份驗證控制器:

nest generate controller auth/controller/auth –flat

現在打開運行上述命令生成的auth.controller.ts文件,並使用以下代碼段配置身份驗證路由:

import { Controller, Request, Post, UseGuards, Body } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../service/auth.service';
import { Users } from '../user.entity';

@Controller('api/v1/auth/')
export class AuthController {
   constructor(private usersService: AuthService) { }
  
   @Post('signup')
   async signup(@Body() user: Users): Promise<Users> {
       return this.usersService.signup(user);
   }

   @UseGuards(AuthGuard('local'))
   @Post('login')
   async login(@Request() req) {
       return this.usersService.login(req.user)
   }
}

配置認證路由

創建產品控制器

配置 auth 控制器路由後,使用以下命令生成產品控制器:

nest generate controller product/controller/product –flat

產品控制器

打開運行上述命令生成的 product.controller.ts 文件,並使用以下代碼段配置產品路由:

import { Controller, Post, Get, Put, Delete, Param, Request, Body, UseGuards } from '@nestjs/common';
import {UpdateResult, DeleteResult} from 'typeorm';
import { ProductsService } from '../service/products.service';
import { ProductEntity } from '../product.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/products')
export class ProductsController {
 constructor(private productsService: ProductsService) { }

 @UseGuards(JwtAuthGuard)
 @Get()
 async GetAll(): Promise<ProductEntity[]> {
   return await this.productsService.getAll();

 }

 @UseGuards(JwtAuthGuard)
 @Post()
 async Create(@Request() req, @Body() product: ProductEntity): Promise<ProductEntity> {
   return await this.productsService.create(product, req.user);
 }


 @UseGuards(JwtAuthGuard)
 @Get(':id')
 async GetOne(@Param() id: number): Promise<ProductEntity> {
   return await this.productsService.getOne(id);

 }

 @UseGuards(JwtAuthGuard)
 @Put(':id')
 async Update(@Param() id: number, @Body() product: ProductEntity, @Request() req): Promise<UpdateResult> {
   return await this.productsService.update(id, product, req.user);

 }

 @UseGuards(JwtAuthGuard)
 @Delete(':id')
 async Delete(@Param() id: number, @Request() req): Promise<DeleteResult> {
   return await this.productsService.delete(id, req.user);

 }
}

產品路線

在上面的代碼片段中,您為產品服務定義了 CRUD 路由。我們使用UseGuard裝飾器傳遞您的JwtAuthGaurd來保護路由免受未經授權的用戶的攻擊。

創建購物車控制器

現在使用以下命令生成一個購物車控制器:

nest generate controller cart/controller/cart –flat

推車控制器

然後打開運行上述命令生成的cart.controller.ts文件,並使用以下代碼段配置購物車路線:

import { Controller, Post, Get,Request, Delete, Body, UseGuards } from '@nestjs/common';
import { CartService } from '../service/cart.service';
import { CartEntity } from '../cart.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/cart')
export class CartController {
   constructor(private cartService: CartService) { }

   @UseGuards(JwtAuthGuard)
   @Post()
   async AddToCart(@Body() body, @Request() req): Promise<void> {
       const { productId, quantity } = body
       return await this.cartService.addToCart(productId, quantity, req.user.username);
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getItemsInCart(@Request() req): Promise<CartEntity[]> {
       return await this.cartService.getItemsInCard(req.user.username);

   }
}

購物車路線

創建訂單控制器

配置購物車路線後,使用以下命令創建訂單控制器:

nest generate controller order/controller/order –flat

訂單控制器

然後打開運行上述命令生成的order.controlle.ts文件,並使用以下代碼段配置購物車路線:

import { Controller, Post, Get, Request, UseGuards } from '@nestjs/common';
import { OrderService } from '../service/order.service'
import { OrderEntity } from '../order.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/order')
export class OrderController {
   constructor(private orderService: OrderService) { }


   @UseGuards(JwtAuthGuard)
   @Post()
   async order(@Request() req): Promise<any> {
       return this.orderService.order(req.user.username)
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getOrders(@Request() req): Promise<OrderEntity[]> {
       return await this.orderService.getOrders(req.user.username)
   }
}

訂購路線

至此,您的所有 API 路由都已配置完畢。

測試申請

現在讓我們使用 Postman 測試它們。come 測試的結果如下所示。

用戶路線登錄路線產品路線。在添加產品之前複製訪問令牌並將其添加到請求標頭中。

隨意測試其他路線並在 Github 上使用代碼

將 Arctype 連接到 SQLite 數據庫

Arctype是一個使用起來很有趣的 SQL 客戶端和數據庫管理工具。它允許您擁有數據庫表的可視化表示,並且您可以使用 Arctype 在數據庫中執行 CRUD 操作目錄。將 Arctype 連接到 SQLite 數據庫是一個簡單的過程。要開始,請按照以下步驟操作:

首先,啟動 Arctype。您將看到如下所示的屏幕,允許您添加憑據。

單擊 SQLite 選項卡。這將打開下面的屏幕。

單擊選擇 SQLite 文件按鈕並導航到您的項目文件夾。選擇應用程序數據庫文件並按打開按鈕。按保存按鈕,您應該會看到您的數據庫實體,如下面的屏幕截圖所示。

結論

通過構建一個演示項目,我們學習瞭如何使用 Nestjs 和 SQLite 數據庫製作電子商務應用程序。首先,我們從 NestJS 的介紹開始。然後,我們創建了一個 NestJS 應用程序,使用 TypeORM 將應用程序連接到 SQLite 數據庫,並執行 CRUD 操作。現在您已經獲得了您所尋求的知識,可以隨意向應用程序添加額外的功能。

鏈接:https ://arctype.com/blog/sqlite-nestjs-tutorial/

#nestjs  #typeorm #sqlite 

使用 Nestjs、SQLite 和 TypeORM 構建電子商務 API

Создание API электронной коммерции с использованием Nestjs, SQLite

Введение

Nestjs — это передовая платформа Node.js для разработки эффективных, надежных и масштабируемых серверных приложений. Его легко интегрировать с базами данных NoSQL и SQL, такими как MongoDB, Yugabyte , SQLite , Postgres , MySQL и другими. Он поддерживает популярные объектно-реляционные преобразователи, такие как TypeORM Sequelize и Mongoose.

В этом руководстве мы создадим приложение электронной коммерции с помощью SQLite и TypeORM. Мы также рассмотрим Arctype , мощный клиент SQL и средство управления базами данных.

Давайте начнем!

Предпосылки

Этот учебник представляет собой практическую демонстрацию, которая поможет вам начать работу. Убедитесь, что вы выполнили следующие требования:

  • У вас установлен Node (>= 10.13.0, кроме v13)
  • У вас есть базовые знания Javascript
  • Arctype установлен

Настройка проекта

Чтобы начать работу с Nestjs, установите интерфейс командной строки Nestjs с помощью следующей команды:

npm i -g @nestjs/cli

Установка интерфейса командной строки NestJS

После завершения установки создайте проект Nestjs с помощью следующей команды:

nest new ecommerce

Создание нового проекта

Выберите свой npm в качестве менеджера пакетов, нажмите кнопку ввода и подождите, пока Nest установит необходимые пакеты для запуска этого приложения.

После завершения установки измените каталог на папку проекта с помощью следующей команды:

cd ecommerce

Изменить каталог

Затем откройте каталог проекта в своем любимом текстовом редакторе или IDE, откройте новый терминал и запустите сервер в режиме разработки (это активирует горячую перезагрузку и позволит нам увидеть возможные ошибки на консоли) с помощью следующей команды:

npm run start:dev

Запуск сервера

Установить зависимости

Когда сервер запущен и работает, откройте новое окно терминала, чтобы не выходить из сервера. Это позволит вам увидеть эффект изменений, внесенных в кодовую базу на протяжении всего этого руководства.

Теперь установите следующие зависимости:

Вы можете сделать это с помощью команды ниже:

npm install --save @nestjs/passport passport passport-local @nestjs/jwt passport-jwt @nestjs/typeorm typeorm sqlite3 bcrypt

Установка зависимостей

Затем установите зависимости dev с помощью команды ниже:

npm install --save-dev @types/passport-local @types/passport-jwt @types/bcrypt

Установка зависимостей для разработчиков

Вы можете выпить чашечку кофе, пока npm устанавливает пакеты. Как только установка будет завершена, давайте запачкаем руки.

Создание модулей приложений

Со всеми необходимыми пакетами, необходимыми для запуска приложения, приступим к созданию модулей приложения. Чтобы создать чистое и простое в обслуживании приложение, вы создадите отдельные модули для всех функций, реализованных в этом приложении. Поскольку это приложение для электронной коммерции, у вас будет аутентификация , корзина , товары и заказы . Все это будет в своих отдельных модулях. Начнем с модуля аутентификации.

Создать модуль аутентификации

Создайте модуль аутентификации с помощью следующей команды:

nest g module auth

Модуль аутентификации

Приведенная выше команда создает папку auth в каталоге src проекта с необходимыми шаблонами и регистрирует модуль в корневом модуле проекта ( файл app.module.ts ).

Далее создайте товар, корзину, заказ, модуль с помощью команды ниже:

#Create a product module
nest g module product

#Create cart module
nest g module cart

#Create cart module
nest g module order

Дополнительные модули

Вышеупомянутое создаст папку продукта, корзины и заказа в папке src проекта с основными шаблонами и зарегистрирует эти модули в корневом модуле приложения проекта.

Настройка баз данных TypeORM и SQLite

Установив модули приложения, настройте TypeORM для подключения вашего приложения к базе данных SQLite и создания объектов вашего модуля. Для начала откройте app.module.ts и настройте базу данных SQLite с помощью приведенных ниже фрагментов кода:

imports: [
 …
 TypeOrmModule.forRoot({
   type :"sqlite",
   database: "shoppingDB",
   entities: [__dirname + "/**/*.entity{.ts,.js}"],
   synchronize: true
 })
],
…

Настройка TypeORM

В приведенном выше фрагменте кода вы подключили приложение к базе данных SQLite с помощью TypeORM forRoot, указав тип базы данных, имя базы данных и место, где Nestjs может найти объекты модели.

После обновления сервера вы должны увидеть файл shoppingDB , созданный в корневом каталоге этого проекта.

Создание моделей сущностей приложения

Настроив базу данных, давайте создадим модели сущностей для наших модулей приложения. Начнем с модуля авторизации . Создайте файл сущности в папке модуля аутентификации с помощью следующей команды:

nest generate class auth/user.entity –flat

Создайте файл сущности

Затем добавьте фрагмент кода ниже, чтобы определить свойства пользовательской таблицы с помощью приведенного ниже фрагмента кода:

import { Entity, OneToOne, JoinColumn,Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, OneToMany } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'
import { OrderEntity } from 'src/order/order.entity'

@Entity()
export class Users {
   @PrimaryGeneratedColumn()
   id: number

   @Column()
   username: string

   @Column()
   password: string

   @Column()
   role: string

   @CreateDateColumn()
   createdAt : String

   @UpdateDateColumn()
   updtedAt : String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]

   @OneToOne(type => OrderEntity, order => order.id)
   @JoinColumn()
   order : OrderEntity;
}

Определение свойств

Во фрагменте кода вы импортировали декораторы, необходимые для настройки таблицы базы данных. Вы также импортировали классы cartEntity и orderEntity , которые вскоре создадите. Используя декоратор typeorm , мы определили свойства базы данных модели пользователя. Наконец, мы создали отношения « один к одному » и « один ко многим » между сущностью пользователей и объектами cartEntity и orderEntity. Таким образом, вы можете связать элемент корзины с пользователем. То же самое относится и к заказу пользователя.

Затем создайте класс сущности продукта с помощью следующей команды:

nest generate class product/product.entity –flat

Создание класса сущности продукта

Приведенная выше команда создаст файл product.entity.ts в папке модуля продуктов.

Теперь настройте свойства таблицы продуктов с помощью приведенного ниже фрагмента кода:

import { Entity, JoinColumn, OneToMany, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'

@Entity()
export class ProductEntity {
   @PrimaryGeneratedColumn("uuid")
   id!: number

   @Column()
   name: string

   @Column()
   price: number

   @Column()
   quantity: string

   @CreateDateColumn()
   createdAt: String

   @UpdateDateColumn()
   updtedAt: String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]
}

Определение свойств

В приведенном выше фрагменте кода мы настроили свойства таблицы продуктов и создали связь « один ко многим » с сущностью корзины.

Затем создайте объект корзины с помощью команды ниже:

nest generate class cart/cart.entity –flat

Сущность корзины

Приведенная выше команда создаст файл cart.entity.ts в папке модуля корзины. Теперь добавьте приведенный ниже фрагмент кода в созданный вами файл для настройки свойств таблицы корзины.

import { Entity, OneToOne,ManyToOne, JoinColumn, Column, PrimaryGeneratedColumn } from 'typeorm'
import { OrderEntity } from 'src/order/order.entity'
import { ProductEntity } from 'src/product/product.entity'
import { Users } from 'src/auth/user.entity'

@Entity()
export class CartEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @Column()
   total: number

   @Column()
   quantity: number
  
   @ManyToOne(type => ProductEntity, order => order.id)
   @JoinColumn()
   item: ProductEntity

   @ManyToOne(type => Users, user => user.username)
   @JoinColumn()
   user: Users
}

Определение свойств

В приведенном выше фрагменте кода вы настроили свойства таблицы корзины, создали связь « многие к одному » между сущностью корзины и связь « многие к одному » с сущностью пользователя.

Наконец, создайте объект заказа с помощью команды ниже:

nest generate class order/order.entity –flat

Сущность заказа

Приведенная выше команда создаст файл order.entity.ts в папке модуля заказа. Откройте order.entity.ts и настройте таблицу базы данных с помощью следующей команды:

import { Entity, OneToMany, JoinColumn, OneToOne, Column, PrimaryGeneratedColumn } from 'typeorm'
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Entity()
export class OrderEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @OneToMany(type => ProductEntity, item => item.id)
   items: ProductEntity[];

   @OneToOne(type => Users, user => user.username)
   @JoinColumn()
   user : Users;

   @Column()
   subTotal: number

   @Column({ default: false })
   pending: boolean

}

Определение свойств

В приведенном выше фрагменте кода вы создали отношение «один к одному» между сущностью «пользователи» и связь «один ко многим» с сущностью «продукты».

На этом этапе объекты вашей базы данных настроены и подключены. Теперь создайте свою бизнес-логику для хранения записей об этих объектах.

Создание служб приложений

Теперь создайте службы для модулей в этом приложении. Здесь вы позволите администратору добавлять продукты в таблицу продуктов, аутентифицировать пользователей, разрешать пользователям добавлять продукты в магазине в корзину и заказывать продукт через свою корзину.

Создайте службу аутентификации

Чтобы создать службу аутентификации, выполните приведенную ниже команду, чтобы сгенерировать службу для модуля аутентификации.

nest generate service auth/service/auth --flat

Создание службы авторизации

Приведенная выше команда создаст файл auth.service.ts в папке src/auth/service . Теперь откройте файл auth.service.ts и добавьте приведенный ниже фрагмент кода:

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from '../user.entity';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(@InjectRepository(Users) private userRepository: Repository<Users>, private jwt: JwtService) { }

   }

Импорт модулей

В приведенном выше фрагменте кода вы импортировали модули InjectRepository, Repository decorator, JwtService и bcrypt . Затем с помощью декоратора InjectRepository вы сделали класс сущности User доступным в службе аутентификации, предоставив метод для выполнения операций CRUD в вашей сущности User.

Затем создайте метод регистрации , чтобы пользователи могли зарегистрироваться в приложении с помощью приведенного ниже фрагмента кода:

async signup(user: Users): Promise<Users> {
       const salt = await bcrypt.genSalt();
       const hash = await bcrypt.hash(user.password, salt);
       user.password = hash
       return await this.userRepository.save(user);
   }

Метод регистрации

Теперь создайте метод validateUser для проверки сведений о пользователях и метод входа в систему для создания токена jwt для аутентифицированного пользователя.

 …
 async validateUser(username: string, password: string): Promise<any> {
       const foundUser = await this.userRepository.findOne({ username });
       if (foundUser) {
           if (await bcrypt.compare(password, foundUser.password)) {
               const { password, ...result } = foundUser
               return result;
           }

           return null;
       }
       return null

   }
   async login(user: any) {
       const payload = { username: user.username, sub: user.id, role:user.role };

       return {
           access_token: this.jwt.sign(payload),
       };
   }

Проверка пользователя

Теперь мы можем реализовать нашу стратегию локальной аутентификации Passport . Создайте файл с именем local.strategy.ts в папке модуля аутентификации и добавьте следующий код:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './service/auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
   constructor(private authService: AuthService) {
       super();
   }

   async validate(username: string, password: string): Promise<any> {
  
       const foundUser = await this.authService.validateUser(username, password);
       if (!foundUser) {
           throw new UnauthorizedException();
       }
       return foundUser;
   }
}

Стратегия локальной аутентификации

В приведенном выше фрагменте кода вы реализовали стратегию локального паспорта. Параметры конфигурации отсутствуют, поэтому наш конструктор просто вызывает super() без объекта параметров.

Вы также реализовали метод validate() . Passport вызовет функцию проверки для каждой стратегии, используя соответствующий набор параметров для конкретной стратегии. Для локальной стратегии Passport ожидает метод validate() со следующей подписью: validate(имя пользователя: строка, пароль: строка): любой.

Затем создайте файл jwt-auth.guard.ts в папке модуля аутентификации и определите пользовательскую защиту аутентификации с помощью приведенного ниже фрагмента кода:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

Определение пользовательской защиты авторизации

Вы будете использовать AuthGuard, созданный во фрагменте кода, для защиты маршрутов API от неавторизованных пользователей.

Теперь создайте файл jwt-strategy в папке модуля аутентификации для аутентификации пользователей и создания токенов jwt для вошедших в систему пользователей с помощью приведенного ниже фрагмента кода:

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
 constructor() {
   super({
     jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
     ignoreExpiration: false,
     secretOrKey: jwtConstants.secret,
   });
 }

 async validate(payload: any) {
   return { userId: payload.sub, username: payload.username, role: payload.role };
 }
}

Файл стратегии

Затем настройте модуль jwt в файле auth.module.ts в папке модуля auth. Перед этим создайте файл Constants.ts в той же папке модуля аутентификации, чтобы определить секрет jwt с помощью приведенного ниже фрагмента кода:

export const jwtConstants = {
   secret: 'wjeld-djeuedw399e3-uejheuii33-4jrjjejei3-rjdjfjf',
}

Секрет (используется для целей тестирования)

Вы можете сгенерировать более безопасный секрет jwt в рабочей среде, но мы будем использовать его для демонстрации.

Теперь импортируйте все необходимые модули в файл auth.module.ts с помощью приведенного ниже фрагмента кода:

…
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './user.entity';
…

Импорт модулей

Затем в массиве импорта настройте jwt с помощью приведенного ниже фрагмента кода:

…
imports: [
   PassportModule,
   JwtModule.register({
     secret: jwtConstants.secret,
     signOptions: { expiresIn: '60m' },
   }),
…

Настройка jwt

В приведенном выше фрагменте кода мы добавляем пакет PassModule , чтобы паспорт мог обрабатывать аутентификацию пользователей и настраивать jwt с использованием метода регистрации JwtModule . Мы передаем секрет, который мы создали в файле констант, и указываем время истечения срока действия сгенерированного токена (вы можете уменьшить или увеличить время в зависимости от варианта использования).

Создайте услугу продукта

С настройкой службы аутентификации создайте службу продукта с помощью следующей команды:

nest generate service product/service/product

Сервис продукта

Теперь откройте файл product.service.ts , сгенерированный приведенной выше командой, в модуле продукта и добавьте фрагменты кода ниже:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ProductEntity } from '../product.entity';
import { Repository, UpdateResult, DeleteResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class ProductsService {
   constructor(@InjectRepository(ProductEntity) private productRepository: Repository<ProductEntity>) { }
  
   async getAll(): Promise<ProductEntity[]> {
       return await this.productRepository.find()
   }

   async create(product: ProductEntity, user: Users): Promise<ProductEntity> {
       if (user.role == 'admin') {
           return await this.productRepository.save(product);

       }
       throw new UnauthorizedException();

   }

   async getOne(id: number): Promise<ProductEntity> {
       return this.productRepository.findOne(id);
   }

   async update(id: number, product: ProductEntity, user: Users): Promise<UpdateResult> {
       if (user.role == 'admin') {
           return await this.productRepository.update(id, product);
       }
       throw new UnauthorizedException();
   }

   async delete(id: number, user: Users): Promise<DeleteResult> {
       if (user.role == 'admin') {
           return await this.productRepository.delete(id);
       }
       throw new UnauthorizedException();
   }
  
}

Создание CRUD-сервисов

В приведенном выше фрагменте мы создали наши службы CRUD. Пользователи не могут использовать методы create , update и delete . Только администратор может создать продукт, удалить или обновить продукт.

Теперь откройте файл product.module.ts и сделайте сущность продукта доступной с помощью приведенного ниже фрагмента кода:

imports: [TypeOrmModule.forFeature([ProductEntity])],

Делаем объект продукта доступным

Создать службу корзины

На этом этапе администратор может добавлять продукты в базу данных, а аутентифицированные пользователи могут видеть все доступные продукты. Теперь давайте разрешим пользователям добавлять понравившиеся товары в корзину. Для начала создайте службу корзины с помощью следующей команды:

nest generate service cart/service/cart –flat

Обслуживание корзины

Затем откройте файл cart.service.ts, сгенерированный командой, и добавьте приведенный ниже фрагмент кода:

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartEntity } from '../cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class CartService {
   constructor(
       @InjectRepository(CartEntity)
       private cartRepository: Repository<CartEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private productsService: ProductsService,
   ) { }
   …

Сервисный файл корзины

Вы импортировали модули, необходимые для создания службы Nest.js, в приведенном выше фрагменте кода. Мы также импортировали их сюда, так как вы создали отношения между корзиной, пользователем и объектами продукта. Затем вы создаете метод конструктора для привязки этих объектов к классу CartService. Теперь создайте метод для добавления товара в корзину.

async addToCart(productId: number, quantity: number, user: string): Promise<any> {
       const cartItems = await this.cartRepository.find({ relations: ["item",'user'] });
       const product = await this.productsService.getOne(productId);
       const authUser = await this.userRepository.findOne({ username: user })
      
       //Confirm the product exists.
       if (product) {
           //confirm if user has item in cart
           const cart = cartItems.filter(
               (item) => item.item.id === productId && item.user.username === user,
           );
           if (cart.length < 1) {

               const newItem = this.cartRepository.create({ total: product.price * quantity, quantity });
               newItem.user = authUser;
               newItem.item = product;
               this.cartRepository.save(newItem)


               return await this.cartRepository.save(newItem)
           } else {
               //Update the item quantity
               const quantity = (cart[0].quantity += 1);
               const total = cart[0].total * quantity;

               return await this.cartRepository.update(cart[0].id, { quantity, total });
           }
       }
       return null;
   }

Добавление товара в корзину

В приведенном выше фрагменте кода вы создали метод addToCart , который принимает productId , количество и пользователя в качестве аргументов. Затем проверьте, есть ли у пользователя товар в корзине. Если это так, вы увеличиваете количество и обновляете общую цену этого товара. В противном случае вы добавляете товар в корзину пользователя.

Затем сделайте объект cartEntity , productEntity User и productService доступными в cartService , зарегистрировав их в файле cart.module.ts с помощью приведенного ниже фрагмента кода:

…
import { CartEntity } from './cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Module({
 imports: [TypeOrmModule.forFeature([CartEntity, ProductEntity, Users])],
 providers: [CartService, ProductsService],
 …
})

Регистрация услуг

Наконец, создайте метод getItemsInCart, который принимает пользователя в качестве аргумента для возврата всех тележек, принадлежащих конкретному пользователю.

async getItemsInCard(user: string): Promise<CartEntity[]> {
       const userCart = await this.cartRepository.find({ relations: ["item",'user'] });
       return (await userCart).filter(item => item.user.username === user)
   }

Получение товаров в корзине 

Создать службу заказов

Когда пользователи сделали покупки, они могут заказать товары в своей корзине. Создайте службу заказа с помощью следующей команды:

nest generate service order/service/order –flat

Создание службы заказа

Теперь откройте файл order.service.ts, сгенерированный при выполнении приведенной выше команды, и добавьте фрагмент кода ниже:

import { OrderEntity } from '../order.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartService } from 'src/cart/service/cart.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class OrderService {
   constructor(@InjectRepository(OrderEntity)
   private orderRepository: Repository<OrderEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private cartService: CartService) { }

Служба заказа

Вы импортировали модули, необходимые для создания службы Nest.js, в приведенном выше фрагменте кода. Мы также импортировали их сюда, так как вы создали отношения между корзиной, пользователем и объектами продукта. Затем вы создали метод конструктора для привязки этих сущностей к классу OrderService. Теперь создайте метод для заказа товаров в корзине пользователя.

…
async order(user: string): Promise<any> {
       //find user existing orders
       const usersOrder = await this.orderRepository.find({ relations: ['user'] });
       const userOrder = usersOrder.filter(order => order.user?.username === user && order.pending === false);
       //find user's cart items
       const cartItems = await this.cartService.getItemsInCard(user)
       const subTotal = cartItems.map(item => item.total).reduce((acc, next) => acc + next);
       //get the authenticated user
       const authUser = await this.userRepository.findOne({ username: user })
       //if users has an pending order - add item to the list of order
       const cart = await cartItems.map(item => item.item);

       if (userOrder.length === 0) {
           const newOrder = await this.orderRepository.create({ subTotal });
           newOrder.items = cart
           newOrder.user = authUser;
           return await this.orderRepository.save(newOrder);


       } else {
           const existingOrder = userOrder.map(item => item)
           await this.orderRepository.update(existingOrder[0].id, { subTotal: existingOrder[0].subTotal + cart[0].price });
           return { message: "order modified" }
       }
   }
…

Заказ товаров в корзине пользователя

Затем создайте еще один метод для получения заказа пользователя из массива заказов из базы данных с помощью приведенного ниже фрагмента кода:

…
async getOrders(user: string): Promise<OrderEntity[]> {
       const orders = await this.orderRepository.find({ relations: ['user'] });
       return orders.filter(order => order.user?.username === user)
   }
}

Получение заказов пользователя

Наконец, откройте файл order.module.ts и сделайте объекты пользователей, продуктов и корзины доступными в orderService с помощью приведенного ниже фрагмента кода:

import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderEntity } from './order.entity';
import { ProductEntity } from 'src/product/product.entity';
import { CartService } from 'src/cart/service/cart.service';
import { CartEntity } from 'src/cart/cart.entity';
import { Users } from 'src/auth/user.entity';
import { ProductsService } from 'src/product/service/products.service';

@Module({
 imports: [TypeOrmModule.forFeature([OrderEntity, ProductEntity, CartEntity, Users])],
 controllers: [OrderController],
 providers: [OrderService, CartService, ProductsService]
})

Делаем объекты доступными

Создание контроллеров приложений

После успешного создания служб приложений давайте создадим маршруты API для служб приложений.

Создайте контроллер авторизации

Создайте контроллер авторизации с помощью следующей команды:

nest generate controller auth/controller/auth –flat

Теперь откройте файл auth.controller.ts , сгенерированный при выполнении вышеуказанной команды, и настройте маршруты авторизации с помощью приведенного ниже фрагмента кода:

import { Controller, Request, Post, UseGuards, Body } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../service/auth.service';
import { Users } from '../user.entity';

@Controller('api/v1/auth/')
export class AuthController {
   constructor(private usersService: AuthService) { }
  
   @Post('signup')
   async signup(@Body() user: Users): Promise<Users> {
       return this.usersService.signup(user);
   }

   @UseGuards(AuthGuard('local'))
   @Post('login')
   async login(@Request() req) {
       return this.usersService.login(req.user)
   }
}

Настройка маршрутов авторизации

Создайте контроллер продукта

С настроенными маршрутами контроллера авторизации создайте контроллер продукта с помощью следующей команды:

nest generate controller product/controller/product –flat

Контроллер продукта

Откройте файл product.controller.ts, сгенерированный при выполнении приведенной выше команды, и настройте маршруты продуктов с помощью приведенного ниже фрагмента кода:

import { Controller, Post, Get, Put, Delete, Param, Request, Body, UseGuards } from '@nestjs/common';
import {UpdateResult, DeleteResult} from 'typeorm';
import { ProductsService } from '../service/products.service';
import { ProductEntity } from '../product.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/products')
export class ProductsController {
 constructor(private productsService: ProductsService) { }

 @UseGuards(JwtAuthGuard)
 @Get()
 async GetAll(): Promise<ProductEntity[]> {
   return await this.productsService.getAll();

 }

 @UseGuards(JwtAuthGuard)
 @Post()
 async Create(@Request() req, @Body() product: ProductEntity): Promise<ProductEntity> {
   return await this.productsService.create(product, req.user);
 }


 @UseGuards(JwtAuthGuard)
 @Get(':id')
 async GetOne(@Param() id: number): Promise<ProductEntity> {
   return await this.productsService.getOne(id);

 }

 @UseGuards(JwtAuthGuard)
 @Put(':id')
 async Update(@Param() id: number, @Body() product: ProductEntity, @Request() req): Promise<UpdateResult> {
   return await this.productsService.update(id, product, req.user);

 }

 @UseGuards(JwtAuthGuard)
 @Delete(':id')
 async Delete(@Param() id: number, @Request() req): Promise<DeleteResult> {
   return await this.productsService.delete(id, req.user);

 }
}

Маршруты продуктов

В приведенном выше фрагменте кода вы определили маршруты CRUD для службы продукта. Мы использовали декоратор UseGuard , передавая ваш JwtAuthGaurd , чтобы защитить маршруты от неавторизованных пользователей.

Создайте контроллер корзины

Теперь сгенерируйте контроллер тележки с помощью следующей команды:

nest generate controller cart/controller/cart –flat

Контроллер тележки

Затем откройте файл cart.controller.ts , сгенерированный при выполнении приведенной выше команды, и настройте маршруты корзины с помощью приведенного ниже фрагмента кода:

import { Controller, Post, Get,Request, Delete, Body, UseGuards } from '@nestjs/common';
import { CartService } from '../service/cart.service';
import { CartEntity } from '../cart.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/cart')
export class CartController {
   constructor(private cartService: CartService) { }

   @UseGuards(JwtAuthGuard)
   @Post()
   async AddToCart(@Body() body, @Request() req): Promise<void> {
       const { productId, quantity } = body
       return await this.cartService.addToCart(productId, quantity, req.user.username);
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getItemsInCart(@Request() req): Promise<CartEntity[]> {
       return await this.cartService.getItemsInCard(req.user.username);

   }
}

Карты маршрутов

Создайте контроллер заказа

С настроенными маршрутами корзины создайте контроллер заказов с помощью следующей команды:

nest generate controller order/controller/order –flat

Контроллер заказов

Затем откройте файл order.controlle.ts , сгенерированный при выполнении приведенной выше команды, и настройте маршруты корзины с помощью приведенного ниже фрагмента кода:

import { Controller, Post, Get, Request, UseGuards } from '@nestjs/common';
import { OrderService } from '../service/order.service'
import { OrderEntity } from '../order.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/order')
export class OrderController {
   constructor(private orderService: OrderService) { }


   @UseGuards(JwtAuthGuard)
   @Post()
   async order(@Request() req): Promise<any> {
       return this.orderService.order(req.user.username)
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getOrders(@Request() req): Promise<OrderEntity[]> {
       return await this.orderService.getOrders(req.user.username)
   }
}

Заказать маршруты

На данный момент все ваши маршруты API настроены.

Тестовое приложение

Теперь давайте проверим их с помощью Postman. Результаты проходных тестов показаны ниже.

Маршрут пользователя Маршрутвхода Маршрутпродукта. Скопируйте токен доступа и добавьте его в заголовок запроса перед добавлением продукта.

Не стесняйтесь тестировать другие маршруты и экспериментировать с кодом здесь, на Github.

Подключение Arctype к базе данных SQLite

Arctype — это клиент SQL и инструмент управления базой данных, которым интересно пользоваться. Это позволяет вам иметь визуальное представление таблиц вашей базы данных, и вы можете выполнять каталог операций CRUD в своей базе данных, используя Arctype. Подключение Arctype к базе данных SQLite — простой процесс. Чтобы начать, выполните следующие действия:

Сначала запустите Arctype. Вы увидите экран, подобный приведенному ниже, позволяющий добавить свои учетные данные.

Нажмите на вкладку SQLite. Это вызовет экран ниже.

Нажмите кнопку « Выбрать файл SQLite» и перейдите в папку проекта. Выберите файл базы данных приложения и нажмите кнопку открытия . Нажмите кнопку «Сохранить», и вы должны увидеть объекты вашей базы данных, как показано на снимке экрана ниже.

Вывод

Создав демонстрационный проект, мы узнали, как создать приложение для электронной коммерции, используя Nestjs и базу данных SQLite. Во-первых, мы начали с введения NestJS. Затем мы создали приложение NestJS, подключили его к базе данных SQLite с помощью TypeORM и выполнили операции CRUD. Теперь, когда вы получили необходимые знания, не стесняйтесь добавлять в приложение дополнительные функции.

Ссылка: https://arctype.com/blog/sqlite-nestjs-tutorial/

#nestjs  #typeorm #sqlite 

Создание API электронной коммерции с использованием Nestjs, SQLite
Thierry  Perret

Thierry Perret

1657522201

Création D'une API De Commerce électronique à L'aide De Nestjs, SQLite

Introduction

Nestjs est un framework Node.js de pointe pour le développement d'applications côté serveur efficaces, fiables et évolutives. Il est simple à intégrer aux bases de données NoSQL et SQL telles que MongoDB, Yugabyte , SQLite , Postgres , MySQL et autres. Il prend en charge les mappeurs relationnels objet populaires tels que TypeORM Sequelize et Mongoose.

Dans ce tutoriel, nous allons créer une application e-commerce avec SQLite et TypeORM. Nous examinerons également Arctype , un puissant client SQL et un outil de gestion de base de données.

Commençons!

Conditions préalables

Ce didacticiel est une démonstration pratique pour vous aider à démarrer. Assurez-vous que vous remplissez les conditions suivantes :

  • Vous avez Node (>= 10.13.0, sauf pour la v13) installé
  • Vous avez une connaissance de base de Javascript
  • Arctype installé

Configuration du projet

Pour commencer avec Nestjs, installez la CLI Nestjs avec la commande ci-dessous :

npm i -g @nestjs/cli

Installation de la CLI NestJS

Une fois l'installation terminée, créez un projet Nestjs avec la commande ci-dessous :

nest new ecommerce

Créer un nouveau projet

Choisissez votre npm comme gestionnaire de packages, appuyez sur le bouton Entrée et attendez que Nest installe les packages requis pour exécuter cette application.

Une fois l'installation terminée, changez le répertoire dans le dossier du projet avec la commande ci-dessous :

cd ecommerce

Changer de répertoire

Ouvrez ensuite le répertoire du projet dans votre éditeur de texte ou IDE préféré, ouvrez un nouveau terminal et exécutez le serveur en mode développement (cela activera le rechargement à chaud et nous permettra de voir les éventuelles erreurs sur la console) avec la commande ci-dessous :

npm run start:dev

Démarrage du serveur

Installer les dépendances

Avec le serveur opérationnel, ouvrez une nouvelle fenêtre de terminal afin de ne pas quitter le serveur. Cela vous permettra de voir l'effet des modifications apportées à la base de code tout au long de ce didacticiel.

Installez maintenant les dépendances suivantes :

Vous pouvez le faire avec la commande ci-dessous :

npm install --save @nestjs/passport passport passport-local @nestjs/jwt passport-jwt @nestjs/typeorm typeorm sqlite3 bcrypt

Installation des dépendances

Installez ensuite les dépendances de développement avec la commande ci-dessous :

npm install --save-dev @types/passport-local @types/passport-jwt @types/bcrypt

Installation des dépendances de développement

Vous pouvez prendre une tasse de café pendant que le npm installe les packages. Une fois l'installation terminée, mettons les mains dans le cambouis.

Créer des modules d'application

Avec tous les packages nécessaires pour exécuter l'application, procédons à la création des modules d'application. Pour créer une application propre et facile à entretenir, vous allez générer des modules distincts pour toutes les fonctionnalités implémentées dans cette application. Puisqu'il s'agit d'une application de commerce électronique, vous aurez une authentification , un panier , des produits et des commandes . Tous ceux-ci seront dans leurs propres modules séparés. Commençons par le module d'authentification.

Créer un module d'authentification

Générez un module d'authentification avec la commande ci-dessous :

nest g module auth

Le module d'authentification

La commande ci-dessus crée un dossier auth dans le répertoire src du projet avec les passe-partout nécessaires et enregistre le module dans le module racine du projet ( fichier app.module.ts ).

Créez ensuite un produit, un panier, une commande, un module avec la commande ci-dessous :

#Create a product module
nest g module product

#Create cart module
nest g module cart

#Create cart module
nest g module order

Modules supplémentaires

Ce qui précède créera un produit, un panier et un dossier de commande dans le dossier src du projet avec les passe-partout de base et enregistrera ces modules dans le module d'application racine du projet.

Configuration des bases de données TypeORM et SQLite

Une fois les modules d'application installés, configurez TypeORM pour connecter votre application à la base de données SQLite et créez vos entités de module. Pour commencer, ouvrez app.module.ts et configurez votre base de données SQLite avec les extraits de code ci-dessous :

imports: [
 …
 TypeOrmModule.forRoot({
   type :"sqlite",
   database: "shoppingDB",
   entities: [__dirname + "/**/*.entity{.ts,.js}"],
   synchronize: true
 })
],
…

Configuration de TypeORM

Dans l'extrait de code ci-dessus, vous avez connecté l'application à une base de données SQLite à l'aide de TypeORM forRoot, en spécifiant le type de base de données, le nom de la base de données et l'emplacement où Nestjs peut trouver les entités de modèle.

Une fois le serveur actualisé, vous devriez voir un fichier shoppingDB créé dans le répertoire racine de ce projet.

Créer des modèles d'entité d'application

Avec la configuration de la base de données, créons les modèles d'entité pour nos modules d'application. Nous allons commencer par le module auth . Générez un fichier d'entité dans le dossier du module auth avec la commande ci-dessous :

nest generate class auth/user.entity –flat

Créer le fichier d'entité

Ajoutez ensuite l'extrait de code ci-dessous pour définir les propriétés de la table utilisateur avec l'extrait de code ci-dessous :

import { Entity, OneToOne, JoinColumn,Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, OneToMany } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'
import { OrderEntity } from 'src/order/order.entity'

@Entity()
export class Users {
   @PrimaryGeneratedColumn()
   id: number

   @Column()
   username: string

   @Column()
   password: string

   @Column()
   role: string

   @CreateDateColumn()
   createdAt : String

   @UpdateDateColumn()
   updtedAt : String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]

   @OneToOne(type => OrderEntity, order => order.id)
   @JoinColumn()
   order : OrderEntity;
}

Définir les propriétés

Dans l'extrait de code, vous avez importé les décorateurs requis pour configurer votre table de base de données. Vous avez également importé les classes cartEntity et orderEntity que vous allez créer sous peu. À l'aide du décorateur typeorm , nous avons défini les propriétés de la base de données du modèle de l'utilisateur. Enfin, nous avons créé des relations un-à-un et un-à-plusieurs entre l'entité users et les cartEntity et orderEntity. De cette façon, vous pouvez associer un article du panier à un utilisateur. Il en va de même pour la commande de l'utilisateur.

Ensuite, créez la classe d'entité produit avec la commande ci-dessous :

nest generate class product/product.entity –flat

Création de la classe d'entité produit

La commande ci-dessus générera un fichier product.entity.ts dans le dossier du module produits.

Configurez maintenant les propriétés de la table des produits avec l'extrait de code ci-dessous :

import { Entity, JoinColumn, OneToMany, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'

@Entity()
export class ProductEntity {
   @PrimaryGeneratedColumn("uuid")
   id!: number

   @Column()
   name: string

   @Column()
   price: number

   @Column()
   quantity: string

   @CreateDateColumn()
   createdAt: String

   @UpdateDateColumn()
   updtedAt: String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]
}

Définir les propriétés

Dans l'extrait de code ci-dessus, nous avons configuré les propriétés de la table des produits et créé une relation un-à-plusieurs avec l'entité panier.

Créez ensuite l'entité panier avec la commande ci-dessous :

nest generate class cart/cart.entity –flat

L'entité panier

La commande ci-dessus générera un fichier cart.entity.ts dans le dossier du module cart. Ajoutez maintenant l'extrait de code ci-dessous au fichier que vous avez créé pour configurer les propriétés du tableau du panier.

import { Entity, OneToOne,ManyToOne, JoinColumn, Column, PrimaryGeneratedColumn } from 'typeorm'
import { OrderEntity } from 'src/order/order.entity'
import { ProductEntity } from 'src/product/product.entity'
import { Users } from 'src/auth/user.entity'

@Entity()
export class CartEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @Column()
   total: number

   @Column()
   quantity: number
  
   @ManyToOne(type => ProductEntity, order => order.id)
   @JoinColumn()
   item: ProductEntity

   @ManyToOne(type => Users, user => user.username)
   @JoinColumn()
   user: Users
}

Définir les propriétés

Dans l'extrait de code ci-dessus, vous avez configuré les propriétés de la table du panier, créé une relation plusieurs à un entre l'entité du panier et une relation plusieurs à un avec l'entité de l'utilisateur.

Enfin, créez l'entité de commande avec la commande ci-dessous :

nest generate class order/order.entity –flat

L'entité de commande

La commande ci-dessus générera un fichier order.entity.ts dans le dossier du module de commande. Ouvrez order.entity.ts et configurez la table de base de données avec la commande ci-dessous :

import { Entity, OneToMany, JoinColumn, OneToOne, Column, PrimaryGeneratedColumn } from 'typeorm'
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Entity()
export class OrderEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @OneToMany(type => ProductEntity, item => item.id)
   items: ProductEntity[];

   @OneToOne(type => Users, user => user.username)
   @JoinColumn()
   user : Users;

   @Column()
   subTotal: number

   @Column({ default: false })
   pending: boolean

}

Définir les propriétés

Dans l'extrait de code ci-dessus, vous avez créé une relation un-à-un entre l'entité utilisateurs et une relation un-à-plusieurs avec l'entité produits.

À ce stade, vos entités de base de données sont définies et connectées. Créez maintenant votre logique métier pour stocker des enregistrements sur ces entités.

Créer des services d'application

Créez maintenant les services pour les modules de cette application. Ici, vous autorisez l'administrateur à ajouter des produits au tableau des produits, à authentifier les utilisateurs, à autoriser les utilisateurs à ajouter les produits du magasin au panier et à commander le produit via leur panier.

Créer le service d'authentification

Pour créer le service d'authentification, exécutez la commande ci-dessous afin de générer le service pour le module d'authentification.

nest generate service auth/service/auth --flat

Création du service d'authentification

La commande ci-dessus générera un fichier auth.service.ts dans le dossier src/auth/service . Ouvrez maintenant le fichier auth.service.ts et ajoutez l'extrait de code ci-dessous :

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from '../user.entity';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(@InjectRepository(Users) private userRepository: Repository<Users>, private jwt: JwtService) { }

   }

Importation de modules

Dans l'extrait de code ci-dessus, vous avez importé les modules InjectRepository, Repository decorator, JwtService et bcrypt . Ensuite, à l'aide du décorateur InjectRepository , vous avez rendu la classe d'entité User disponible dans le service d'authentification, en fournissant la méthode pour effectuer des opérations CRUD dans votre entité User.

Créez ensuite une méthode d' inscription pour permettre aux utilisateurs de s'inscrire dans l'application avec l'extrait de code ci-dessous :

async signup(user: Users): Promise<Users> {
       const salt = await bcrypt.genSalt();
       const hash = await bcrypt.hash(user.password, salt);
       user.password = hash
       return await this.userRepository.save(user);
   }

La méthode d'inscription

Créez maintenant la méthode validateUser pour valider les détails des utilisateurs et la méthode de connexion pour générer un jeton jwt pour l'utilisateur authentifié.

 …
 async validateUser(username: string, password: string): Promise<any> {
       const foundUser = await this.userRepository.findOne({ username });
       if (foundUser) {
           if (await bcrypt.compare(password, foundUser.password)) {
               const { password, ...result } = foundUser
               return result;
           }

           return null;
       }
       return null

   }
   async login(user: any) {
       const payload = { username: user.username, sub: user.id, role:user.role };

       return {
           access_token: this.jwt.sign(payload),
       };
   }

Validation de l'utilisateur

Nous pouvons maintenant implémenter notre stratégie d'authentification locale Passport . Créez un fichier appelé local.strategy.ts dans le dossier du module auth et ajoutez le code suivant :

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './service/auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
   constructor(private authService: AuthService) {
       super();
   }

   async validate(username: string, password: string): Promise<any> {
  
       const foundUser = await this.authService.validateUser(username, password);
       if (!foundUser) {
           throw new UnauthorizedException();
       }
       return foundUser;
   }
}

Stratégie d'authentification locale

Dans l'extrait de code ci-dessus, vous avez implémenté une stratégie de passeport local. Il n'y a pas d'options de configuration, donc notre constructeur appelle simplement super() sans objet options.

Vous avez également implémenté la méthode validate() . Passport appellera la fonction de vérification pour chaque stratégie à l'aide d'un ensemble de paramètres spécifiques à la stratégie. Pour la stratégie locale, Passport attend une méthode validate() avec la signature suivante : validate(username : string, password:string): any.

Créez ensuite un fichier jwt-auth.guard.ts dans le dossier du module auth et définissez un garde d'authentification personnalisé avec l'extrait de code ci-dessous :

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

Définir une protection d'authentification personnalisée

Vous utiliserez l'AuthGuard créé dans l'extrait de code pour protéger vos itinéraires d'API contre les utilisateurs non autorisés.

Créez maintenant un fichier jwt-strategy dans le dossier du module auth pour authentifier les utilisateurs et générer des jetons jwt pour les utilisateurs connectés avec l'extrait de code ci-dessous :

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
 constructor() {
   super({
     jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
     ignoreExpiration: false,
     secretOrKey: jwtConstants.secret,
   });
 }

 async validate(payload: any) {
   return { userId: payload.sub, username: payload.username, role: payload.role };
 }
}

Le dossier de stratégie

Configurez ensuite le module jwt dans le fichier auth.module.ts du dossier du module auth. Avant cela, créez un fichier constants.ts dans le même dossier du module auth pour définir un secret jwt avec l'extrait de code ci-dessous :

export const jwtConstants = {
   secret: 'wjeld-djeuedw399e3-uejheuii33-4jrjjejei3-rjdjfjf',
}

Le secret (utilisé à des fins de test)

Vous pouvez générer un secret jwt plus sécurisé en production, mais nous l'utiliserons à des fins de démonstration.

Importez maintenant tous les modules requis dans votre fichier auth.module.ts avec l'extrait de code ci-dessous :

…
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './user.entity';
…

Importation de modules

Ensuite, dans le tableau des importations, configurez jwt avec l'extrait de code ci-dessous :

…
imports: [
   PassportModule,
   JwtModule.register({
     secret: jwtConstants.secret,
     signOptions: { expiresIn: '60m' },
   }),
…

Configuration de jwt

Dans l'extrait de code ci-dessus, nous ajoutons le package PassModule pour permettre au passeport de gérer l'authentification des utilisateurs et jwt configuré à l'aide de la méthode de registre JwtModule . Nous transmettons le secret que nous avons créé dans le fichier de constantes et spécifions le délai d'expiration du jeton généré (vous pouvez réduire ou augmenter le délai selon le cas d'utilisation).

Créer le service produit

Avec la configuration du service d'authentification, générez un service de produit avec la commande ci-dessous :

nest generate service product/service/product

Le service produit

Ouvrez maintenant le fichier product.service.ts généré par la commande ci-dessus dans le module produit et ajoutez les extraits de code ci-dessous :

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ProductEntity } from '../product.entity';
import { Repository, UpdateResult, DeleteResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class ProductsService {
   constructor(@InjectRepository(ProductEntity) private productRepository: Repository<ProductEntity>) { }
  
   async getAll(): Promise<ProductEntity[]> {
       return await this.productRepository.find()
   }

   async create(product: ProductEntity, user: Users): Promise<ProductEntity> {
       if (user.role == 'admin') {
           return await this.productRepository.save(product);

       }
       throw new UnauthorizedException();

   }

   async getOne(id: number): Promise<ProductEntity> {
       return this.productRepository.findOne(id);
   }

   async update(id: number, product: ProductEntity, user: Users): Promise<UpdateResult> {
       if (user.role == 'admin') {
           return await this.productRepository.update(id, product);
       }
       throw new UnauthorizedException();
   }

   async delete(id: number, user: Users): Promise<DeleteResult> {
       if (user.role == 'admin') {
           return await this.productRepository.delete(id);
       }
       throw new UnauthorizedException();
   }
  
}

Création des services CRUD

Dans l'extrait ci-dessus, nous avons créé nos services CRUD. Les méthodes create , update et delete sont restreintes aux utilisateurs. Seul l'administrateur peut créer un produit, supprimer ou mettre à jour un produit.

Ouvrez maintenant le fichier product.module.ts et rendez l'entité product accessible avec l'extrait de code ci-dessous :

imports: [TypeOrmModule.forFeature([ProductEntity])],

Rendre l'entité produit accessible

Créer le service de panier

À ce stade, l'administrateur peut ajouter des produits à la base de données et les utilisateurs authentifiés peuvent voir tous les produits disponibles. Laissons maintenant les utilisateurs ajouter les articles qu'ils aiment au panier. Pour commencer, générez un service Cart avec la commande ci-dessous :

nest generate service cart/service/cart –flat

Faire le service de chariot

Ouvrez ensuite le fichier cart.service.ts généré par la commande et ajoutez l'extrait de code ci-dessous :

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartEntity } from '../cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class CartService {
   constructor(
       @InjectRepository(CartEntity)
       private cartRepository: Repository<CartEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private productsService: ProductsService,
   ) { }
   …

Le dossier de service du panier

Vous avez importé les modules requis pour créer un service Nest.js dans l'extrait de code ci-dessus. Nous les avons également importés ici puisque vous avez créé une relation entre les entités panier, utilisateur et produit. Ensuite, vous créez une méthode constructeur pour lier ces entités à la classe CartService. Créez maintenant une méthode pour ajouter un article au panier.

async addToCart(productId: number, quantity: number, user: string): Promise<any> {
       const cartItems = await this.cartRepository.find({ relations: ["item",'user'] });
       const product = await this.productsService.getOne(productId);
       const authUser = await this.userRepository.findOne({ username: user })
      
       //Confirm the product exists.
       if (product) {
           //confirm if user has item in cart
           const cart = cartItems.filter(
               (item) => item.item.id === productId && item.user.username === user,
           );
           if (cart.length < 1) {

               const newItem = this.cartRepository.create({ total: product.price * quantity, quantity });
               newItem.user = authUser;
               newItem.item = product;
               this.cartRepository.save(newItem)


               return await this.cartRepository.save(newItem)
           } else {
               //Update the item quantity
               const quantity = (cart[0].quantity += 1);
               const total = cart[0].total * quantity;

               return await this.cartRepository.update(cart[0].id, { quantity, total });
           }
       }
       return null;
   }

Ajouter un article au panier

Dans l'extrait de code ci-dessus, vous avez créé une méthode addToCart qui prend le productId , la quantité et l'utilisateur comme arguments. Vérifiez ensuite si l'utilisateur a déjà l'article dans son panier. Si tel est le cas, vous incrémentez la quantité et mettez à jour le prix total de cet article. Sinon, vous ajoutez l'article au panier de l'utilisateur.

Ensuite, rendez le cartEntity , l'entité productEntity User et le productService accessibles dans le cartService en les enregistrant dans le fichier cart.module.ts avec l'extrait de code ci-dessous :

…
import { CartEntity } from './cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Module({
 imports: [TypeOrmModule.forFeature([CartEntity, ProductEntity, Users])],
 providers: [CartService, ProductsService],
 …
})

Enregistrement des services

Enfin, créez une méthode getItemsInCart qui prend un utilisateur comme argument pour renvoyer tous les chariots appartenant à un utilisateur particulier.

async getItemsInCard(user: string): Promise<CartEntity[]> {
       const userCart = await this.cartRepository.find({ relations: ["item",'user'] });
       return (await userCart).filter(item => item.user.username === user)
   }

Obtenir des articles dans le panier 

Créer le service de commande

Lorsque les utilisateurs ont terminé leurs achats, ils peuvent commander les articles de leur panier. Générez un service de commande avec la commande ci-dessous :

nest generate service order/service/order –flat

Création du service de commande

Ouvrez maintenant le fichier order.service.ts généré à partir de l'exécution de la commande ci-dessus et ajoutez l'extrait de code ci-dessous :

import { OrderEntity } from '../order.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartService } from 'src/cart/service/cart.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class OrderService {
   constructor(@InjectRepository(OrderEntity)
   private orderRepository: Repository<OrderEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private cartService: CartService) { }

Le service de commande

Vous avez importé les modules requis pour créer un service Nest.js dans l'extrait de code ci-dessus. Nous les avons également importés ici puisque vous avez créé une relation entre les entités panier, utilisateur et produit. Ensuite, vous avez créé une méthode constructeur pour lier ces entités à la classe OrderService. Créez maintenant une méthode pour commander les articles dans le panier de l'utilisateur.

…
async order(user: string): Promise<any> {
       //find user existing orders
       const usersOrder = await this.orderRepository.find({ relations: ['user'] });
       const userOrder = usersOrder.filter(order => order.user?.username === user && order.pending === false);
       //find user's cart items
       const cartItems = await this.cartService.getItemsInCard(user)
       const subTotal = cartItems.map(item => item.total).reduce((acc, next) => acc + next);
       //get the authenticated user
       const authUser = await this.userRepository.findOne({ username: user })
       //if users has an pending order - add item to the list of order
       const cart = await cartItems.map(item => item.item);

       if (userOrder.length === 0) {
           const newOrder = await this.orderRepository.create({ subTotal });
           newOrder.items = cart
           newOrder.user = authUser;
           return await this.orderRepository.save(newOrder);


       } else {
           const existingOrder = userOrder.map(item => item)
           await this.orderRepository.update(existingOrder[0].id, { subTotal: existingOrder[0].subTotal + cart[0].price });
           return { message: "order modified" }
       }
   }
…

Commande des articles dans le panier de l'utilisateur

Créez ensuite une autre méthode pour obtenir la commande d'un utilisateur à partir du tableau de commandes de la base de données avec l'extrait de code ci-dessous :

…
async getOrders(user: string): Promise<OrderEntity[]> {
       const orders = await this.orderRepository.find({ relations: ['user'] });
       return orders.filter(order => order.user?.username === user)
   }
}

Obtenir les commandes d'un utilisateur

Enfin, ouvrez le fichier order.module.ts et rendez les entités users, product et cart accessibles dans orderService avec l'extrait de code ci-dessous :

import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderEntity } from './order.entity';
import { ProductEntity } from 'src/product/product.entity';
import { CartService } from 'src/cart/service/cart.service';
import { CartEntity } from 'src/cart/cart.entity';
import { Users } from 'src/auth/user.entity';
import { ProductsService } from 'src/product/service/products.service';

@Module({
 imports: [TypeOrmModule.forFeature([OrderEntity, ProductEntity, CartEntity, Users])],
 controllers: [OrderController],
 providers: [OrderService, CartService, ProductsService]
})

Rendre les entités accessibles

Créer des contrôleurs d'application

Une fois les services d'application créés avec succès, créons les routes d'API pour les services d'application.

Créer le contrôleur d'authentification

Générez un contrôleur d'authentification avec la commande ci-dessous :

nest generate controller auth/controller/auth –flat

Ouvrez maintenant le fichier auth.controller.ts généré à partir de l'exécution de la commande ci-dessus et configurez les routes d'authentification avec l'extrait de code ci-dessous :

import { Controller, Request, Post, UseGuards, Body } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../service/auth.service';
import { Users } from '../user.entity';

@Controller('api/v1/auth/')
export class AuthController {
   constructor(private usersService: AuthService) { }
  
   @Post('signup')
   async signup(@Body() user: Users): Promise<Users> {
       return this.usersService.signup(user);
   }

   @UseGuards(AuthGuard('local'))
   @Post('login')
   async login(@Request() req) {
       return this.usersService.login(req.user)
   }
}

Configuration des routes d'authentification

Créer le contrôleur de produit

Une fois les routes du contrôleur d'authentification configurées, générez un contrôleur de produit avec la commande ci-dessous :

nest generate controller product/controller/product –flat

Le contrôleur de produit

Ouvrez le fichier product.controller.ts généré à partir de l'exécution de la commande ci-dessus et configurez les routes du produit avec l'extrait de code ci-dessous :

import { Controller, Post, Get, Put, Delete, Param, Request, Body, UseGuards } from '@nestjs/common';
import {UpdateResult, DeleteResult} from 'typeorm';
import { ProductsService } from '../service/products.service';
import { ProductEntity } from '../product.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/products')
export class ProductsController {
 constructor(private productsService: ProductsService) { }

 @UseGuards(JwtAuthGuard)
 @Get()
 async GetAll(): Promise<ProductEntity[]> {
   return await this.productsService.getAll();

 }

 @UseGuards(JwtAuthGuard)
 @Post()
 async Create(@Request() req, @Body() product: ProductEntity): Promise<ProductEntity> {
   return await this.productsService.create(product, req.user);
 }


 @UseGuards(JwtAuthGuard)
 @Get(':id')
 async GetOne(@Param() id: number): Promise<ProductEntity> {
   return await this.productsService.getOne(id);

 }

 @UseGuards(JwtAuthGuard)
 @Put(':id')
 async Update(@Param() id: number, @Body() product: ProductEntity, @Request() req): Promise<UpdateResult> {
   return await this.productsService.update(id, product, req.user);

 }

 @UseGuards(JwtAuthGuard)
 @Delete(':id')
 async Delete(@Param() id: number, @Request() req): Promise<DeleteResult> {
   return await this.productsService.delete(id, req.user);

 }
}

Itinéraires de produits

Dans l'extrait de code ci-dessus, vous avez défini les routes CRUD pour le service produit. Nous avons utilisé le décorateur UseGuard passant votre JwtAuthGaurd pour protéger les routes des utilisateurs non autorisés.

Créer le contrôleur de chariot

Générez maintenant un contrôleur de panier avec la commande ci-dessous :

nest generate controller cart/controller/cart –flat

Le contrôleur de chariot

Ouvrez ensuite le fichier cart.controller.ts généré à partir de l'exécution de la commande ci-dessus et configurez les itinéraires du panier avec l'extrait de code ci-dessous :

import { Controller, Post, Get,Request, Delete, Body, UseGuards } from '@nestjs/common';
import { CartService } from '../service/cart.service';
import { CartEntity } from '../cart.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/cart')
export class CartController {
   constructor(private cartService: CartService) { }

   @UseGuards(JwtAuthGuard)
   @Post()
   async AddToCart(@Body() body, @Request() req): Promise<void> {
       const { productId, quantity } = body
       return await this.cartService.addToCart(productId, quantity, req.user.username);
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getItemsInCart(@Request() req): Promise<CartEntity[]> {
       return await this.cartService.getItemsInCard(req.user.username);

   }
}

Itinéraires de chariot

Créer le contrôleur de commande

Avec les routes de panier configurées, créez un contrôleur de commande avec la commande ci-dessous :

nest generate controller order/controller/order –flat

Contrôleur de commande

Ouvrez ensuite le fichier order.controlle.ts généré à partir de l'exécution de la commande ci-dessus et configurez les routes du panier avec l'extrait de code ci-dessous :

import { Controller, Post, Get, Request, UseGuards } from '@nestjs/common';
import { OrderService } from '../service/order.service'
import { OrderEntity } from '../order.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/order')
export class OrderController {
   constructor(private orderService: OrderService) { }


   @UseGuards(JwtAuthGuard)
   @Post()
   async order(@Request() req): Promise<any> {
       return this.orderService.order(req.user.username)
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getOrders(@Request() req): Promise<OrderEntity[]> {
       return await this.orderService.getOrders(req.user.username)
   }
}

Itinéraires de commande

À ce stade, toutes vos routes d'API ont été configurées.

Demande d'essai

Testons-les maintenant avec Postman. Les résultats de certains tests sont présentés ci-dessous.

Parcours utilisateur Parcours deconnexion Parcoursproduit. Copiez le jeton d'accès et ajoutez-le à l'en-tête de la demande avant d'ajouter un produit.

N'hésitez pas à tester les autres itinéraires et à jouer avec le code ici sur Github.

Connexion d'Arctype à la base de données SQLite

Arctype est un client SQL et un outil de gestion de base de données amusant à utiliser. Il vous permet d'avoir une représentation visuelle de vos tables de base de données et vous pouvez effectuer le répertoire des opérations CRUD dans votre base de données à l'aide d'Arctype. La connexion d'Arctype à une base de données SQLite est un processus simple. Pour commencer, suivez les étapes ci-dessous :

Tout d'abord, lancez Arctype. Vous verrez un écran comme celui ci-dessous, vous permettant d'ajouter vos informations d'identification.

Cliquez sur l'onglet SQLite. Cela fera apparaître l'écran ci-dessous.

Cliquez sur le bouton Choisir un fichier SQLite et accédez au dossier de votre projet. Sélectionnez le fichier de base de données d'application et appuyez sur le bouton d'ouverture . Appuyez sur le bouton Enregistrer et vous devriez voir vos entités de base de données, comme indiqué dans la capture d'écran ci-dessous.

Conclusion

En créant un projet de démonstration, nous avons appris à créer une application de commerce électronique à l'aide de Nestjs et de la base de données SQLite. Tout d'abord, nous avons commencé par l'introduction de NestJS. Ensuite, nous avons créé une application NestJS, connecté l'application à une base de données SQLite à l'aide de TypeORM et effectué des opérations CRUD. Maintenant que vous avez acquis les connaissances que vous recherchez, n'hésitez pas à ajouter des fonctionnalités supplémentaires à l'application.

Lien : https://arctype.com/blog/sqlite-nestjs-tutorial/

#nestjs  #typeorm #sqlite 

Création D'une API De Commerce électronique à L'aide De Nestjs, SQLite
Laura  Fox

Laura Fox

1657096260

How to Nestjs using The Serverless Framework with TypeORM

serverless-nestJS-typeORM-crud

This is example how to nestjs using the serverless framework

  • TypeORM
  • MySql
  • CRUD

setup mysql connection in serverless.yml

# Custom Variables
custom:
  ...
  mysqlHost:
    local: localhost
  mysqlUser:
    local: user
  mysqlPassword:
    local: password
  mysqlDatabase:
    local: dbname
  mysqlPort:
    local: '3306'

How to prepare

$ npm install serverless -g
$ git clone https://github.com/kop7/serverless-nestjs-typeorm.git 【projectName】
$ cd 【projectName】
$ npm install        

Development

$ npm run sls:offline 
Serverless: Typescript compiled.
Serverless: Watching typescript files...
Serverless: Starting Offline: undefined/undefined.

Serverless: Routes for author:
Serverless: ANY /api/author

Serverless: Routes for book:
Serverless: ANY /api/book

Serverless: Offline listening on http://localhost:3000

The logs should be :

Serverless: ANY /api/book (λ: book)
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [NestFactory] Starting Nest application...
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] TypeOrmModule dependencies initialized +34ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] AppModule dependencies initialized +43ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] ConfigModule dependencies initialized +5ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] TypeOrmCoreModule dependencies initialized +168ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] TypeOrmModule dependencies initialized +1ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] TypeOrmModule dependencies initialized +0ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] BookModule dependencies initialized +3ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [InstanceLoader] AuthorModule dependencies initialized +0ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RoutesResolver] AppController {/}: +10ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RoutesResolver] BookController {/api/book}: +1ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/, GET} route +6ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, GET} route +3ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/, POST} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/bulk, POST} route +4ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, PATCH} route +4ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, PUT} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, DELETE} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RoutesResolver] AuthorController {/api/author}: +1ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, GET} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/, POST} route +3ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/bulk, POST} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, PATCH} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, PUT} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [RouterExplorer] Mapped {/:id, DELETE} route +2ms
[Nest] 7980   - 09/02/2019, 6:33:47 PM   [NestApplication] Nest application successfully started +6ms

Download Details:
Author: kop7
Source Code: https://github.com/kop7/serverless-nestjs-typeorm
License:

#nestjs #node #javascript #serverless #typeorm

How to Nestjs using The Serverless Framework with TypeORM

TypeScriptスキーマ構築のためのPothos対TypeGraphQL

この投稿では、2つのスキーマビルダーであるPothosとTypeGraphQLが、開発者がサービス用のGraphQLスキーマを構築するのにどのように役立つか、およびこれらのツールでTypeScriptを使用して実際にスキーマを構築する方法を比較します。 

投稿の最後に、両方のツールといくつかのユースケースで提供される機能を比較します。

GraphQLスキーマ構築の入門書

GraphQLスキーマは、受け入れることができるデータのタイプを指定するフィールドを持つオブジェクトタイプで構成されています。これらのタイプはスカラータイプと呼ばれ、SDLコアに組み込まれています。GraphQLスキーマを定義すると、それぞれのタイプを持つオブジェクトで定義された指定されたフィールドのみを照会または変更できます。

GraphQLスキーマ内では、スキーマの作成時にクエリとミューテーションのタイプを定義する必要があります。ただし、ミューテーションのタイプは必ずしも必須ではありません。どちらのタイプ定義も、事前定義されたスキーマに基づいて、GraphQLサービスまたはAPIに対して行うすべてのクエリのエントリポイントを定義します。ブログの他の場所で、GraphQLでのスキーマ構築の詳細を読むことができます。

ポトスとは何ですか?

Pothosは、GraphQLとTypeScriptを使用してスキーマを作成および構築する簡単な方法を提供するプラグインです。

TSベースであるため、PothosはGraphQLスキーマ構築に必要な型安全性を提供します。また、TypeScriptの強力な型システムと型推論に基づいて構築されているため、コードを生成したり、どこでも手動の型を使用したりする必要はありません。

graphqlPothosスキーマは、パッケージの型を使用するプレーンスキーマに構築されます。これは、Node.js用の一般的なGraphQLサーバー実装のほとんどと互換性がある必要があることを意味します。このガイドでは@graphql-yoga/node、例を実行するために使用しますが、任意のサーバーを使用できます。

Pothosを使用したスキーマ構築

Pothosでは、通常、データの形状(タイプ、インターフェイス、クラス、Prismaモデルなどとして定義)から始めて、そのデータを使用するGraphQLタイプを定義しますが、必ずしもその形状に準拠しているとは限りません。Pothosが採用しているアプローチは、GraphQLAPI用に純粋に作成されていない実際のデータがある大規模なアプリケーションではより自然に感じられます。

デコレータに依存しない直接型の安全性の利点とは別に、Pothosは、プラグインの大規模なエコシステムを構成するプラグインとして公開される多くの機能を提供することに誇りを持っています。Pothosの主な利点の1つは、GraphQL APIが分離されていることと、データがスキーマ内でどのように表現されているかです。

Pothosドキュメントの例から始めましょう:「Hello、World!」から単純なスキーマを構築します。アプリ。

import { createServer } from '@graphql-yoga/node';
import SchemaBuilder from '@pothos/core';

const builder = new SchemaBuilder({});

builder.queryType({
  fields: (t) =&gt; ({
    hello: t.string({
      args: {
        name: t.arg.string(),
      },
      resolve: (parent, { name }) =&gt; `hello, ${name || 'World'}`,
    }),
  }),
});

const server = createServer({
  schema: builder.toSchema({}),
});

server.start();

この例ではgraphl-yoga/node、Pothosスキーマビルダーを使用して単純なボイラープレートサーバーを作成します。Pothosコアからスキーマビルダーをインポートし、新しいスキーマビルダーをインスタンス化します。これにより、GraphQL言語が理解できるプレーンなスキーマが構築されます。

その後、タイプセーフな方法でフィールドタイプと引数を使用してクエリビルダーをセットアップします。リゾルバーは、クエリに渡されたフィールド引数とクエリ自体に対して必要なすべての検証が行われた後、クエリが実行されたときに応答を返す責任があります。

最後に、ビルドされたスキーマをcreateServer関数に渡し、メソッドを呼び出しますserver.start

Pothosを使用すると、オブジェクトタイプの形式でデータの構造を定義できます。これにより、基になるデータタイプの詳細を知ることができます。その後、タイプの定義に進むことができます。ここで、実際のタイプを検証する方法として定義した構造を渡します。

したがって、基本的に、基になるデータ構造に関する型情報を渡す方法が必要です。これにより、フィールドは、オブジェクト型で使用可能なプロパティを認識できます。

型推論の助けを借りて、オブジェクト型に間違ったフィールドを渡したときを確認しobjectType、オブジェクトがどの型を期待するかを伝えることができるので、型定義に準拠していることを確認できます。スキーマで定義されたフィールドに基づいて、使用可能なデータの性質とそのタイプを判別できます。つまり、スキーマを追加する予定のデータはすべて明示的に定義する必要があります。

Pothosでオブジェクトを定義する方法

Pothosでオブジェクトを定義するには、クラス、スキーマタイプ、および参照を使用する3つの方法があります。

Pothosクラスの定義は、通常のクラスの定義と同じです。データを構造化し、クラスを初期化します。以下の例を参照してください。

export class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

クラスを定義した後、上記のクラスのフィールドのタイプをマップできます。これは、Pothosフィールドビルダーを使用して、上記のスキーマクラスのオブジェクトタイプに対して検証するために行われます。以下でそれを行う方法をご覧ください。

<

pre class =” language-graphql hljs> const builder = new SchemaBuilder({});

builder.objectType(Person、{
name:'Person Schema'、
description:“ Person schema”、
fields:(t)=&gt;({})、
});

objectParam引数、は、初期クラスを表します。Personこれは、その青写真に基づいて個々のプロパティごとに渡すことができるタイプの種類に対して検証するための青写真として機能します。これは、フィールドでこれらのプロパティを使用するときに、それらが正しいタイプを表していることを確認できるようにするためです。

上記のフィールドオブジェクトを使用して、上記のスキーマにあるデータの種類を定義することができます。以下でそれをどのように行うかを見てみましょう。

builder.objectType(Person, {
  name: 'Person Schema',
  description: 'A person schema',
  fields: (t) =&gt; ({
    nameNew: t.string({
    resolve: (parent) =&gt; {
        const name = console.log(parent.name)
      },
    }),
    ageNew: t.int({
      resolve: (parent) =&gt; {
         const age = console.log(parent.age)
      },
    }),
  }),
});

ご覧のとおり、スキーマで定義されているプロパティに直接アクセスすることはできません。これは、基礎となるスキーマからのみプロパティにアクセスできるようにするための設計によるものです。

親argは、スキーマクラスで指定された現在のタイプのバッキングモデルの値になることに注意してください。

ただし、スキーマまたはモデルで定義されているフィールドプロパティに直接アクセスするには、ここでドキュメントで定義されているexposeように、を利用できます。

exposeString(name, options)

注:nameargは、公開されているタイプに一致するバッキングモデルの任意のフィールドにすることができます。

次に、このフィールドの値を解決する関数であるリゾルバーを使用して、実際の値に解決するクエリを実際に記述します。以下のPothosでそのようなクエリを作成しましょう。

builder.queryType({
  fields: (t) =&gt; ({
    Person: t.field({
      type: Person,
      resolve: () =&gt; new Person('Alexander', 25),
    }),
  }),
});

次のステップは、前に見たように、単純なサーバーを作成し、構築したスキーマをサーバーに渡すことです。Pothosは、利用可能な一般的なGraphQLサーバーの実装でうまく機能します。

最後に、サーバーに対して通常のGraphQLクエリを実行します。

query {
  Person {
    name
    age
  }
}

ポトスの特徴

スキーマを定義するいくつかの方法

上で概説したように、Pothos(スキーマの基になるデータがどのように構造化されているかに関するタイプ情報を提供する方法)を使用してオブジェクトタイプを作成または定義する際に、上記のクラス、スキーマタイプ、または参照。ユースケースに基づいてこれを使用する方法の詳細については、ドキュメントを参照してください。

スキーマファイルの印刷と生成

Pothosでは、を使用してスキーマファイルを生成できますgraphql-code-generator。スキーマを印刷することもできます。これは、スキーマのSDLバージョンが必要な場合に役立ちます。このシナリオでは、printSchemaまたはを使用できlexicographicSortSchemaます。どちらもGraphQLパッケージからインポートできます。

タイプの生成

Pothosには、クライアントで使用する型を生成するための組み込みのメカニズムはありませんが、graphql-code-generatorは、TypeScriptファイルから直接スキーマを使用するように構成できます。

ポトスバッキングモデル

Pothosは、外部GraphQLAPIの形状とデータの内部表現を明確に分離します。この分離を支援するために、Pothosは、スキーマとリゾルバーが使用するタイプを定義する方法を大幅に制御できるバッキングモデルを提供しています。

Pothosのバッキングモデルは、ドキュメントで詳しく説明されています。

プラグインベース

Pothosはプラグインベースsimple-objectsで、、、などのサンプルプラグインを提供してscope-authおりmocks、作業が簡単になります。たとえば、simple-objectsプラグインを使用すると、グラフ内のすべてのオブジェクトに明示的なタイプやモデルを提供する必要がないため、グラフの作成がはるかに迅速になります。

意見のない

Pothosは、コードがどのように構造化されているかについては無関心であり、多くのことを行うための複数の方法を提供します。実際、Pothosは、ファイルを整理する方法のガイドを提供するところまで行っています。

Pothos SchemaBuilder API

Pothosでスキーマを作成するには、以下に示すように、Pothosコアからクラスをインポートするだけです。schemaBuilder

import SchemaBuilder from '@pothos/core';
const builder = new SchemaBuilder&lt;{
  Context: {};
}&gt;({
  // plugins 
});

スキーマビルダーは、グラフの型を作成するのに役立ち、作成された型をGraphQLスキーマに埋め込みます。PothosスキーマビルダーAPI設計の詳細は、ドキュメントに記載されています。

ORMのサポート

Pothosは主にスキーマビルダーですが、ほとんどのORM、特にPothosのPrismaプラグインを介したPrismaをサポートし、それらとうまく統合されています。このプラグインを使用すると、Prismaベースのオブジェクトタイプを簡単に定義でき、同様に、Prismaモデルに基づいてGraphQLタイプを定義できます。これを実行する方法の例と設定は、ドキュメントに示されています

もちろん、この統合の注目すべき機能の1つは、厳密に型指定されたAPIのサポート、自動クエリ最適化(n + 1リレーションのクエリ問題を含む)、同じデータベーススキーマに基づく多くの異なるGraphQLモデルのサポートなどです。ドキュメントはそれをすべてカバーしています

注:PrismaはPothosと直接統合することもできます。プラグインを使用すると、これら2つのテクノロジーをより簡単に、よりパフォーマンスが高く、より効率的に操作できるようになります。この統合を実行する方法に関するガイドには、より多くの情報が含まれています。

TypeGraphQLとは何ですか?

TypeGraphQLは、スキーマを構築するための異なるアプローチを提供します。TypeGraphQLでは、クラスとデコレータマジックのみを使用してスキーマを定義します。graphql-jsこれは主に、、、class-validator()およびシムに依存しておりreflect-metadata、TypeScriptでの反映が機能します。class-validatorクラスのデコレータベースのプロパティ検証です。

オブジェクトタイプの作成方法など、以前の投稿でTypeGraphQLを使用したGraphQLAPIの構築について詳しく説明しました。

@ObjectType()
class Recipe {
  @Field()
  title: string;

  @Field(type =&gt; [Rate])
  ratings: Rate[];

  @Field({ nullable: true })
  averageRating?: number;
}

上記のように、スキーマの青写真として機能するクラスを定義することにより、TypeGraphQLを使用してスキーマの定義を開始します。以下の例を見てみましょう。

まず、SDLの型に似た型を作成することから始めます。

type Person {
  name: String!
  age: Number
  dateofBirth: Date
}

次に、クラスの作成に進むことができます。このクラスには、タイプのすべてのプロパティと定義されたタイプが含まれている必要がありますPerson

class Recipe {
  name: string;
  age?: number;
  dateofBirth: ate
}

次のように、デコレータを使用してクラスとそのプロパティを設計します。

@ObjectType()
class Person {
  @Field()
  name: string;

  @Field()
  age?: number;

  @Field()
  dateOfBirth: Date;
}

次に、タイプと呼ばれるものを作成しますinput。これは、クエリとミューテーションを実行するために必要です。

@InputType()
class NewPersonInput {
  @Field()
  @MaxLength(40)
  name: string;

  @Field({ nullable: true })
  age?: number;

  @Field()
  dateOfBirth: Date;
}

を含むフィールド検証メソッドは、ライブラリmaxLengthからのものです。class-validator通常のクエリとミューテーションを作成した後、最後のステップは、GraphQLサーバーに渡すスキーマを構築することです。

const schema = await buildSchema({
  resolvers: [PersonResolver],
});

このタイプのミューテーションタイプの例をPerson以下に示します。

type Mutation {
  addNewPerson (newPersonData: NewPersonInput!): Person!
  deletePerson(id: ID!): Boolean!
}

TypeGraphQLの機能

TypeGraphQLの機能には、検証、承認などが含まれ、開発者がGraphQL APIをすばやく作成し、すべての引数と入力、および/またはオブジェクトタイプ用のTypeScriptインターフェイスを作成する必要性を減らします。TypeGraphQLは、クラスとデコレータのヘルプを使用してスキーマを定義することにより、すべての人が信頼できる唯一の情報源から作業できるようにするのにも役立ちます。これは確かにコードの冗長性を減らすのに役立ちます。

依存性注入のサポート

TypeGraphQLは、ユーザーがフレームワークで使用されるIoCコンテナーを提供できるようにすることで、依存性注入をサポートします。

厳密な検証

フィールドプロパティは、クラス検証ライブラリで厳密に検証されます。TypeGraphQLは、Pothosよりも柔軟性があり、型パラメーターなど、より柔軟な方法で一部のフィールドの型を宣言する必要がある場合に、ジェネリック型をサポートします。

カスタムデコレータのサポート

TypeGraphQLは、メソッドやパラメーターなどのカスタムデコレーターをサポートします。これは、ボイラープレートコードを削減し、複数のリゾルバーに共通して再利用するための優れた方法を提供します。

ORMのサポート

TypeGraphQLは、TypeORMやPrismaなどの複数の異なるサードパーティORMも強力にサポートしています。Prismaを使用すると、 TypeGraphQLはtypegraphql-prismaパッケージとの統合を提供します。これはnpmにあります。

TypeGraphQLには、Prismaスキーマに基づいて型クラスとリゾルバーを生成するためのプロビジョニングがすでに用意されています。つまり、通常のクエリやミューテーションを実行するために多くのコードを記述する必要はありません。ドキュメントには、これら2つのテクノロジの設定例と、インストール手順、構成などを含む、より多くの例とチュートリアルが含まれている専用のWebサイトがあります。

結論

この投稿では、2つの素晴らしいTypeScriptベースのライブラリのスキーマ構築へのアプローチを見てきました。PothosはTypeScriptベースのGraphQLAPIを構築するために使用できますが、主にスキーマビルダーとして輝いています。

一方、TypeGraphQLはより柔軟性があり、さまざまなORMをサポートする単純なGraphQLAPIを構築できます。

Node.js /TypeScriptおよびGraphQLベースのAPIでスキーマを構築するための重要な機能、ユースケース、および方法論のいくつかをカバーすることができました。この投稿の目的は、これら2つの異なる独自のライブラリがこれらのプロセスにどのようにアプローチしたかを示し、将来のプロジェクトで使用する次善のツールについて情報に基づいた決定を下せるようにすることです。

このストーリーは、もともとhttps://blog.logrocket.com/pothos-vs-typegraphql-for-typescript-schema-building/で公開されました。

#typegraphql #typescript #prisma #typeorm 

TypeScriptスキーマ構築のためのPothos対TypeGraphQL
Diego  Elizondo

Diego Elizondo

1652872260

Los Mejores ORM de TypeScript

Al construir o diseñar sistemas de software, la mayoría de las decisiones de diseño vienen con compensaciones. El uso o no de mapeadores relacionales de objetos (ORM) ha sido un tema controvertido entre muchos desarrolladores, ya que siempre ha habido un debate sobre si realmente valen la pena o no.

En algunos casos, mientras que los ORM mal escritos pueden provocar cuellos de botella en el rendimiento en comparación con las consultas sin procesar, en otros casos, los ORM pueden ser útiles cuando necesita un acceso rápido y fácil a los métodos de la base de datos.

Los ORM también ayudan a acelerar los tiempos de desarrollo, lo que por supuesto conduce a una mejor productividad del desarrollador. Es probable que la capacidad de mantenimiento de su base de código también experimente una mejora general, ya que hay una interfaz más sencilla en términos de API de base de datos para interactuar con ella.

Los ORM también sirven como una herramienta importante cuando se inicia una aplicación desde cero: los desarrolladores pueden iniciar e iterar rápidamente en las funcionalidades de trabajo iniciales de una aplicación de back-end en un período de tiempo más corto.

En esta publicación, vamos a explorar un par de ORM populares utilizados en el panorama de TypeScript.

Cómo definir los ORM de manera práctica

Los ORM nos ayudan a lidiar con las complejidades asociadas con la capa de datos o los modelos en nuestras aplicaciones. Con los ORM, los desarrolladores pueden realizar fácilmente operaciones de lectura y escritura de bases de datos llamando a métodos específicos directamente en las instancias de las clases modelo. Esto, en cierto modo, es un enfoque más conveniente y se acerca más al modelo mental al que están acostumbrados los desarrolladores cuando piensan en la manipulación de datos.

Con los ORM, los desarrolladores pueden administrar modelos o esquemas de bases de datos asignando filas en tablas de bases de datos a clases (o instancias de estas clases). La lógica de la base de datos, que maneja todos los métodos necesarios para interactuar con la base de datos, se abstrae en objetos o clases, de una manera que, la mayoría de las veces, es altamente reutilizable. Por lo tanto, podemos imponer una especie de separación de preocupaciones, que puede ser bastante útil.

Pero antes de continuar, hagámonos estas preguntas muy pertinentes: ¿Está bien usar ORM con TypeScript? ¿Qué requeriría un enfoque ORM para acceder a nuestros modelos de datos o consultar nuestra base de datos?

Responderemos estas preguntas y cubriremos todas las ventajas y desventajas del uso de ORM en general.

¿Deberíamos usar ORM en nuestras bases de código de TypeScript?

Los ORM no son una panacea. No todos son perfectos para todos los casos de uso. Sigue siendo nuestro deber como desarrolladores elegir las compensaciones correctas para el trabajo cuando surjan las necesidades.

En mi propia experiencia, los ORM no deberían ser la solución inmediata para todos los problemas de la capa de acceso a la base de datos. En general, depende del problema específico que un desarrollador está tratando de resolver, las compensaciones que pueden permitirse hacer y el alcance de su capa de datos.

Si bien los ORM pueden hacer que parezca más fácil crear consultas debido al mayor nivel de abstracción involucrado, generalmente son más difíciles de migrar y, por lo tanto, no son factibles a largo plazo. Además, no se puede confiar en ellos para todos los casos de uso porque no todos los problemas se pueden solucionar con el tipo de consultas ORM o API compatibles o disponibles.

Además, con ORM mal diseñados, las costosas consultas SQL generalmente se generan bajo el capó. Esto tiene el potencial de ralentizar drásticamente una aplicación y tener un gran impacto en el rendimiento general.

Beneficios de usar ORM

Por otro lado, los ORM tienen su lugar a pesar de los argumentos en contra de su uso. Uno de los mayores argumentos para usar los ORM es que la capa de abstracción de la base de datos que proporcionan facilita el cambio de bases de datos, al ayudar a crear un patrón consistente de acceso a la capa de datos de nuestra aplicación. Esto significa que podemos manipular fácilmente la capa de datos de nuestra aplicación de manera predecible. Y con los generadores de consultas (que mejoran las consultas sin procesar al proporcionar métodos incorporados adicionales), los desarrolladores pueden acelerar más fácil y rápidamente la escritura de consultas, lo que lleva a un mayor aumento de la productividad en comparación con la escritura de consultas de bases de datos sin procesar.

A medida que una aplicación se hace más grande, escribir consultas sin procesar grandes se vuelve más complejo y complicado, y las consultas en sí mismas se vuelven difíciles de comprender; no importa que existan múltiples formas de escribir las mismas consultas sin procesar basadas en el desarrollador, lo que puede incluso hacer que sea más difícil. para extender, más especialmente para los nuevos miembros del equipo. ¡Aquí es donde los ORM vienen a nuestro rescate!

Los documentos de Prisma destacan un argumento interesante sobre el uso de ORM, generadores de consultas y consultas sin procesar en términos del nivel de productividad y el nivel de control que permite a los desarrolladores. Con las consultas sin formato, los desarrolladores tienen control total sobre la calidad y la complejidad de las consultas que escriben.

Sin embargo, surge una pregunta. ¿Qué tan seguro es ejecutar esas consultas contra esquemas de bases de datos? ¿Son estas consultas lo suficientemente seguras para evitar los populares ataques de inyección SQL, por ejemplo? ¿Cómo es la carga de mantenimiento para construir consultas grandes y complejas, como uniones múltiples? Bueno, eso depende de la experiencia y los conocimientos del programador que implemente esa solución.

Por lo tanto, con las consultas sin formato, los desarrolladores obtienen un recuento de productividad más bajo, pero más control sobre cómo interactúan con su capa de datos. También tienen control total sobre el rendimiento del tipo de consultas que escriben.

Sin embargo, con los ORM, la idea es que los desarrolladores no tengan que preocuparse por averiguar si las consultas SQL son complicadas o por modificar los resultados de las consultas para que se ajusten a sus necesidades. En cambio, se debe prestar atención a refinar los datos que necesitan para implementar características.

Elegir los mejores ORM de TypeScript

Elegir un ORM para sus proyectos de TS puede ser un desafío porque hay muchas opciones disponibles. Varían en su diseño y su nivel de abstracción. Algunos generadores de consultas y ORM también ofrecen características adicionales, como desinfección y seguridad de tipos, o pueden abstraer muchas cosas, lo que les da a los desarrolladores menos de qué preocuparse.

El uso de ORM es más popular entre los desarrolladores en estos días, y ahora también hay varias bibliotecas para elegir. Al elegir un ORM para usar en nuestra aplicación, nuestro objetivo es observar varios factores clave y examinarlos ampliamente. Vamos a cubrir sus características, el alcance de su documentación, qué tan bien funcionan, su nivel de soporte de la comunidad y las métricas de mantenimiento.

Los mejores ORM de TypeScript: Prisma

Prisma es un generador de consultas de tipo seguro y autogenerado para aplicaciones TypeScript y Node.js. Es un ORM de próxima generación de código abierto que permite a los desarrolladores administrar e interactuar fácilmente con su base de datos. Tiene una enorme comunidad de apoyo de muchos colaboradores y mantenedores de código abierto.

Características

Prisma es un tipo de ORM orientado a SQL/relacional y NoSQL con soporte actual para PostgreSQL, MYSQL, servidor MicrosoftSQL, SQLite, CockroachDB y MongoDB.

Prisma también es compatible con la mayoría de las tecnologías en el ecosistema de JavaScript, incluidos los patrones de API REST, GraphQL, gRPC y la mayoría de los marcos de back-end en el entorno JS y TS, como Express.

Cuando se trata de características, Prisma es bastante impresionante. Ofrece:

  • Un cliente de Prisma
  • Un ORM de tipo seguro que permite a los desarrolladores pensar en cómo trabajan con sus datos (en lugar de cómo escribir consultas sin formato)
  • Una herramienta de migración que es útil para ejecutar migraciones de bases de datos a medida que evoluciona el esquema, lo que sucede con el tiempo durante el desarrollo y la producción.
  • Una GUI para jugar con datos en la base de datos, útil también para el desarrollo

Prisma tiene baterías incluidas, ya que permite a los desarrolladores definir sus modelos de aplicación en un lenguaje de modelado de datos. También contiene una forma sencilla de conectarse a la base de datos de su elección y define un generador. A continuación se muestra un ejemplo de un archivo de esquema de Prisma de los documentos de Prisma:

datasource db { 
provider = "postgresql" 
  url      = env("DATABASE_URL") 
} 

generator client { 
  provider = "prisma-client-js" 
} 

model User { 
  id    Int     @id @default(autoincrement()) 
  email String  @unique 
  name  String? 
  posts Post[] 
} 

Con lo anterior, podemos configurar tres cosas, entre ellas:

  1. La fuente de datos, que especifica la conexión a la base de datos.
  2. Un generador, que indica que pretendemos generar un Cliente Prisma
  3. Un modelo de datos, que define el modelo de aplicación.

El modelo de datos Prisma

Para obtener un modelo de datos en Prisma, existen dos flujos de trabajo principales. Incluyen:

  1. Escribir el modelo de datos y asignarlo a la base de datos con el comando Prisma Migrate
  2. Generación del modelo de datos mediante la introspección de una base de datos

Una vez que se define el modelo de datos, se puede generar el Prisma Client , que expondrá las operaciones y consultas CRUD para los modelos definidos. Con TypeScript, también podemos obtener todos los beneficios de la seguridad de tipos para todas las consultas.

Para instalar @prisma/clientcon npm, podemos ejecutar el siguiente comando:

npm install @prisma/client 

Después de realizar un cambio en el modelo de datos, también deberá volver a generar Prisma Client manualmente. Podemos hacerlo ejecutando:

prisma generate

Podemos importar el código del cliente como se muestra a continuación.

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient() 

Ahora, podemos enviar consultas a través de la API Prisma Client generada. Aquí hay una consulta de muestra a continuación:

// Run inside async function 
const allUsers = await prisma.user.findMany() 

Se pueden encontrar más detalles sobre las operaciones disponibles en la referencia de la API de Prisma Client . La documentación de Prisma también es madura, con detalles extensos sobre la hoja de ruta, las limitaciones, la referencia de la API, el proyecto de ejemplo, las preguntas frecuentes, etc.

Configuración de mecanografiado

Para usar Prisma con TypeScript, asegúrese de estar usando la versión de TypeScript ≥ 3.3. El tsconfig.jsonarchivo debería verse así a continuación:

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "target": "ES2018",
    "module": "commonjs",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true
  },
  "exclude": ["dist", "prisma", "tests"]
}

Finalmente, continúe e instale los siguientes npmpaquetes como dependencias de desarrollo para que Prisma funcione con Typescript:

npm install ts-node ts-node-dev typescript --save-dev

Los mejores ORM de TypeScript: TypeORM

TypeORM es un ORM de código abierto que se ejecuta en Node.js y en el navegador. Es compatible con TypeScript y todas las últimas aplicaciones de JavaScript. El objetivo del proyecto es proporcionar funciones adicionales para aplicaciones pequeñas y aplicaciones empresariales a gran escala.

Según la documentación, es compatible con los patrones Active Record y Data Mapper , a diferencia de otros ORM de JavaScript. Este soporte significa que los desarrolladores pueden escribir aplicaciones de alta calidad, débilmente acopladas, escalables, mantenibles y listas para producción.

Características

TypeORM tiene una rica lista de características en su documentación. Algunos de los más populares incluyen la CLI, el almacenamiento en caché de consultas, la agrupación de conexiones y la compatibilidad con Hooks. También viene con una API decoradora con una extensa referencia .

TypeORM también es compatible con MySQL, MariaDB, PostgreSQL, CockroachDB, SQLite, Microsoft SQL Server, SQL.js y Oracle, además de la compatibilidad básica con MongoDB. También soporta migraciones, relaciones e índices; con TypeORM, podemos migrar usando Sequelize directamente. Finalmente, tiene una gran comunidad de apoyo con muchos colaboradores y mantenedores de código abierto.

Configuración de mecanografiado

Para usar TypeORM con TS, asegúrese de estar usando la versión de TypeScript ≥ 3.3 y de haber habilitado las siguientes configuraciones en el tsconfig.jsonarchivo:

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "target": "ES2018",
    "module": "commonjs",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true,
    "emitDecoratorMetadata": true, 
    "experimentalDecorators": true, 
  },

Luego, continúa e instala el npmpaquete:

npm install typeorm --save 

Instale la reflect-metadatacuña:

npm install reflect-metadata --save 

Asegúrese de importarlo en algún lugar del espacio de nombres global de su aplicación, por ejemplo, en el app.tsarchivo de conexión de la base de datos o.

Es posible que también deba instalar los tipos de nodos, lo que puede hacer ejecutando:

npm install @types/node --save-dev 

A ormconfig.jsoncontinuación se muestra el archivo con la configuración de la conexión a la base de datos.

{ 
   "type": "mysql", 
   "host": "localhost", 
   "port": 3306, 
   "username": "test", 
   "password": "test", 
   "database": "test", 
   "synchronize": true, 
   "logging": false, 
   "entities": [ 
      "src/entity/**/*.ts" 
   ], 
   "migrations": [ 
      "src/migration/**/*.ts" 
   ], 
   "subscribers": [ 
      "src/subscriber/**/*.ts" 
   ] 
} 

A continuación, podemos seguir adelante para configurar la conexión a la base de datos.

import "reflect-metadata"; 
import { createConnection } from "typeorm"; 

createConnection({ 
    type: "mysql", 
    host: "localhost", 
    port: 3306, 
    username: "root", 
    password: "admin", 
    database: "test", 
    entities: [ 
        __dirname + "/entity/*.js" 
    ], 
    synchronize: true, 
    logging: false 
}).then(connection => { 
    // here you can start to work with your entities 
}).catch(error => console.log(error)); 

Con TypeORM, necesitamos decorar el modelo con el @Entitydecorador, lo que significa que se crearía una tabla de base de datos equivalente para el modelo. Podemos trabajar con entidades en todas partes con TypeORM, y eso nos permite consultar nuestra capa de base de datos.

El modelo de datos TypeORM

Los modelos TypeORM se ven así:

@Entity() 
export class User { 
    @PrimaryGeneratedColumn() 
    id: number;  
    @Column() 
    firstName: string;  
    @Column() 
    lastName: string; 
    @Column() 
    age: number; 
} 

Aquí hay una lógica de dominio de muestra, que se ve así:

const repository = connection.getRepository(User); 

const user = new User(); 
user.firstName = "Alex"; 
user.lastName = "Cage"; 
user.age = 26; 
await repository.save(user); 

const allUsers = await repository.find(); 
const firstUser = await repository.findOne(1); 
const name = await repository.findOne({ firstName: "Alex" }); 
await repository.remove(name); 

Bonificación: QueryBuilder

TypeORM viene con un QueryBuilder incorporado , que es una de sus características más poderosas. Esto permite a los desarrolladores crear consultas SQL utilizando una sintaxis elegante y conveniente, ejecutarlas y recibir entidades transformadas automáticamente.

Un ejemplo simple de QueryBuilder en TypeORM:

const firstUser = await connection 
    .getRepository(User) 
    .createQueryBuilder("user") 
    .where("user.id = :id", { id: 1 }) 
    .getOne(); 

Construye la siguiente consulta SQL que se muestra a continuación, que se traduce en una excelente consulta SQL sin formato.

SELECT 
    user.id as userId, 
    user.firstName as userFirstName, 
    user.lastName as userLastName 
FROM users user 
WHERE user.id = 1 

Los mejores ORM de TypeScript: MikroORM

MikroORM es un ORM TypeScript de código abierto para Node.js basado en los patrones Data Mapper, Unit of Work y Identity Map.

Características

MikroORM tiene soporte para bases de datos SQL y NoSQL, incluidas las bases de datos MongoDB, MySQL, MariaDB, PostgreSQL y SQLite. Se pueden admitir más bases de datos a través de controladores personalizados .

También es compatible con un generador de consultas para cuando necesitamos ejecutar una consulta SQL sin todas las cosas de ORM involucradas. Al hacerlo, podemos redactar la consulta nosotros mismos o usar el asistente del generador de consultas para construir la consulta por nosotros.

MikroORM viene con muchas funciones avanzadas , que incluyen soporte para eventos y Hooks, un generador de esquemas, migraciones y propagación.

Instalacion y configuracion

Para instalar, todo lo que necesitamos ejecutar es el paquete de controladores para cada proveedor de nuestra elección, como se muestra a continuación.

npm i -s @mikro-orm/core @mikro-orm/mongodb     # for mongo 
npm i -s @mikro-orm/core @mikro-orm/mysql       # for mysql/mariadb 
npm i -s @mikro-orm/core @mikro-orm/mariadb     # for mysql/mariadb 
npm i -s @mikro-orm/core @mikro-orm/postgresql  # for postgresql 
npm i -s @mikro-orm/core @mikro-orm/sqlite      # for sqlite 

Configuración de mecanografiado

A continuación, necesitaremos habilitar el soporte para decoradores y esModuleInteropen nuestro tsconfig.json, como antes. Vea abajo:

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "target": "ES2018",
    "module": "commonjs",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true,
    "emitDecoratorMetadata": true, 
    "experimentalDecorators": true, 
}

MikroORM también viene con varias herramientas de línea de comandos que son extremadamente útiles durante el desarrollo, como SchemaGeneratory EntityGenerator.

Para trabajar con la CLI, necesitamos instalar el @mikro-orm/clipaquete localmente. Tenga en cuenta que la versión debe estar alineada con el @mikro-orm/corepaquete.

$ yarn add @mikro-orm/cli 

Los mejores ORM de TypeScript: Sequelize

Sequelize es un conocido ORM de Node.js basado en Promise que funciona con MySQL, MariaDB, SQLite, Microsoft SQL Server y PostgreSQL. Tiene un gran conjunto de características, lo que significa que a los desarrolladores les encanta.

Características

Sequelize viene con documentación sólida , que admite muchas funciones excelentes, como inicialización de bases de datos, migraciones, validaciones de modelos, consultas sin procesar y compatibilidad con transacciones. Sequelize también proporciona sus propias definiciones de TypeScript.

Tenga en cuenta que, por ahora, solo se admiten las versiones de TypeScript ≥ 4.1. Desafortunadamente, el soporte de TypeScript para Sequelize no sigue a SemVer. Sequelize también depende en gran medida de las asignaciones de propiedades en tiempo de ejecución para que las declaraciones de tipos manuales funcionen con modelos, lo cual es otra desventaja.

Aunque la configuración con TypeScript es similar a nuestra guía de configuración anterior para otros ORM, para obtener más información sobre cómo configurar sus proyectos de TypeScript con Sequelize, consulte nuestra publicación anterior sobre este tema .

Para evitar conflictos con diferentes versiones de Node, los tipos de Node no están incluidos en nuestra demostración. Debe instalar @types/nodemanualmente usted mismo si desea probarlo. Más detalles se pueden encontrar aquí .

Instalacion y configuracion

Sequelize está disponible a través de npm y Yarn. Para instalar podemos ejecutar:

# using npm 
npm i sequelize 
npm i @sequelize/core 

# using yarn 
yarn add sequelize 
yarn add @sequelize/core

También tendremos que instalar manualmente el controlador para nuestra base de datos de elección. Para conectarnos a la base de datos, debemos crear una instancia de Sequelize. Podemos pasar los parámetros de conexión al constructor Sequelize o pasar un único URI de conexión. Ambas opciones se describen a continuación:

const { Sequelize } = require('@sequelize/core'); 

// Option 1: Passing a connection URI 
const sequelize = new Sequelize('sqlite::memory:') // Example for sqlite 
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Example for postgres 

// Option 2: Passing parameters separately (sqlite) 
const sequelize = new Sequelize({ 
  dialect: 'sqlite', 
  storage: 'path/to/database.sqlite' 
}); 

// Option 3: Passing parameters separately (other dialects) 
const sequelize = new Sequelize('database', 'username', 'password', { 
  host: 'localhost', 
  dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ 
}); 

Los mejores ORM de TypeScript: Objection.js

Objection.js es un ORM compatible con SQL para aplicaciones Node.js. Proporciona todos los beneficios de un generador de consultas SQL y un potente conjunto de API para trabajar también con bases de datos relacionales.

De hecho, se puede decir que Objection es un generador de consultas relacionales. La objeción se basa en un generador de consultas SQL, knex . Debido a esto, todas las bases de datos compatibles con Knex son igualmente compatibles con Objection.js. Incluyen SQLite3, PostgreSQL y MySQL.

La objeción se puede instalar a través de npm o Yarn como de costumbre. Dado que utiliza Knex como su capa de acceso a la base de datos, también debemos instalarlo.

npm install objection knex 
yarn add objection knex 

También necesitamos instalar uno de los siguientes, dependiendo del dialecto de la base de datos que planeamos usar:

npm install pg 
npm install sqlite3 
npm install mysql 
npm install mysql2 

Objection admite bases de datos basadas en documentos, transacciones, Hooks, validación y tiene un ecosistema de complementos en crecimiento. También admite consultas SQL sin formato. Se pueden encontrar más detalles en la guía de documentación .

Configuración de mecanografiado

Con Objection.js podemos ampliar el generador de consultas con TypeScript, aunque todavía no es una característica totalmente compatible. Necesitamos agregar algunos tipos adicionales al generador de consultas personalizadas. Para hacerlo, todo lo que tenemos que hacer es definir un BaseModel, solo una vez. Vea a continuación de la documentación.

import { Model } from 'objection';

class YourQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<M, R> {
  // Make sure to change the name of the query builder classes. 
  ArrayQueryBuilderType!: MyQueryBuilder<M, M[]>;
  SingleQueryBuilderType!: MyQueryBuilder<M, M>;
  NumberQueryBuilderType!: MyQueryBuilder<M, number>;

  customMethod(something: number): this {
    //use function;
  }
}

class BaseModel extends Model {
  QueryBuilderType!: YourQueryBuilder<this>;
  static QueryBuilder = YourQueryBuilder;
}

Después de esto, podemos heredar de BaseModel con el generador de consultas definido. Más detalles en la documentación .

Conclusión

En TypeScript, si bien tenemos una diversidad de ORM para elegir, es más fácil interactuar con algunos a través de una capa de interfaz API más agradable, mientras que otros ofrecen un mejor rendimiento y consultas más optimizadas e intuitivas.

Un argumento de peso para cualquiera que use ORM sería que, en general, ahorran mucho tiempo de desarrollo y tiempo en la realización de tareas de mantenimiento, que suelen ser aburridas y repetitivas. Además, la capacidad de cambiar fácilmente los esquemas de la base de datos y acceder a ellos sin problemas en minutos, sin necesidad de aprender todos los modelos de datos o las funciones internas de la base de datos, es de hecho una gran victoria para los ORM.

Los desarrolladores deberían poder solicitar los datos que necesitan en lugar de tener que preocuparse por escribir consultas, y una abstracción que tome las decisiones correctas para ellos puede ser de gran ayuda. En algunos casos, sin embargo, esto puede significar que la abstracción impone ciertas restricciones que pueden hacer que nuestras consultas sean lentas y engorrosas, que es donde surgen muchos de los problemas con los ORM.

En resumen, los ORM son más útiles para aplicaciones que tienen patrones simples de acceso a datos, por ejemplo, aplicaciones sin consultas complejas. Para aplicaciones CRUD simples y aquellas que requieren consultas simples, los ORM pueden ser una herramienta útil para el trabajo. Por otro lado, si deseamos que la velocidad sea una prioridad en términos de rendimiento de consultas, el uso de ORM no sería beneficioso en ese sentido.

Fuente: https://blog.logrocket.com/best-typescript-orms/#best-typescript-orms-typeorm

#typescript #orm #typeorm 

Los Mejores ORM de TypeScript