API pour créer une conversation, affichage des utilisateurs actifs, historique des discussions.
Dans cette leçon, tu vas apprendre à :
Prérequis : authentication JWT en place, service
ChatService
avec méthodes
createConversation
etgetConversations
(voir le module précédent).
Pour permettre à un utilisateur de démarrer un chat, on définit un endpoint POST /chat
protégé par le guard JwtAuthGuard
.
1import {2Body,3Controller,4Post,5Request,6UseGuards,7} from '@nestjs/common'8import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'9import { ChatService } from './chat.service'10import { CreateConversationDto } from './dto/create-conversation.dto'1112@Controller('chat')13export class ChatController {14constructor(private readonly chatService: ChatService) {}1516@UseGuards(JwtAuthGuard)17@Post()18async createConversation(19@Body() createConversationDto: CreateConversationDto,20@Request() request: RequestWithUser,21) {22return this.chatService.createConversation({23createConversationDto,24userId: request.user.userId, // ^? ID de l’utilisateur courant25})26}27}
// @callout: Le guard JwtAuthGuard
vérifie que le JWT est valide et
// injecte les infos de l’utilisateur dans request.user
.
Le DTO CreateConversationDto
contient simplement l’ID du destinataire :
1export class CreateConversationDto {2recipientId: string3}
Le service NestJS utilise Prisma pour :
ConversationUser
pour les deux utilisateursTu peux utiliser connectOrCreate
pour éviter les doublons.
Pour obtenir toutes les discussions d’un usager, on expose GET /chat
:
1@UseGuards(JwtAuthGuard)2@Get()3async getConversations(@Request() request: RequestWithUser) {4return this.chatService.getConversations({5userId: request.user.userId, // conversations de cet utilisateur6})7}
Le service remonte chaque conversation avec :
Optimise la requête Prisma en sélectionnant uniquement les champs nécessaires.
Dans Remix, tu vas créer la route app/routes/conversations.tsx
qui contient loader et action.
1import { json } from "@remix-run/node"2import { useLoaderData, Form } from "@remix-run/react"3import type { ActionFunction, LoaderFunction } from "@remix-run/node"45export const loader: LoaderFunction = async ({ request }) => {6const res = await fetch("http://localhost:3000/chat", {7headers: { Authorization: request.headers.get("Cookie")! },8})9return json(await res.json())10}1112export const action: ActionFunction = async ({ request }) => {13const form = await request.formData()14const recipientId = form.get("recipientId")15await fetch("http://localhost:3000/chat", {16method: "POST",17headers: {18"Content-Type": "application/json",19Authorization: request.headers.get("Cookie")!,20},21body: JSON.stringify({ recipientId }),22})23return null24}2526export default function ConversationsRoute() {27const conversations = useLoaderData()28return <Conversations conversations={conversations} />29}
// @callout: Le loader appelle ton API NestJS en passant le cookie JWT.
Conversations
On affiche d’abord les utilisateurs sans discussion, puis l’historique.
1import { Form } from "@remix-run/react"2import { useOptionalUser } from "~/utils/session"34export const Conversations = ({5conversations,6users,7}: {8conversations: any[]9users: { id: string; firstName: string }[]10}) => {11const connectedUser = useOptionalUser()12if (!connectedUser) return null1314// Tous sauf moi15const usersExceptMe = users.filter(u => u.id !== connectedUser.id)16// Ceux sans conversation17const usersWithoutConv = usersExceptMe.filter(18u => !conversations.find(c =>19c.users.some(u2 => u2.id === u.id),20),21)2223return (24<div className="flex flex-col gap-y-4 max-w-md mx-auto">25<span className="font-bold text-sm">Utilisateurs actifs</span>26<div className="flex flex-col gap-2">27{usersWithoutConv.map(user => (28<Form method="post" key={user.id}>29<input30type="hidden"31name="recipientId"32value={user.id}33/>34<button className="btn">35Envoyer un message à {user.firstName}36</button>37</Form>38))}39</div>4041<span className="font-bold mt-6">Historique des conversations</span>42{conversations.length > 0 ? (43conversations.map(conv => (44<ConversationItem45key={conv.id}46conversation={conv}47/>48))49) : (50<div>Aucune conversation n'a été créée.</div>51)}52</div>53)54}
// @callout: On utilise useOptionalUser
(session Remix) pour récupérer
// l’utilisateur connecté.
Si tu as beaucoup d’utilisateurs, charge-les paginés ou via une requête séparée.
CreateConversationDto
ne contient que recipientId
.filter
<Conversations>
, <ConversationItem>
redirect
de @remix-run/node
).unread
dans le loader)./chat/:conversationId
pour supprimer