Dowiedz się, jak zbudować API czatu w czasie rzeczywistym, wykorzystując moc WebSockets za pomocą NestJS.

NestJS to popularny framework do budowania aplikacji po stronie serwera za pomocą Node.js. Dzięki obsłudze WebSockets, NestJS doskonale nadaje się do tworzenia aplikacji czatu w czasie rzeczywistym.

Czym więc są WebSockets i jak zbudować aplikację czatu w czasie rzeczywistym w NestJS?

Czym są WebSockety?

WebSockets to protokół do trwałej, dwukierunkowej komunikacji w czasie rzeczywistym między klientem a serwerem.

W przeciwieństwie do protokołu HTTP, w którym połączenie jest zamykane po zakończeniu cyklu żądania między klientem a serwerem, połączenie WebSocket jest otwarte i nie zamyka się nawet po zwróceniu odpowiedzi na a wniosek.

Poniższy obraz jest wizualizacją tego, jak działa komunikacja WebSocket między serwerem a klientem:

Aby nawiązać komunikację dwukierunkową, klient wysyła żądanie uzgadniania protokołu WebSocket do serwera. Nagłówki żądań zawierają bezpieczny klucz WebSocket (

Sec-WebSocket-Key) i an Aktualizacja: WebSocket nagłówek, który wraz z Połączenie: aktualizacja nagłówek mówi serwerowi, aby uaktualnił protokół z HTTP do WebSocket i utrzymywał otwarte połączenie. Uczyć się o WebSockets w JavaScript pomaga jeszcze lepiej zrozumieć koncepcję.

Budowanie interfejsu API czatu w czasie rzeczywistym przy użyciu modułu NestJS WebSocket

Node.js udostępnia dwie główne implementacje WebSockets. Pierwsza to ws który implementuje nagie WebSockets. A drugi jest gniazdo.io, który zapewnia więcej funkcji wysokiego poziomu.

NestJS ma moduły dla obu gniazdo.io I ws. W tym artykule użyto gniazdo.io moduł dla funkcji WebSocket przykładowej aplikacji.

Kod użyty w tym projekcie jest dostępny w formacie Repozytorium GitHub. Zaleca się sklonowanie go lokalnie, aby lepiej zrozumieć strukturę katalogów i zobaczyć, jak wszystkie kody współdziałają ze sobą.

Konfiguracja i instalacja projektu

Otwórz swój terminal i wygeneruj nową aplikację NestJS za pomocą gniazdo nowe polecenie (np. zagnieżdż nową aplikację do czatu). Polecenie generuje nowy katalog zawierający pliki projektu. Teraz możesz rozpocząć proces programowania.

Skonfiguruj połączenie MongoDB

Aby zachować wiadomości czatu w aplikacji, potrzebujesz bazy danych. Ten artykuł używa baza danych MongoDB dla naszej aplikacji NestJS, a najłatwiejszym sposobem na uruchomienie jest bieganie skonfigurować klaster MongoDB w chmurze i uzyskaj adres URL MongoDB. Skopiuj adres URL i zapisz go jako MONGO_URI zmienna w twoim .env plik.

Będziesz także potrzebować Mongoose później, gdy będziesz zadawać zapytania do MongoDB. Zainstaluj go, uruchamiając npm zainstaluj mangustę w twoim terminalu.

w źródło folder, utwórz plik o nazwie mongo.config.ts i wklej do niego następujący kod.

import { zarejestruj jako } z'@nestjs/config';

/**
* Konfiguracja połączenia z bazą danych Mongo
*/

eksportdomyślny zarejestruj jako ('mongodb', () => {
konst { MONGO_URI } = proces.env; // z pliku .env
powrót {
adres:`${MONGO_URI}`,
};
});

Twój projekt główne.ts plik powinien wyglądać tak:

import {NestFactory} z„@nestjs/rdzeń”;
import { Moduł aplikacji } z„./moduł aplikacji”;
import * Jak cookieParser z„parser plików cookie”
import kask z'kask'
import { Logger, ValidationPipe } z'@nestjs/wspólny';
import { konfiguracjaSwagger} z'./narzędzia/swagger';
import {HttpExceptionFilter} z„./filters/http-exception.filter”;

