Nest.js Blog Schritt für Schritt: Kommentarsystem
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Im vorherigen Tutorial haben wir ein vollständiges Benutzerauthentifizierungssystem für unseren Blog auf Basis von Sessions und Passport.js integriert. Jetzt können sich Benutzer im Blogsystem registrieren und anmelden und wir haben geschützte Routen, für deren Zugriff ein Login erforderlich ist.
Da wir nun klar zwischen Lesern und Autoren unterscheiden können, ist es nicht der perfekte Zeitpunkt, eine Funktion für die Interaktion zwischen ihnen hinzuzufügen?
In diesem Artikel fügen wir eine grundlegende, aber zentrale Funktion zu unserem Blog hinzu: ein Kommentarsystem.
Insbesondere werden wir die folgenden Funktionalitäten implementieren:
- Anzeige einer Liste von Kommentaren unter jedem Beitrag.
- Ermöglichen von angemeldeten Benutzern, Kommentare zu posten.
Schritt Eins: Erstellen des Datenmodells für Kommentare
Genau wie unsere Post
- und User
-Entitäten benötigt unsere Comment
-Entität eine eigene Datenbanktabelle und eine entsprechende Entitätsdatei.
1. Erstellen der Datenbanktabelle
Führen Sie zunächst die folgende SQL-Anweisung in Ihrer PostgreSQL-Datenbank aus, um die Tabelle comment
zu erstellen. Diese Tabelle stellt über postId
und userId
Beziehungen zu den Tabellen post
und user
her.
CREATE TABLE "comment" ( "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(), "content" TEXT NOT NULL, "createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "postId" UUID REFERENCES "post"("id") ON DELETE CASCADE, "userId" UUID REFERENCES "user"("id") ON DELETE CASCADE );
Tipp:
ON DELETE CASCADE
bedeutet, dass, wenn einPost
oder einUser
gelöscht wird, alle zugehörigen Kommentare automatisch gelöscht werden, um die Datenkonsistenz zu gewährleisten.
2. Erstellen der Comment-Entität
Verwenden Sie als Nächstes die Nest CLI, um ein neues comments
-Modul zu erstellen, das die gesamte Kommentar-bezogene Logik verwaltet.
nest generate module comments nest generate service comments
Fürs Erste benötigen wir keinen separaten Controller für Kommentare, da das Einreichen und Anzeigen von Kommentaren an die Beiträge angehängt wird. Daher werden wir die relevante Routing-Logik in die PostsController
und einen später zu erstellenden CommentsController
legen.
Erstellen Sie eine Datei namens comment.entity.ts
im Verzeichnis src/comments
:
// src/comments/comment.entity.ts import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, ManyToOne } from 'typeorm'; import { User } from '../users/user.entity'; import { Post } from '../posts/post.entity'; @Entity() export class Comment { @PrimaryGeneratedColumn('uuid') id: string; @Column('text') content: string; @CreateDateColumn() createdAt: Date; @ManyToOne(() => User, (user) => user.comments) user: User; @ManyToOne(() => Post, (post) => post.comments) post: Post; }
3. Aktualisieren der zugehörigen Entitäten
Wir haben die ManyToOne
-Beziehung von Kommentaren zu Benutzern und Beiträgen definiert. Nun müssen wir die umgekehrte OneToMany
-Beziehung in den Entitäten User
und Post
definieren.
Aktualisieren Sie src/users/user.entity.ts
:
// src/users/user.entity.ts import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { Comment } from '../comments/comment.entity'; // Importieren Sie Comment @Entity() export class User { @PrimaryGeneratedColumn('uuid') id: string; @Column({ unique: true }) username: string; @Column() password: string; @OneToMany(() => Comment, (comment) => comment.user) // Fügen Sie die Beziehung hinzu comments: Comment[]; }
Aktualisieren Sie src/posts/post.entity.ts
:
// src/posts/post.entity.ts import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, OneToMany } from 'typeorm'; import { Comment } from '../comments/comment.entity'; // Importieren Sie Comment @Entity() export class Post { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; @Column('text') content: string; @CreateDateColumn() createdAt: Date; @OneToMany(() => Comment, (comment) => comment.post) // Fügen Sie die Beziehung hinzu comments: Comment[]; }
Schritt Zwei: Implementieren des Comment-Services
Schreiben wir nun den CommentsService
, der für die Logik der Erstellung und Abfrage von Kommentaren zuständig ist.
1. Registrieren der Comment-Entität
Öffnen Sie src/comments/comments.module.ts
, registrieren Sie TypeOrmModule
und exportieren Sie den CommentsService
, damit andere Module ihn verwenden können.
// src/comments/comments.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CommentsService } from './comments.service'; import { Comment } from './comment.entity'; @Module({ imports: [TypeOrmModule.forFeature([Comment])], providers: [CommentsService], exports: [CommentsService], // Exportieren Sie den Service }) export class CommentsModule {}
2. Schreiben der Service-Logik
Ändern Sie die Datei src/comments/comments.service.ts
, um Methoden zum Erstellen und Abfragen von Kommentaren hinzuzufügen.
// src/comments/comments.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Comment } from './comment.entity'; import { Post } from '../posts/post.entity'; import { User } from '../users/user.entity'; @Injectable() export class CommentsService { constructor( @InjectRepository(Comment) private commentsRepository: Repository<Comment> ) {} // Finden Sie alle Kommentare zu einem Beitrag anhand seiner ID, einschließlich Benutzerinformationen findByPostId(postId: string): Promise<Comment[]> { return this.commentsRepository.find({ where: { post: { id: postId } }, relations: ['user'], // Wichtig: Laden Sie auch das zugehörige Benutzerobjekt order: { createdAt: 'ASC', // Sortieren nach Erstellungszeit aufsteigend }, }); } // Kommentar erstellen async create(content: string, user: User, post: Post): Promise<Comment> { const newComment = this.commentsRepository.create({ content, user, post, }); return this.commentsRepository.save(newComment); } }
relations: ['user']
ist ein mächtiges Feature von TypeORM. Es teilt TypeORM mit, das zugehörigeuser
-Objekt automatisch über den Fremdschlüssel abzurufen und zu füllen, wenn nach Kommentaren gesucht wird. Dies erleichtert es uns, den Benutzernamen des Autors des Kommentars zu erhalten.
Schritt Drei: Kommentare einreichen und anzeigen
Nun müssen wir die Kommentarfunktionalität in die Beitragsseite integrieren. Dazu gehört die Erstellung eines Controllers zur Verarbeitung von Kommentareinreichungen und die Aktualisierung des PostsController
, um Kommentare auf der Beitragsseite anzuzeigen.
1. Erstellen des CommentsController
Obwohl wir die Kommentareinreichungen im PostsController
abwickeln könnten, erstellen wir zur Wahrung der Trennung der Zuständigkeiten einen eigenen Controller dafür.
nest generate controller comments
Ändern Sie src/comments/comments.controller.ts
:
// src/comments/comments.controller.ts import { Controller, Post, Body, Param, Req, Res, UseGuards } from '@nestjs/common'; import { CommentsService } from './comments.service'; import { AuthenticatedGuard } from '../auth/authenticated.guard'; import { Response, Request } from 'express'; @Controller('posts/:postId/comments') export class CommentsController { constructor(private readonly commentsService: CommentsService) {} @UseGuards(AuthenticatedGuard) // Stellen Sie sicher, dass nur angemeldete Benutzer kommentieren können @Post() async create( @Param('postId') postId: string, @Body('content') content: string, @Req() req: Request, @Res() res: Response ) { // req.user wird vom Passport-Session hinzugefügt const user = req.user as any; // Hinweis: In einer realen Anwendung sollten Sie prüfen, ob die postId existiert await this.commentsService.create(content, user, { id: postId } as any); res.redirect(`/posts/${postId}`); // Nach dem Kommentieren zur Beitragsseite zurückleiten } }
Vergessen Sie nicht, diesen neuen Controller auch in src/comments/comments.module.ts
zu registrieren.
// src/comments/comments.module.ts // ... import { CommentsController } from './comments.controller'; @Module({ imports: [TypeOrmModule.forFeature([Comment])], controllers: [CommentsController], // Fügen Sie den Controller hinzu providers: [CommentsService], exports: [CommentsService], }) export class CommentsModule {}
2. Aktualisieren des PostsController
Wir müssen die findOne
-Methode des PostsController
ändern, damit sie beim Rendern der Beitragsdetailseite auch alle Kommentare für diesen Beitrag abruft und übergibt.
Importieren Sie zunächst CommentsModule
in src/posts/posts.module.ts
, damit PostsModule
den CommentsService
verwenden kann.
// src/posts/posts.module.ts // ... import { CommentsModule } from '../comments/comments.module'; @Module({ imports: [TypeOrmModule.forFeature([Post]), CommentsModule], // Importieren Sie CommentsModule controllers: [PostsController], providers: [PostsService], }) export class PostsModule {}
Aktualisieren Sie dann src/posts/posts.controller.ts
:
// src/posts/posts.controller.ts import { Controller, Get, Render, Param, Request /* ... */ } from '@nestjs/common'; import { PostsService } from './posts.service'; import { CommentsService } from '../comments/comments.service'; // Importieren Sie CommentsService // ... @Controller('posts') export class PostsController { constructor( private readonly postsService: PostsService, private readonly commentsService: CommentsService // Injizieren Sie CommentsService ) {} // ... andere Methoden bleiben unverändert @Get(':id') @Render('post') async findOne(@Param('id') id: string, @Request() req) { const post = await this.postsService.findOne(id); const comments = await this.commentsService.findByPostId(id); // Kommentare abrufen return { post, user: req.user, comments }; // Übergeben Sie Kommentare an die Vorlage } }
Schritt Vier: Aktualisieren der Frontend-Ansicht
Der letzte Schritt ist die Änderung der EJS-Vorlage, um die Kommentarliste und das Kommentarformular anzuzeigen.
Öffnen Sie views/post.ejs
und fügen Sie den folgenden Code unter dem Beitragstext hinzu:
<a href="/" class="back-link">← Zurück zur Startseite</a> <section class="comments-section"> <h3>Kommentare</h3> <div class="comment-list"> <% if (comments.length > 0) { %> <% comments.forEach(comment => { %> <div class="comment-item"> <p class="comment-content"><%= comment.content %></p> <small> Von <strong><%= comment.user.username %></strong> am <%= new Date(comment.createdAt).toLocaleDateString() %> </small> </div> <% }) %> <% } else { %> <p>Noch keine Kommentare. Seien Sie der Erste, der einen Kommentar hinterlässt!</p> <% } %> </div> <% if (user) { %> <form action="/posts/<%= post.id %>/comments" method="POST" class="comment-form"> <h4>Kommentar hinterlassen</h4> <div class="form-group"> <textarea name="content" rows="4" placeholder="Schreiben Sie hier Ihren Kommentar..." required ></textarea> </div> <button type="submit">Kommentar abschicken</button> </form> <% } else { %> <p><a href="/auth/login">Anmelden</a>, um einen Kommentar zu hinterlassen.</p> <% } %> </section> <%- include('_footer') %>
Damit die Seite besser aussieht, können Sie einige Stile zu public/css/style.css
hinzufügen:
/* ... andere Stile ... */ .comments-section { margin-top: 3rem; border-top: 1px solid #eee; padding-top: 2rem; } .comment-list .comment-item { background: #f9f9f9; border: 1px solid #ddd; padding: 1rem; border-radius: 5px; margin-bottom: 1rem; } .comment-content { margin-top: 0; } .comment-item small { color: #666; } .comment-form { margin-top: 2rem; } .comment-form textarea { width: 100%; padding: 0.5rem; margin-bottom: 1rem; }
Ausführen und Testen
Starten Sie Ihre Anwendung neu:
npm run start:dev
Besuchen Sie nun Ihren Browser:
- Rufen Sie einen beliebigen Beitrag als Gast auf. Sie sehen den Kommentarbereich, aber nur einen Link "Zum Kommentieren anmelden".
- Melden Sie sich mit Ihrem Konto an.
- Besuchen Sie denselben Beitrag erneut. Sie sehen nun ein Kommentarfeld, in das Sie Text eingeben können.
- Senden Sie einen Kommentar. Nachdem die Seite aktualisiert wurde, sehen Sie den gerade geposteten Inhalt in der Kommentarliste.
Glückwunsch! Sie haben erfolgreich ein voll funktionsfähiges Kommentarsystem zu Ihrem Nest.js-Blog hinzugefügt. Wir haben das Datenmodell für Kommentare erstellt, den Backend-Service und die Routing-Logik implementiert und die Frontend-Ansicht aktualisiert, um Kommentare und ein Einreichungsformular anzuzeigen, während wir einen Guard verwendet haben, um sicherzustellen, dass nur angemeldete Benutzer kommentieren können.
Natürlich ist die aktuelle Kommentarfunktion noch recht einfach. Im nächsten Artikel werden wir diese Funktion weiter verbessern, indem wir eine Logik implementieren, damit Autoren auf Kommentare antworten können, und so die Interaktivität des Blogs auf die nächste Stufe heben.