В моем предыдущем сообщении в блоге мы углубились в TypeORM и создали базовое приложение CRUD todo. В этом сообщении блога мы продолжим наше исследование, выведя приложение todo на новый уровень с добавлением сложности отношений «один ко многим.

В контексте приложения Todo отношение «один ко многим» можно проиллюстрировать следующим образом:

У одного TodoList может быть много TodoItem.

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

В этом сценарии мы можем определить класс сущности TodoList с первичным ключом id и свойством name, представляющим имя списка. Мы также можем определить класс сущностей TodoItem с первичным ключом id, свойством description, представляющим описание задачи, и внешним ключом todoListId, ссылающимся на id из TodoList.

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

  @Column()
  name: string;

  @OneToMany(() => TodoItem, todoItem => todoItem.todoList)
  todoItems: TodoItem[];
}

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

  @Column()
  description: string;

  @ManyToOne(() => TodoList, todoList => todoList.todoItems)
  todoList: TodoList;

  @Column()
  todoListId: number;
}

В нашем предыдущем примере мы использовали декораторы @OneToMany и @ManyToOne, предоставленные TypeORM, для установления отношения «один ко многим» между сущностями TodoList и TodoItem. Декоратор @OneToMany использовался для определения обратной стороны отношения в классе TodoList, а декоратор @ManyToOne определял владеющую сторону отношения в классе TodoItem.

Определив обе стороны связи, мы можем обеспечить лучшую согласованность данных, сделать запросы к данным более интуитивными и улучшить организацию кода. С помощью этой настройки мы можем легко получить все объекты TodoItem, связанные с конкретным объектом TodoList, запросив свойство todoItems объекта TodoList.

Давайте сосредоточимся на декораторе @OneToMany, который используется для определения отношения «один ко многим» между двумя объектами в TypeORM. Синтаксис этого декоратора:

@OneToMany(type => RelatedEntity, relatedEntity => relatedEntity.entityProperty)

Часть «type =› RelatedEntity» указывает тип связанной сущности, которая определяет «многие» стороны отношения. В нашем случае связанной сущностью является TodoItem. Часть «relatedEntity =› relatedEntity.entityProperty» указывает свойство в связанном объекте, которое представляет «одну» сторону отношения. В нашем примере это свойство todoList, представляющее сущность TodoList, которая имеет множество сущностей TodoItem.

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

Теперь, когда мы определили наши сущности TodoList и TodoItem, мы можем приступить к реализации функциональности CRUD с отношением «один ко многим». Следующим шагом является определение нашего TodoListService, который будет содержать методы CRUD:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { TodoList } from './entities/todo-list.entity';

@Injectable()
export class TodoListService {
  constructor(
    @InjectRepository(TodoList)
    private readonly todoListRepository: Repository<TodoList>,
  ) {}

  async findAll(): Promise<TodoList[]> {
    return this.todoListRepository.find();
  }

  async create(name: string, items: TodoItem[]): Promise<TodoList> {
    const todoList = new TodoList();
    todoList.name = name;
    todoList.todoItems = items;
  
    return this.todoListRepository.save(todoList);
  }

  async update(id: number, updateTodoListDto: UpdateTodoListDto): Promise<TodoList> {
    const todoList = await this.todoListRepository.findOne(id, { relations: ['todoItems'] });
    if (!todoList) {
      throw new NotFoundException(`Todo list with id ${id} not found`);
    }
  
    const { name, todoItems } = updateTodoListDto;
  
    if (name) {
      todoList.name = name;
    }
  
    if (todoItems) {
      // Remove existing todo items
      todoList.todoItems = [];
  
      // Create new todo items and associate them with the todo list
      for (const todoItemDto of todoItems) {
        const todoItem = new TodoItem();
        todoItem.name = todoItemDto.name;
        todoItem.done = todoItemDto.done;
        todoItem.todoList = todoList;
        await this.todoItemRepository.save(todoItem);
        todoList.todoItems.push(todoItem);
      }
    }
  
    return this.todoListRepository.save(todoList);
  }

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

Метод findAll извлекает все TodoList объектов. Метод create создает новый объект TodoList с заданным именем и списком объектов TodoItem. Метод update обновляет существующий объект TodoList с заданным идентификатором и свойствами, указанными в объекте UpdateTodoListDto, включая список связанных объектов TodoItem. Метод delete удаляет существующий объект TodoList с заданным идентификатором.

Наконец, мы можем определить наш TodoListController для обработки HTTP-запросов:

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { TodoListService } from './todo-list.service';
import { CreateTodoListDto } from './dto/create-todo-list.dto';
import { UpdateTodoListDto } from './dto/update-todo-list.dto';
import { TodoList } from './entities/todo-list.entity';

@Controller('todo-lists')
export class TodoListController {
  constructor(private readonly todoListService: TodoListService) {}

  @Get()
  async findAll(): Promise<TodoList[]> {
    return this.todoListService.findAll();
  }

  @Post()
  async create(@Body() createTodoListDto: CreateTodoListDto): Promise<TodoList> {
    return this.todoListService.create(createTodoListDto.name, createTodoListDto.todoItems);
  }

  @Put(':id')
  async update(
    @Param('id') id: number,
    @Body() updateTodoListDto: UpdateTodoListDto,
  ): Promise<TodoList> {
    return this.todoListService.update(id, updateTodoListDto);
  }

  @Delete(':id')
  async delete(@Param('id') id: number): Promise<void> {
    return this.todoListService.delete(id);
  }
}

Мы определили контроллер API с четырьмя конечными точками, которые соответствуют операциям CRUD, определенным в файле TodoListService. Декоратор @Get используется для метода findAll, декоратор @Post для create, декоратор @Put для update и декоратор @Delete для delete. Декораторы @Body и @Param используются для извлечения данных из тела запроса и параметров URL соответственно.

Обратите внимание, что контроллер зависит от экземпляра класса TodoListService, который передается конструктору с помощью внедрения зависимостей.

В заключение, связь «один ко многим» в TypeORM и NestJS может значительно улучшить функциональность вашего приложения, позволяя вам создавать более сложную и всеобъемлющую модель данных. Поняв, как реализовать это отношение и связанные с ним декораторы, вы сможете создавать более надежные и эффективные приложения. Поэтому не стесняйтесь экспериментировать с отношениями «один ко многим» в своем следующем проекте!

Если вы найдете эту информацию полезной, поддержите меня и следите за обновлениями.🙂