Связи: статьи и комментарии
Связь (relation) описывает, как строки одной таблицы относятся к строкам другой. У одной статьи может быть много комментариев, а каждый комментарий принадлежит одной статье — это связь «один ко многим». TypeORM задаёт её декораторами @OneToMany() и @ManyToOne() и сам добавляет в таблицу комментариев столбец со ссылкой на статью.
Ресурс комментариев
nest g resource commentsВыберите REST API и ответьте n на вопрос о CRUD-точках: маршруты комментариев будут вложены в адрес статьи, и их удобнее написать вручную.
Сущность комментария
Создайте файл src/comments/entities/comment.entity.ts:
// src/comments/entities/comment.entity.ts
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Post } from '../../posts/entities/post.entity';
@Entity()
export class Comment {
@PrimaryGeneratedColumn()
id: number;
@Column('text')
text: string;
@CreateDateColumn()
createdAt: Date;
@ManyToOne(() => Post, (post) => post.comments, { onDelete: 'CASCADE' })
post: Post;
}@ManyToOne() — «много комментариев к одной статье». Первый аргумент называет связанную сущность, второй — обратное свойство в ней. onDelete: 'CASCADE' удаляет комментарии вместе со статьёй. В таблице comment появится столбец postId.
Обратная сторона в статье
// src/posts/entities/post.entity.ts
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Comment } from '../../comments/entities/comment.entity';
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 120 })
title: string;
@OneToMany(() => Comment, (comment) => comment.post)
comments: Comment[];
}Остальные поля статьи не изменились. @OneToMany() не создаёт столбцов — он позволяет загрузить комментарии из статьи.
Сервис и контроллер комментариев
Зарегистрируйте сущность в модуле: imports: [TypeOrmModule.forFeature([Comment]), PostsModule], а в PostsModule добавьте exports: [PostsService].
// src/comments/comments.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PostsService } from '../posts/posts.service';
import { Comment } from './entities/comment.entity';
@Injectable()
export class CommentsService {
constructor(
@InjectRepository(Comment)
private readonly commentsRepository: Repository<Comment>,
private readonly postsService: PostsService,
) {}
async create(postId: number, text: string): Promise<Comment> {
const post = await this.postsService.findOne(postId);
const comment = this.commentsRepository.create({ text, post });
return this.commentsRepository.save(comment);
}
}postsService.findOne() бросит 404, если статьи нет, а иначе вернёт её — объект статьи присваивается свойству post.
// src/comments/comments.controller.ts
import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common';
import { CommentsService } from './comments.service';
import { CreateCommentDto } from './dto/create-comment.dto';
@Controller('posts/:postId/comments')
export class CommentsController {
constructor(private readonly commentsService: CommentsService) {}
@Post()
create(@Param('postId', ParseIntPipe) postId: number, @Body() dto: CreateCommentDto) {
return this.commentsService.create(postId, dto.text);
}
}CreateCommentDto содержит одно поле text с правилами @IsString() и @IsNotEmpty(). Отправьте POST /posts/1/comments с телом {"text":"Отличная статья"} — придёт созданный комментарий.
Загрузка связанных данных
По умолчанию связи не загружаются. Их перечисляют в relations:
// src/posts/posts.service.ts
async findOne(id: number): Promise<Post> {
const post = await this.postsRepository.findOne({
where: { id },
relations: { comments: true },
});
if (!post) {
throw new NotFoundException(`Статья ${id} не найдена`);
}
return post;
}Откройте /posts/1 — в ответе появится массив comments.
Частая ошибка
Код обращается к post.comments.length, а сервер падает с Cannot read properties of undefined. Причина в том, что статью загрузили без relations, и свойства comments у объекта просто нет. TypeORM никогда не подгружает связи сам: если они нужны, указывайте relations в каждом запросе, где к ним обращаетесь.