1668670503
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.
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.
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.
Before you start following along with this tutorial, make sure you have the following things ready:
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.
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
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
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
Subscription
allows Azure keep track of where to charge for the resource used. You can use your free subscription here.resource group
is a central grouping for your resource(s). It helps you structure and organize your Azure resources based on your wants.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
When successfully created, the following windows should show up:
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
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 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
Cool! You've configured your Azure blob storage. The next step is to setup and link your NestJS application with the blob storage.
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
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.
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
ornpm install @azure/storage-blob uuidv4 @types/multer
Now, let's add the connection string we saved earlier to our .env file
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.
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.
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.
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
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;
}
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
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
And confirm on Azure:
Confirm image on the Azure portal
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.
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.
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
1661585642
Pagination helper method for TypeORM repositories or queryBuilders with strict typings
$ 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
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 addDefaultValuePipe
. 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 ofhttp://cats.com/cats
{
"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
@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 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 });
}
}
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,
);
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);
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;
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
},
...
]
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
.
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
.
Author: nestjsx
Source code: https://github.com/nestjsx/nestjs-typeorm-paginate
License: MIT license
#nest #nestjs #node #typeorm
1659533643
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.
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.
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:
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 TypeORM và mysql2 : 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 đó.
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.ts
tệ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 TypeOrmModule
từ mô-đun typeorm mà chúng tôi đã cài đặt trước đó. Chúng tôi đã sử dụng forRoot
phươ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 entities
thuộ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 Image
thực thể mà bạn sẽ tạo trong thời gian ngắn: chúng tôi cũng có thuộc synchronize
tính được đặt true
để tự động di chuyển cơ sở dữ liệu.
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ó id
trườ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í, name
trường để lưu trữ tên của các hình ảnh sẽ được tải lên bằng trình @Column
trang 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()
và @UpdateDateColumn()
.
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.ts
tệ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 injectRepository
trang trí để đưa imageRepository
vào AppService
và trình trang trí Repository
cung 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 createImage
dị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.
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.ts
tệ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
, UploadedFile
và UseInterceptors
. 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ố, fieldName
là chuỗi cung cấp tên của trường từ biểu mẫu HTML chứa tệp và options
là đố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ề createImage
chứ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 storage
tính bằng cách sử dụng diskStorage
hà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 filter
thuộ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.ts
tệ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 files
thư mục trong thư mục src
để thực sự lưu trữ các 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-static
mô-đun bằng cách chạy lệnh bên dưới.
npm install --save @nestjs/serve-static
Sau đó, đăng ký ServeStaticModule
mảng nhập trong app.module.ts
tệ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ừ đó.
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/images
và 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.
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
1659526403
В этом блоге мы научим вас, как создать функцию загрузки файлов с помощью NestJS и MySQL.
Многие разработчики презирают загрузку файлов. Это может быть связано с отсутствием знаний о наилучшем подходе или с трудностями при определении того, как настроить приложение Nest.js для обработки загрузки файлов. Многие люди могут захотеть сохранить свои файлы непосредственно в базе данных MySQL или сохранить имена изображений и сохранить изображение на диске: все зависит от их предпочтений и целей, которых они хотят достичь. В этом руководстве вы узнаете, как создать функцию загрузки файлов с помощью Nestjs и MySQL.
Прежде чем приступить к выполнению этого руководства, убедитесь, что ваша система соответствует следующим требованиям:
Как только вышеупомянутые требования будут выполнены, перейдите к установке интерфейса командной строки 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. Для начала добавьте код в 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')
}),],
...
В приведенном выше фрагменте кода вы указали место, где находятся файлы и откуда они могут быть отправлены.
Теперь откройте Postman и протестируйте приложение, отправив POST-запрос на конечную точку localhost:4000/images
, и передайте полезную нагрузку в теле запроса в виде данных формы.
Наш файл
Если вы сейчас посмотрите на папку с файлами, вы должны увидеть файл, который вы загрузили. Не стесняйтесь идти вперед: тестируйте и экспериментируйте с другими маршрутами.
Из этого руководства вы узнали, как обрабатывать загрузку файлов с помощью NestJS и MySQL. Вы узнали, как подключиться к базе данных MySQL с помощью TypeORM, а также создали объект и загрузили изображения в приложение Nestjs.
Ссылка: https://arctype.com/blog/nestjs-mysql-tutorial-fileuploads/
1659511980
在這篇博客中,我們將教你如何使用 NestJS 和 MySQL 構建文件上傳功能。
許多開發人員鄙視處理文件上傳。這可以歸因於缺乏對最佳方法的了解,或者難以確定如何配置他們的 Nest.js 應用程序來處理文件上傳。許多人可能希望將他們的文件直接保存到 MySQL 數據庫,或者保存圖像名稱並將圖像保存在磁盤存儲中:這完全取決於他們的偏好和他們想要實現的目標。本教程將教您如何使用 Nestjs 和 MySQL 構建文件上傳功能。
在開始學習本教程之前,請確保您的系統滿足以下要求:
滿足上述要求後,繼續安裝 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
在上面的命令中,您已經安裝了TypeORM和mysql2模塊:它們將使您能夠將應用程序連接到 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
裝飾器來注入,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()
。裝飾器是從. 裝飾器有兩個參數,一個是提供包含文件的 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')
}),],
...
在上面的代碼片段中,您已經指定了文件所在的位置並且可以從中提供服務。
現在打開 Postman 並通過向端點發送 POST 請求來測試應用程序,並將localhost:4000/images
請求正文中的有效負載作為表單數據傳遞。
我們的檔案
如果您現在查看文件文件夾,您應該會看到已上傳的文件。隨意繼續:測試並嘗試其他路線。
通過本教程,您學習瞭如何使用 NestJS 和 MySQL 處理文件上傳。您已經學習瞭如何使用 TypeORM 連接到 MySQL 數據庫,並且您還創建了一個實體並將圖像上傳到 Nestjs 應用程序。
鏈接:https ://arctype.com/blog/nestjs-mysql-tutorial-fileuploads/
#nestjs #node #mysql #typeorm #database
1659504720
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/
1659497335
Dans ce blog, nous allons vous apprendre à créer une fonctionnalité de téléchargement de fichiers à l'aide de NestJS et MySQL.
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.
Avant de commencer à suivre ce didacticiel, assurez-vous que votre système répond aux exigences suivantes :
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.
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.ts
fichier 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é TypeOrmModule
du module typeorm que nous avons installé précédemment. Nous avons utilisé la forRoot
mé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 entities
les propriétés, qui nous ont permis de spécifier les entités dans notre module et qui nous donneront accès à l' Image
entité que vous allez créer prochainement : nous avons également la synchronize
propriété définie sur true
pour migrer automatiquement la base de données.
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 id
champ pour générer des identifiants aléatoires pour chaque enregistrement dans la base de données à l'aide du @PrimaryGeneratedColumn()
décorateur, le name
champ pour stocker les noms des images qui seront téléchargées à l'aide du @Column
décorateur, les champs dateCreated et dateUpdate pour enregistrer la date à laquelle un enregistrement a été créé et mis à jour en utilisant @CreateDateColumn()
et @UpdateDateColumn()
.
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.ts
fichier, 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 injectRepository
décorateur à injecter imageRepository
dans AppService
et Repository
qui vous fournit les méthodes nécessaires pour effectuer certaines opérations sur votre base de données. Donc, pour le createImage
service d'image, nous enregistrons le nom de l'image qui a été téléchargée et qui sera transmise au contrôleur.
Créons maintenant les contrôleurs pour utiliser les services. Dans le app.controller.ts
fichier 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
, UploadedFile
et 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-express
package. Le @UploadedFile()
décorateur est exporté depuis @nestjs/common
. Le FileInterceptor()
décorateur prend deux arguments, fieldName
qui est la chaîne qui fournit le nom du champ du formulaire HTML qui contient un fichier, et options
qui est un objet facultatif de type MulterOptions. C'est le même objet utilisé par le constructeur multer.
En ce qui concerne la createImage
fonction, 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 storage
propriété à l'aide de la diskStorage
fonction 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 filter
proprié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.ts
fichier :
...
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 files
dossier dans le src
répertoire pour stocker les fichiers.
Pour réellement servir les images téléchargées sur votre application à l'utilisateur, vous devez installer le serve-static
module en exécutant la commande ci-dessous.
npm install --save @nestjs/serve-static
Ensuite, enregistrez le ServeStaticModule
dans le tableau des importations dans le app.module.ts
fichier 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.
Ouvrez maintenant Postman et testez l'application en envoyant une requête POST au point de terminaison localhost:4000/images
et 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.
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
1658372400
Pagination helper method for TypeORM repositories or queryBuilders with strict typings
$ 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
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 addDefaultValuePipe
. 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 ofhttp://cats.com/cats
{
"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
@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 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 });
}
}
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,
);
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);
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;
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
},
...
]
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
.
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
1657565580
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!
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ắ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ủ
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.
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 và đơ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 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.
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.
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 cartEntity và orderEntity 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.
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, 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, JwtService và bcrypt . 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).
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ật và xó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ạ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 User và productService 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
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
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 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
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.
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
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.
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ến
sả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.
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.
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
1657551120
Nestjs 是一個尖端的 Node.js 框架,用於開發高效、可靠和可擴展的服務器端應用程序。與 NoSQL 和 SQL 數據庫(如 MongoDB、Yugabyte、SQLite、Postgres、MySQL等)集成很簡單。它支持流行的對象關係映射器,例如 TypeORM Sequelize 和 Mongoose。
在本教程中,我們將使用 SQLite 和 TypeORM 創建一個電子商務應用程序。我們還將了解Arctype,一個強大的 SQL 客戶端和數據庫管理工具。
讓我們開始吧!
本教程是一個動手演示,可幫助您入門。確保您滿足以下要求:
要開始使用 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 安裝軟件包時,您可以喝杯咖啡。安裝完成後,讓我們動手吧。
有了運行應用程序所需的所有必要包,讓我們繼續創建應用程序模塊。要構建一個乾淨且易於維護的應用程序,您將為該應用程序中實現的所有功能生成單獨的模塊。由於這是一個電子商務應用程序,您將擁有authentication、cart、products和orders。所有這些都將在各自獨立的模塊中。讓我們從身份驗證模塊開始。
使用以下命令生成身份驗證模塊:
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 數據庫並創建模塊實體。首先,打開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;
}
定義屬性
在代碼片段中,您導入了設置數據庫表所需的裝飾器。您還導入了您將很快創建的cartEntity和orderEntity類。使用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裝飾器、JwtService和bcrypt模塊。然後,使用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 服務。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、數量和用戶作為參數。然後檢查用戶的購物車中是否已有該商品。如果是這樣,您增加數量並更新該項目的總價。否則,您將商品添加到用戶的購物車。
接下來,通過使用以下代碼片段在cart.module.ts文件中註冊cartEntity、productEntity 用戶實體和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是一個使用起來很有趣的 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
1657536660
Nestjs — это передовая платформа Node.js для разработки эффективных, надежных и масштабируемых серверных приложений. Его легко интегрировать с базами данных NoSQL и SQL, такими как MongoDB, Yugabyte , SQLite , Postgres , MySQL и другими. Он поддерживает популярные объектно-реляционные преобразователи, такие как TypeORM Sequelize и Mongoose.
В этом руководстве мы создадим приложение электронной коммерции с помощью SQLite и TypeORM. Мы также рассмотрим Arctype , мощный клиент SQL и средство управления базами данных.
Давайте начнем!
Этот учебник представляет собой практическую демонстрацию, которая поможет вам начать работу. Убедитесь, что вы выполнили следующие требования:
Чтобы начать работу с 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 и создания объектов вашего модуля. Для начала откройте 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 — это клиент 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
1657522201
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!
Ce didacticiel est une démonstration pratique pour vous aider à démarrer. Assurez-vous que vous remplissez les conditions suivantes :
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
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.
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.
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.
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.
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é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.
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).
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
À 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
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
Une fois les services d'application créés avec succès, créons les routes d'API pour les services d'application.
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
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.
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
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.
Testons-les maintenant avec Postman. Les résultats de certains tests sont présentés ci-dessous.
Parcours utilisateur Parcours deconnexion Parcours
produit. 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.
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.
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
1657096260
This is example how to nestjs using the serverless framework
# Custom Variables
custom:
...
mysqlHost:
local: localhost
mysqlUser:
local: user
mysqlPassword:
local: password
mysqlDatabase:
local: dbname
mysqlPort:
local: '3306'
$ npm install serverless -g
$ git clone https://github.com/kop7/serverless-nestjs-typeorm.git 【projectName】
$ cd 【projectName】
$ npm install
$ 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
1654319100
この投稿では、2つのスキーマビルダーであるPothosとTypeGraphQLが、開発者がサービス用のGraphQLスキーマを構築するのにどのように役立つか、およびこれらのツールでTypeScriptを使用して実際にスキーマを構築する方法を比較します。
投稿の最後に、両方のツールといくつかのユースケースで提供される機能を比較します。
GraphQLスキーマは、受け入れることができるデータのタイプを指定するフィールドを持つオブジェクトタイプで構成されています。これらのタイプはスカラータイプと呼ばれ、SDLコアに組み込まれています。GraphQLスキーマを定義すると、それぞれのタイプを持つオブジェクトで定義された指定されたフィールドのみを照会または変更できます。
GraphQLスキーマ内では、スキーマの作成時にクエリとミューテーションのタイプを定義する必要があります。ただし、ミューテーションのタイプは必ずしも必須ではありません。どちらのタイプ定義も、事前定義されたスキーマに基づいて、GraphQLサービスまたはAPIに対して行うすべてのクエリのエントリポイントを定義します。ブログの他の場所で、GraphQLでのスキーマ構築の詳細を読むことができます。
Pothosは、GraphQLとTypeScriptを使用してスキーマを作成および構築する簡単な方法を提供するプラグインです。
TSベースであるため、PothosはGraphQLスキーマ構築に必要な型安全性を提供します。また、TypeScriptの強力な型システムと型推論に基づいて構築されているため、コードを生成したり、どこでも手動の型を使用したりする必要はありません。
graphql
Pothosスキーマは、パッケージの型を使用するプレーンスキーマに構築されます。これは、Node.js用の一般的なGraphQLサーバー実装のほとんどと互換性がある必要があることを意味します。このガイドでは@graphql-yoga/node
、例を実行するために使用しますが、任意のサーバーを使用できます。
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) => ({
hello: t.string({
args: {
name: t.arg.string(),
},
resolve: (parent, { name }) => `hello, ${name || 'World'}`,
}),
}),
});
const server = createServer({
schema: builder.toSchema({}),
});
server.start();
この例ではgraphl-yoga/node
、Pothosスキーマビルダーを使用して単純なボイラープレートサーバーを作成します。Pothosコアからスキーマビルダーをインポートし、新しいスキーマビルダーをインスタンス化します。これにより、GraphQL言語が理解できるプレーンなスキーマが構築されます。
その後、タイプセーフな方法でフィールドタイプと引数を使用してクエリビルダーをセットアップします。リゾルバーは、クエリに渡されたフィールド引数とクエリ自体に対して必要なすべての検証が行われた後、クエリが実行されたときに応答を返す責任があります。
最後に、ビルドされたスキーマをcreateServer
関数に渡し、メソッドを呼び出しますserver.start
。
Pothosを使用すると、オブジェクトタイプの形式でデータの構造を定義できます。これにより、基になるデータタイプの詳細を知ることができます。その後、タイプの定義に進むことができます。ここで、実際のタイプを検証する方法として定義した構造を渡します。
したがって、基本的に、基になるデータ構造に関する型情報を渡す方法が必要です。これにより、フィールドは、オブジェクト型で使用可能なプロパティを認識できます。
型推論の助けを借りて、オブジェクト型に間違ったフィールドを渡したときを確認しobjectType
、オブジェクトがどの型を期待するかを伝えることができるので、型定義に準拠していることを確認できます。スキーマで定義されたフィールドに基づいて、使用可能なデータの性質とそのタイプを判別できます。つまり、スキーマを追加する予定のデータはすべて明示的に定義する必要があります。
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) => ({
nameNew: t.string({
resolve: (parent) => {
const name = console.log(parent.name)
},
}),
ageNew: t.int({
resolve: (parent) => {
const age = console.log(parent.age)
},
}),
}),
});
ご覧のとおり、スキーマで定義されているプロパティに直接アクセスすることはできません。これは、基礎となるスキーマからのみプロパティにアクセスできるようにするための設計によるものです。
親argは、スキーマクラスで指定された現在のタイプのバッキングモデルの値になることに注意してください。
ただし、スキーマまたはモデルで定義されているフィールドプロパティに直接アクセスするには、ここでドキュメントで定義されているexpose
ように、を利用できます。
exposeString(name, options)
注:name
argは、公開されているタイプに一致するバッキングモデルの任意のフィールドにすることができます。
次に、このフィールドの値を解決する関数であるリゾルバーを使用して、実際の値に解決するクエリを実際に記述します。以下のPothosでそのようなクエリを作成しましょう。
builder.queryType({
fields: (t) => ({
Person: t.field({
type: Person,
resolve: () => 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でスキーマを作成するには、以下に示すように、Pothosコアからクラスをインポートするだけです。schemaBuilder
import SchemaBuilder from '@pothos/core';
const builder = new SchemaBuilder<{
Context: {};
}>({
// plugins
});
スキーマビルダーは、グラフの型を作成するのに役立ち、作成された型をGraphQLスキーマに埋め込みます。PothosスキーマビルダーAPI設計の詳細は、ドキュメントに記載されています。
Pothosは主にスキーマビルダーですが、ほとんどのORM、特にPothosのPrismaプラグインを介したPrismaをサポートし、それらとうまく統合されています。このプラグインを使用すると、Prismaベースのオブジェクトタイプを簡単に定義でき、同様に、Prismaモデルに基づいてGraphQLタイプを定義できます。これを実行する方法の例と設定は、ドキュメントに示されています。
もちろん、この統合の注目すべき機能の1つは、厳密に型指定されたAPIのサポート、自動クエリ最適化(n + 1
リレーションのクエリ問題を含む)、同じデータベーススキーマに基づく多くの異なるGraphQLモデルのサポートなどです。ドキュメントはそれをすべてカバーしています。
注:PrismaはPothosと直接統合することもできます。プラグインを使用すると、これら2つのテクノロジーをより簡単に、よりパフォーマンスが高く、より効率的に操作できるようになります。この統合を実行する方法に関するガイドには、より多くの情報が含まれています。
TypeGraphQLは、スキーマを構築するための異なるアプローチを提供します。TypeGraphQLでは、クラスとデコレータマジックのみを使用してスキーマを定義します。graphql-js
これは主に、、、class-validator()
およびシムに依存しておりreflect-metadata
、TypeScriptでの反映が機能します。class-validator
クラスのデコレータベースのプロパティ検証です。
オブジェクトタイプの作成方法など、以前の投稿でTypeGraphQLを使用したGraphQLAPIの構築について詳しく説明しました。
@ObjectType()
class Recipe {
@Field()
title: string;
@Field(type => [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の機能には、検証、承認などが含まれ、開発者がGraphQL APIをすばやく作成し、すべての引数と入力、および/またはオブジェクトタイプ用のTypeScriptインターフェイスを作成する必要性を減らします。TypeGraphQLは、クラスとデコレータのヘルプを使用してスキーマを定義することにより、すべての人が信頼できる唯一の情報源から作業できるようにするのにも役立ちます。これは確かにコードの冗長性を減らすのに役立ちます。
TypeGraphQLは、ユーザーがフレームワークで使用されるIoCコンテナーを提供できるようにすることで、依存性注入をサポートします。
フィールドプロパティは、クラス検証ライブラリで厳密に検証されます。TypeGraphQLは、Pothosよりも柔軟性があり、型パラメーターなど、より柔軟な方法で一部のフィールドの型を宣言する必要がある場合に、ジェネリック型をサポートします。
TypeGraphQLは、メソッドやパラメーターなどのカスタムデコレーターをサポートします。これは、ボイラープレートコードを削減し、複数のリゾルバーに共通して再利用するための優れた方法を提供します。
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/で公開されました。
1652872260
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.
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.
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.
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 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.
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.
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:
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:
Para obtener un modelo de datos en Prisma, existen dos flujos de trabajo principales. Incluyen:
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/client
con 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.
Para usar Prisma con TypeScript, asegúrese de estar usando la versión de TypeScript ≥ 3.3. El tsconfig.json
archivo 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 npm
paquetes como dependencias de desarrollo para que Prisma funcione con Typescript:
npm install ts-node ts-node-dev typescript --save-dev
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.
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.
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.json
archivo:
{
"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 npm
paquete:
npm install typeorm --save
Instale la reflect-metadata
cuñ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.ts
archivo 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.json
continuació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 @Entity
decorador, 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.
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);
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
MikroORM es un ORM TypeScript de código abierto para Node.js basado en los patrones Data Mapper, Unit of Work y Identity Map.
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.
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
A continuación, necesitaremos habilitar el soporte para decoradores y esModuleInterop
en 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 SchemaGenerator
y EntityGenerator
.
Para trabajar con la CLI, necesitamos instalar el @mikro-orm/cli
paquete localmente. Tenga en cuenta que la versión debe estar alineada con el @mikro-orm/core
paquete.
$ yarn add @mikro-orm/cli
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.
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/node
manualmente usted mismo si desea probarlo. Más detalles se pueden encontrar aquí .
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' */
});
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 .
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 .
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