asynchronicznyfunkcjonowaćbootstrap() {
konst aplikacja = czekać na NestFactory.create (Moduł aplikacji, { kors: PRAWDA });
app.enableCors({
pochodzenie: '*',
referencje: PRAWDA
})
app.use (cookieParser())
app.useGlobalPipes(
nowy WalidacjaPipe({
biała lista: PRAWDA
})
)
konst rejestrator = nowy Rejestrator ('Główny')

app.setGlobalPrefix('api/v1')
app.useGlobalFilters(nowy HttpExceptionFilter());

setupSwagger (aplikacja)
app.use (hełm())

czekać na app.listen (AppModule.port)

// dokumenty dziennika
konst baseUrl = AppModule.getBaseUrl (aplikacja)
konst adres URL = `http://${baseUrl}:${AppModule.port}`
logger.log(Dokumentacja API dostępna pod adresem ${url}/docs`);
}
bootstrap();

Budowanie modułu czatu

Aby rozpocząć korzystanie z funkcji czatu w czasie rzeczywistym, pierwszym krokiem jest instalacja pakietów NestJS WebSockets. Można to zrobić, uruchamiając następujące polecenie w terminalu.

npm zainstaluj @nestjs/websockets @nestjs/platform-socket.io @types/socket.io

Po zainstalowaniu pakietów musisz wygenerować moduł czatów, uruchamiając następujące polecenia

zagnieżdżaj czaty z modułem g
zagnieżdżaj czaty kontrolera g
zagnieżdżaj czaty serwisowe g

Po zakończeniu generowania modułu następnym krokiem jest utworzenie połączenia WebSockets w NestJS. Stwórz chat.gateway.ts plik wewnątrz czaty folder, w którym zaimplementowana jest brama wysyłająca i odbierająca wiadomości.

Wklej następujący kod do chat.gateway.ts.

import {
WiadomośćCiało,
SubskrybujWiadomość,
WebSocketBrama,
Serwer WebSocket,
} z'@nestjs/websockets';
import { Serwer } z„gniazdo.io”;

@WebSocketGateway()
eksportklasaChatGateway{
@WebSocketServer()
serwer: serwer;
// nasłuchiwanie zdarzeń send_message
@SubskrybujWiadomość('Wyślij wiadomość')
ListenForMessages(@MessageBody() wiadomość: string) {
Ten.server.sockets.emit(„odbierz_wiadomość”, wiadomość);
}
}

Uwierzytelnianie podłączonych użytkowników

Uwierzytelnianie jest istotną częścią aplikacji internetowych i nie różni się niczym w przypadku aplikacji do czatu. Funkcja uwierzytelniania połączeń klienta z gniazdem znajduje się w czaty.usługa.ts jak pokazano tutaj:

@Do wstrzykiwania()
eksportklasaCzatyUsługa{
konstruktor(prywatna usługa uwierzytelniania: usługa uwierzytelniania) {}

asynchroniczny getUserFromSocket (gniazdo: gniazdo) {
pozwalać auth_token = socket.handshake.headers.authorization;
// pobierz sam token bez "Bearer"
auth_token = auth_token.split(' ')[1];

konst użytkownik = Ten.authService.getUserFromAuthenticationToken(
token_autoryzacji
);

Jeśli (!użytkownik) {
rzucićnowy WsException(„Nieprawidłowe poświadczenia”.);
}
powrót użytkownik;
}
}

The getUserFromSocket używa metody getUserFromAuthenticationToken aby uzyskać aktualnie zalogowanego użytkownika z tokena JWT, wyodrębniając token okaziciela. The getUserFromAuthenticationToken funkcja jest zaimplementowana w autoryzacja.ts plik, jak pokazano tutaj:

publiczny asynchroniczny getUserFromAuthenticationToken (token: łańcuch) {
konst ładunek: JwtPayload = Ten.jwtService.verify (token, {
sekret: Ten.configService.get('JWT_ACCESS_TOKEN_SECRET'),
});

konst userId = payload.sub

Jeśli (identyfikator użytkownika) {
powrótTen.usersService.findById (identyfikator użytkownika);
}
}

Bieżące gniazdo jest przekazywane jako parametr do getUserFromSocket kiedy uchwytPołączenie metoda ChatGateway realizuje Połączenie OnGateway interfejs. Dzięki temu możliwe jest otrzymywanie wiadomości i informacji o aktualnie podłączonym użytkowniku.

Pokazuje to poniższy kod:

// chat.gateway.ts
@WebSocketGateway()
eksportklasaChatGatewayprzyboryPołączenie OnGateway{
@WebSocketServer()
serwer: serwer;

konstruktor(prywatna usługa czatów: usługa czatów) {}

asynchroniczny handleConnection (gniazdo: gniazdo) {
czekać naTen.chatsService.getUserFromSocket (gniazdo)
}

@SubskrybujWiadomość('Wyślij wiadomość')
asynchroniczny ListenForMessages(@MessageBody() wiadomość: string, @ConnectedSocket() gniazdo: Socket) {

konst użytkownik = czekać naTen.chatsService.getUserFromSocket (gniazdo)
Ten.server.sockets.emit(„odbierz_wiadomość”, {
wiadomość,
użytkownik
});
}
}

Możesz odwoływać się do plików związanych z powyższym systemem uwierzytelniania w pliku Repozytorium GitHub aby zobaczyć pełne kody (w tym importy), dla lepszego zrozumienia implementacji.

Utrzymywanie czatów w bazie danych

Aby użytkownicy mogli zobaczyć swoją historię wiadomości, potrzebujesz schematu do przechowywania wiadomości. Utwórz nowy plik o nazwie wiadomość.schemat.ts i wklej do niego poniższy kod (pamiętaj, aby zaimportować plik schemat użytkownika lub sprawdź repozytorium dla jednego).

import { Użytkownik } z„./../użytkownicy/schematy/schemat użytkownika”;
import { Rekwizyt, Schemat, Fabryka Schematów} z"@nestjs/mangusta";
import mangusta, { Dokument } z"mangusta";

eksport wpisz WiadomośćDocument = Wiadomość i dokument;

@Schemat({
doJSON: {
pobierające: PRAWDA,
wirtualne: PRAWDA,
},
znaczniki czasu: PRAWDA,
})
eksportklasaWiadomość{
@Rekwizyt({ wymagany: PRAWDA, unikalny: PRAWDA })
wiadomość: ciąg

@Rekwizyt({ typ: mangusta. Schemat. typy. Identyfikator obiektu, ref: 'Użytkownik' })
użytkownik: Użytkownik
}

konst MessageSchema = SchemaFactory.createForClass (wiadomość)

eksport { Schemat wiadomości };

Poniżej znajduje się implementacja usług tworzenia nowej wiadomości i pobierania wszystkich wiadomości czaty.usługa.ts.

import { Wiadomość, dokument wiadomości } z„./schemat.wiadomości”; 
import { Gniazdo elektryczne } z„gniazdo.io”;
import { Usługa autoryzacji } z„./../uwierzytelnianie/usługa.uwierzytelniania”;
import { Do wstrzykiwań } z'@nestjs/wspólny';
import { WsException } z'@nestjs/websockets';
import {Model wstrzykiwania} z'@nestjs/mangusta';
import { Model } z'mangusta';
import { WiadomośćDdo} z'./dto/wiadomość.dto';

@Do wstrzykiwania()
eksportklasaCzatyUsługa{
konstruktor(private authService: AuthService, @InjectModel (Message.name) prywatna wiadomośćModel: Model) {}
...
asynchroniczny createMessage (wiadomość: MessageDto, identyfikator użytkownika: strunowy) {
konst nowawiadomość = nowyTen.messageModel({...wiadomość, userId})
czekać na nowaWiadomość.zapisz
powrót Nowa wiadomość
}
asynchroniczny getAllMessages() {
powrótTen.messageModel.find().populate('użytkownik')
}
}

The WiadomośćDdo jest realizowany w a wiadomość.dto.ts plik w dto folder w czaty informator. Możesz go również znaleźć w repozytorium.

Musisz dodać tzw wiadomość model i schemat do listy importów w czaty.moduł.ts.

import { Wiadomość, schemat wiadomości } z„./schemat.wiadomości”;
import { Moduł } z'@nestjs/wspólny';
import { ChatGateway } z„./czaty.brama”;
import { Usługa czatów } z„./czaty.usługa”;
import {Moduł Mongoose} z'@nestjs/mangusta';

@Moduł({
importuje: [MongooseModule.forFeature([
{ nazwa: Wiadomość.nazwa, schemat: Schemat wiadomości }
])],
kontrolery: [],
dostawcy: [ChatsService, ChatGateway]
})
eksportklasaCzatyModuł{}

Wreszcie, get_all_messages dodano obsługę zdarzeń ChatGateway klasa w chat.gateway.ts jak widać w poniższym kodzie:

// importuje...

@WebSocketGateway()
eksportklasaChatGatewayprzyboryPołączenie OnGateway{
...

@SubskrybujWiadomość(„pobierz_wszystkie_wiadomości”)
asynchroniczny getAllMessages(@ConnectedSocket() gniazdo: gniazdo) {

czekać naTen.chatsService.getUserFromSocket (gniazdo)
konst wiadomości = czekać naTen.chatsService.getAllMessages()

Ten.server.sockets.emit(„odbierz_wiadomość”, wiadomości);

powrót wiadomości
}
}

Gdy podłączony klient (użytkownik) emituje plik get_all_messages zdarzenie, wszystkie ich wiadomości zostaną pobrane, a kiedy wyemitują Wyślij wiadomość, wiadomość jest tworzona i zapisywana w bazie danych, a następnie wysyłana do wszystkich innych podłączonych klientów.

Po wykonaniu wszystkich powyższych kroków możesz rozpocząć korzystanie z aplikacji npm uruchom start: devi przetestuj go za pomocą klienta WebSocket, takiego jak Postman.

Tworzenie aplikacji czasu rzeczywistego za pomocą NestJS

Chociaż istnieją inne technologie do budowania systemów czasu rzeczywistego, WebSockets są bardzo popularne i łatwe do wdrożenia w wielu przypadkach i są najlepszą opcją dla aplikacji czatowych.

Aplikacje działające w czasie rzeczywistym nie ograniczają się tylko do aplikacji czatu, inne przykłady obejmują strumieniowanie wideo lub aplikacje wywołujące i aplikacje pogodowe na żywo, a NestJS zapewnia doskonałe narzędzia do budowania w czasie rzeczywistym aplikacje.