0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

優れたNest.jsブログを構築する:投稿のタグ

Posted at

Cover

前のチュートリアルでは、ブログに訪問者追跡を追加し、各記事の人気度を視覚的に確認できるようにしました。

ブログはどう見てもかなり完成に近づいていますが、まだ何かが足りないようです。ブログにはすでに多くの記事がありますが、ユーザーはそれらの中で迷ってしまうかもしれません…。では、ユーザーは興味のあるトピックをどのように素早く見つけることができるでしょうか?

その通り、ブログにはタグ機能が必要です。

タグは、記事のテーマや内容を表示するために使用されます。記事には複数のキーワードを割り当てることができます(例:「テクニカルチュートリアル」、「Nest.js」、「データベース」)。

次の2つのチュートリアルで、ブログシステムにタグ機能を追加します。このチュートリアルでは、まず記事の作成および編集時にタグを設定するサポートを実装します。

ステップ1:データモデリングとリレーションシップの構築

タグエンティティの作成

まず、この新しい概念に対応するモジュールとエンティティを作成しましょう。

nest generate module tags
nest generate service tags

src/tags ディレクトリに tag.entity.ts を作成します。

// src/tags/tag.entity.ts
import { Entity, Column, PrimaryColumn, ManyToMany } from 'typeorm';
import { Post } from '../posts/post.entity';

@Entity()
export class Tag {
  @PrimaryColumn({ type: 'uuid', default: () => 'gen_random_uuid()' })
  id: string;

  @Column({ unique: true })
  name: string;

  @ManyToMany(() => Post, (post) => post.tags)
  posts: Post[];
}

投稿エンティティを更新してリレーションシップを確立する

次に、投稿とタグの間の関連を確立するために src/posts/post.entity.ts ファイルを更新する必要があります。

1つの投稿には複数のタグを持つことができ、1つのタグは複数の投稿に関連付けることができます。これは 多対多 のリレーションシップです。

// src/posts/post.entity.ts
import { Entity, Column, PrimaryColumn, CreateDateColumn, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
import { Tag } from '../tags/tag.entity';
// ... その他のインポート

@Entity()
export class Post {
  // ... その他のフィールド (id, title, content など)

  @ManyToMany(() => Tag, (tag) => tag.posts, {
    cascade: true, // 投稿を介した新しいタグの作成を許可します
  })
  @JoinTable() // `@JoinTable()` はリレーションの一方の側で指定する必要があります
  tags: Tag[];

  // ... その他のリレーション (comments, views など)
}

@JoinTable() は多対多のリレーションシップを定義するために必要なデコレーターです。PostTag の関連を管理するために、結合テーブル post_tags_tag を作成する必要があります。

データベーステーブル構造の更新

新しいテーブルとフィールドを作成するために、次のSQLステートメントを実行してください。

-- タグテーブルの作成
CREATE TABLE "tag" (
    "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    "name" VARCHAR UNIQUE NOT NULL
);

-- post_tags_tag 結合テーブルの作成
CREATE TABLE "post_tags_tag" (
    "postId" UUID REFERENCES "post" ON DELETE CASCADE,
    "tagId" UUID REFERENCES "tag" ON DELETE CASCADE,
    PRIMARY KEY ("postId", "tagId")
);

Leapcell でデータベースを作成した場合、

Leapcell

ウェブサイトのデータベース管理ページに移動し、上記のステートメントをSQLインターフェイスに貼り付けて実行するだけで、グラフィカルインターフェイスを使用してSQLステートメントを簡単に実行できます。

ImageP0

ステップ2:バックエンドロジックの実装

タグの作成と検索を処理するサービスを作成し、投稿を作成する際のこれらの関連を処理するように PostsService を更新する必要があります。

TagsService の記述

このサービスのロジックは比較的単純で、主にタグの検索または作成に焦点を当てています。

src/tags/tags.module.ts を開き、TypeOrmModule を登録します。

// src/tags/tags.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Tag } from './tag.entity';
import { TagsService } from './tags.service';

@Module({
  imports: [TypeOrmModule.forFeature([Tag])],
  providers: [TagsService],
  exports: [TagsService], // 他のモジュールが使用できるようにサービスをエクスポートします
})
export class TagsModule {}

src/tags/tags.service.ts で:

// src/tags/tags.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In } from 'typeorm';
import { Tag } from './tag.entity';

@Injectable()
export class TagsService {
  constructor(
    @InjectRepository(Tag)
    private tagsRepository: Repository<Tag>
  ) {}

  // タグの検索または作成
  async findOrCreate(tagNames: string[]): Promise<Tag[]> {
    const existingTags = await this.tagsRepository.findBy({ name: In(tagNames) });
    const existingTagNames = existingTags.map((tag) => tag.name);

    const newTagNames = tagNames.filter((name) => !existingTagNames.includes(name));

    const newTags = newTagNames.map((name) => this.tagsRepository.create({ name }));
    await this.tagsRepository.save(newTags);

    return [...existingTags, ...newTags];
  }
}

PostsService を更新して関連を処理する

create および findOne メソッドを変更する必要があります。

まず、PostsModuleTagsModule をインポートします。

// src/posts/posts.module.ts
import { TagsModule } from '../tags/tags.module';

@Module({
  imports: [TypeOrmModule.forFeature([Post]), CommentsModule, TrackingModule, TagsModule],
  // ...
})
export class PostsModule {}

次に src/posts/posts.service.ts を更新します。

// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';
import { TagsService } from '../tags/tags.service';
// ... その他のインポート

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private postsRepository: Repository<Post>,
    private readonly tagsService: TagsService // TagsService をインジェクトします
  ) {}

  // create メソッドを更新します
  async create(post: Omit<Partial<Post>, 'tags'> & { tags: string }): Promise<Post> {
    const tagNames = post.tags
      .split(',')
      .map((tag) => tag.trim())
      .filter(Boolean);
    const tags = await this.tagsService.findOrCreate(tagNames);

    const newPost = this.postsRepository.create({
      ...post,
      tags,
    });

    return this.postsRepository.save(newPost);
  }

  // 関連データをロードするために findOne メソッドを更新します
  findOne(id: string): Promise<Post | null> {
    return this.postsRepository.findOne({
      where: { id },
      relations: ['tags'], // タグをロードします
    });
  }

  // ... その他のメソッド
}

ステップ3:フロントエンドページの統合

最終ステップは、EJSテンプレートを変更して、投稿の作成および編集時にタグを設定できるようにし、投稿詳細ページにタグを表示することです。

新規/編集投稿ページの更新

views/new-post.ejs を開き、タグを入力するためのフォームフィールドを追加します。

<form action="/posts" method="POST" class="post-form">
  <div class="form-group">
    <label for="tags">Tags (comma-separated)</label>
    <input type="text" id="tags" name="tags" />
  </div>

  <button type="submit">Submit</button>
</form>

簡単にするため、ここでは複数のタグにコンマ区切りの入力を利用しています。実際のプロジェクトでは、ユーザーエクスペリエンスを向上させるために、専用のタグ入力コンポーネント、既存タグの自動マッチングなど、より複雑なUIコンポーネントやロジックを使用できます。

投稿詳細ページの更新

views/post.ejs を開き、投稿のメタデータにタグを表示します。

<article class="post-detail">
  <h1><%= post.title %></h1>
  <small> <%= new Date(post.createdAt).toLocaleDateString() %> | Views: <%= viewCount %> </small>

  <div class="post-content"><%- post.content %></div>

  <% if (post.tags && post.tags.length > 0) { %>
  <div class="tags-section">
    <strong>Tags:</strong>
    <% post.tags.forEach(tag => { %>
    <a href="/tags/<%= tag.id %>" class="tag-item"><%= tag.name %></a>
    <% })
    %>  </div>
  <% }
  %> </article>

実行とテスト

アプリケーションを再起動します。

npm run start:dev

ブラウザを開き、 http://localhost:3000/ にアクセスします。

新しい投稿を作成すると、下部にタグ入力ボックスが表示されます。

Nest.js, Tutorial のように、コンマで区切ってタグを入力し、送信します。

ImageP1

送信後、投稿詳細ページに移動すると、投稿のタグが正常に表示されていることがわかります。

ImageP2

これで、ブログはタグの作成と表示をサポートするようになりました。ただし、ユーザーはまだタグで記事をフィルタリングすることはできません。この機能は次のチュートリアルで実装します。


過去のチュートリアル:


Xでフォローする:@LeapcellJP


ブログでこの記事を読む

関連記事:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?