Leer hoe u een real-time chat-API kunt bouwen door gebruik te maken van de kracht van WebSockets met behulp van NestJS.

NestJS is een populair framework voor het bouwen van server-side applicaties met Node.js. Met zijn ondersteuning voor WebSockets is NestJS zeer geschikt voor het ontwikkelen van real-time chat-applicaties.

Dus, wat zijn WebSockets en hoe kun je een real-time chat-app bouwen in NestJS?

Wat zijn websockets?

WebSockets zijn een protocol voor permanente, realtime en tweerichtingscommunicatie tussen een client en een server.

In tegenstelling tot HTTP, waar een verbinding wordt gesloten wanneer een aanvraagcyclus tussen de client en de server is voltooid, een WebSocket-verbinding wordt opengehouden en wordt niet afgesloten, zelfs niet nadat een antwoord is geretourneerd voor een verzoek.

De onderstaande afbeelding is een visualisatie van hoe een WebSocket-communicatie tussen een server en client werkt:

Om bidirectionele communicatie tot stand te brengen, stuurt de client een WebSocket-handshake-verzoek naar de server. De aanvraagheaders bevatten een beveiligde WebSocket-sleutel (

instagram viewer
Sec-WebSocket-sleutel), en een Upgraden: WebSocket header die samen met de Verbinding: upgraden header vertelt de server om het protocol te upgraden van HTTP naar WebSocket en de verbinding open te houden. Leren over WebSockets in JavaScript helpt om het concept nog beter te begrijpen.

Een real-time chat-API bouwen met behulp van de NestJS WebSocket-module

Node.js biedt twee belangrijke implementaties van WebSockets. De eerste is ws die kale WebSockets implementeert. En de tweede is socket.io, die meer functies op hoog niveau biedt.

NestJS heeft modules voor beide socket.io En ws. Dit artikel maakt gebruik van de socket.io module voor de WebSocket-functies van de voorbeeldtoepassing.

De code die in dit project wordt gebruikt, is beschikbaar in een GitHub-opslagplaats. Het wordt aanbevolen om het lokaal te klonen om de directorystructuur beter te begrijpen en te zien hoe alle codes met elkaar omgaan.

Projectopzet en installatie

Open uw terminal en genereer een nieuwe NestJS-app met behulp van de nest nieuw opdracht (bijv. nest nieuwe chat-app). De opdracht genereert een nieuwe map die de projectbestanden bevat. Nu bent u klaar om het ontwikkelingsproces te starten.

Stel een MongoDB-verbinding in

Om de chatberichten in de applicatie te bewaren, heb je een database nodig. Dit artikel gebruikt de MongoDB-database voor onze NestJS-applicatie, en de gemakkelijkste manier om aan de slag te gaan is door een MongoDB-cluster opzetten in de cloud en ontvang uw MongoDB-URL. Kopieer de URL en sla deze op als het MONGO_URI variabel in je .env bestand.

U zou Mongoose later ook nodig hebben wanneer u vragen stelt aan MongoDB. Installeer het door het uit te voeren npm installeer mangoest in uw eindstation.

In de src map, maak een bestand met de naam mongo.config.ts en plak de volgende code erin.

importeren { registreer als } van'@nestjs/config';

/**
* Mongo-databaseverbindingsconfiguratie
*/

exporterenstandaard registreren als('mongodb', () => {
const { MONGO_URI } = proces.env; // uit .env-bestand
opbrengst {
uri:`${MONGO_URI}`,
};
});

Die van je project hoofd.ts bestand zou er zo uit moeten zien:

importeren { NestFactory } van'@nestjs/kern';
importeren { AppModule } van'./app.module';
importeren * als cookieParser van'cookie-parser'
importeren helm van'helm'
importeren { Logger, ValidationPipe } van'@nestjs/common';
importeren { setupSwagger } van'./utils/swagger';
importeren { HttpExceptionFilter } van'./filters/http-exception.filter';

asynchroonfunctiebootstrap() {
const app= wachten NestFactory.create (AppModule, { cors: WAAR });
app.enableCors({
oorsprong: '*',
referenties: WAAR
})
app.gebruik (cookieParser())
app.gebruikGlobalPipes(
nieuw ValidationPipe({
witte lijst: WAAR
})
)
const logger = nieuw Logboek('Voornaamst')

app.setGlobalPrefix('api/v1')
app.gebruikGlobalFilters(nieuw HttpExceptionFilter());

instellenSwagger (app)
app.gebruik (helm())

wachten app.listen (AppModule.port)

// logboekdocumenten
const baseUrl = AppModule.getBaseUrl (app)
const URL = `http://${baseUrl}:${AppModule.poort}`
logger.log(`API-documentatie beschikbaar op ${url}/docs`);
}
bootstrap();

De chatmodule bouwen

Om aan de slag te gaan met de real-time chatfunctie, is de eerste stap het installeren van de NestJS WebSockets-pakketten. Dit kan gedaan worden door het volgende commando in de terminal uit te voeren.

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

Nadat u de pakketten hebt geïnstalleerd, moet u de chatmodule genereren door de volgende opdrachten uit te voeren

nest g module-chats
Nest G-controllerchats
nest g-servicechats

Als u klaar bent met het genereren van de module, is de volgende stap het maken van een WebSockets-verbinding in NestJS. Maak een chat.gateway.ts bestand in de chatten map, hier is de gateway geïmplementeerd die berichten verzendt en ontvangt.

Plak de volgende code in chat.gateway.ts.

importeren {
Bericht lichaam,
AanmeldenBericht,
WebSocketGateway,
WebSocketServer,
} van'@nestjs/websockets';
importeren {Server} van'socket.io';

@WebSocketGateway()
exporterenklasChatGateway{
@WebSocketServer()
server: server;
// luister naar send_message-gebeurtenissen
@SubscribeMessage('bericht versturen')
listenForMessages(@MessageBody() bericht: string) {
dit.server.sockets.emit('ontvang_bericht', bericht);
}
}

Authenticatie van verbonden gebruikers

Authenticatie is een essentieel onderdeel van webapplicaties en dat is bij een chatapplicatie niet anders. De functie om clientverbindingen met de socket te verifiëren, is te vinden in chats.service.ts zoals hier getoond:

@Injecteerbaar()
exporterenklasChatservice{
constructeur(private authService: AuthService) {}

asynchroon getUserFromSocket (socket: Socket) {
laten auth_token = socket.handshake.headers.autorisatie;
// haal het token zelf op zonder "Bearer"
auth_token = auth_token.split(' ')[1];

const gebruiker = dit.authService.getUserFromAuthenticationToken(
auth_token
);

als (!gebruiker) {
gooiennieuw WsException('Ongeldige inloggegevens.');
}
opbrengst gebruiker;
}
}

De getUserFromSocket methode gebruikt getUserFromAuthenticationToken om de momenteel ingelogde gebruiker uit het JWT-token te halen door het Bearer-token te extraheren. De getUserFromAuthenticationToken functie is geïmplementeerd in de auth.service.ts bestand zoals hier getoond:

openbaar asynchroon getUserFromAuthenticationToken (token: string) {
const lading: JwtPayload = dit.jwtService.verify (token, {
geheim: dit.configService.get('JWT_ACCESS_TOKEN_SECRET'),
});

const userId = payload.sub

als (gebruikersnaam) {
opbrengstdit.usersService.findById (gebruikers-ID);
}
}

De huidige socket wordt als parameter doorgegeven aan getUserFromSocket wanneer de omgaanVerbinding methode van ChatGateway voert de OpGatewayConnection koppel. Dit maakt het mogelijk om berichten en informatie over de momenteel verbonden gebruiker te ontvangen.

Onderstaande code demonstreert dit:

// chat.gateway.ts
@WebSocketGateway()
exporterenklasChatGatewayimplementeertOpGatewayConnection{
@WebSocketServer()
server: server;

constructeur(private chatsService: ChatsService) {}

asynchroon handleConnection (socket: Socket) {
wachtendit.chatsService.getUserFromSocket (socket)
}

@SubscribeMessage('bericht versturen')
asynchroon listenForMessages(@MessageBody() bericht: string, @ConnectedSocket() socket: Socket) {

const gebruiker = wachtendit.chatsService.getUserFromSocket (socket)
dit.server.sockets.emit('ontvang_bericht', {
bericht,
gebruiker
});
}
}

U kunt verwijzen naar de bestanden die betrokken zijn bij het authenticatiesysteem hierboven in het GitHub-opslagplaats om de volledige codes te zien (inclusief invoer), voor een beter begrip van de implementatie.

Aanhoudende chats naar database

Om ervoor te zorgen dat gebruikers hun berichtengeschiedenis kunnen zien, hebt u een schema nodig om berichten op te slaan. Maak een nieuw bestand met de naam bericht.schema.ts en plak de onderstaande code erin (vergeet niet uw gebruikersschema of bekijk de repository voor een).

importeren { Gebruiker } van'./../users/schemas/user.schema';
importeren { Prop, Schema, SchemaFactory } van"@nestjs/mangoest";
importeren mangoest, { Document } van"mangoest";

exporteren type BerichtDocument = Bericht & Document;

@Schema({
naarJSON: {
doorzetters: WAAR,
virtueel: WAAR,
},
tijdstempels: WAAR,
})
exporterenklasBericht{
@Prop({ vereist: WAAR, uniek: WAAR })
bericht: tekenreeks

@Prop({ type: mangoest. Schema. Soorten. Object-ID, ref: 'Gebruiker' })
gebruiker: Gebruiker
}

const MessageSchema = SchemaFactory.createForClass (bericht)

exporteren { Berichtenschema };

Hieronder vindt u een implementatie van services om een ​​nieuw bericht te maken en alle berichten binnen te krijgen chats.service.ts.

importeren { Bericht, BerichtDocument } van'./bericht.schema'; 
importeren { Stopcontact } van'socket.io';
importeren { AuthentiekService } van'./../auth/auth.service';
importeren { Injecteerbaar } van'@nestjs/common';
importeren { WsException } van'@nestjs/websockets';
importeren { Injecteer Model } van'@nestjs/mangoest';
importeren { Model } van'mangoest';
importeren { BerichtDnaar } van'./dto/bericht.dto';

@Injecteerbaar()
exporterenklasChatservice{
constructeur(private authService: AuthService, @InjectModel (Message.name) privéberichtModel: Model) {}
...
asynchroon createMessage (bericht: MessageDto, gebruikersnaam: snaar) {
const nieuwBericht = nieuwdit.messageModel({...bericht, gebruikers-ID})
wachten newMessage.opslaan
opbrengst nieuw bericht
}
asynchroon haalAlleBerichten() {
opbrengstdit.messageModel.find().populate('gebruiker')
}
}

De BerichtDto wordt uitgevoerd in een bericht.dto.ts bestand in de idem map in de chatten map. Je vindt het ook in de repository.

Je moet de toevoegen bericht model en schema toe aan de lijst met imports in chats.module.ts.

importeren { Bericht, BerichtSchema } van'./bericht.schema';
importeren { Module } van'@nestjs/common';
importeren { ChatGateway } van'./chats.gateway';
importeren { Chatservice } van'./chats.service';
importeren { Mongoose-module } van'@nestjs/mangoest';

@Module({
invoer: [MongooseModule.forFeature([
{ naam: Bericht.naam, schema: BerichtSchema }
])],
regelaars: [],
providers: [ChatsService, ChatGateway]
})
exporterenklasChatsModule{}

eindelijk, de get_all_messages gebeurtenissenhandler is toegevoegd aan de ChatGateway klas binnen chat.gateway.ts zoals te zien in de volgende code:

// invoer...

@WebSocketGateway()
exporterenklasChatGatewayimplementeertOpGatewayConnection{
...

@SubscribeMessage('krijg_alle_berichten')
asynchroon getAllMessages(@ConnectedSocket() socket: Socket) {

wachtendit.chatsService.getUserFromSocket (socket)
const berichten = wachtendit.chatsService.getAllMessages()

dit.server.sockets.emit('ontvang_bericht', berichten);

opbrengst berichten
}
}

Wanneer een aangesloten client (gebruiker) het get_all_messages evenement, worden al hun berichten opgehaald en wanneer ze uitzenden bericht versturen, er wordt een bericht gemaakt en opgeslagen in de database, en vervolgens verzonden naar alle andere aangesloten clients.

Als u klaar bent met alle bovenstaande stappen, kunt u uw aanvraag starten met npm run start: dev, en test het met een WebSocket-client zoals Postman.

Realtime applicaties bouwen met NestJS

Hoewel er andere technologieën zijn voor het bouwen van real-time systemen, zijn WebSockets erg populair en in veel gevallen eenvoudig te implementeren, en ze zijn de beste optie voor chattoepassingen.

Realtime toepassingen zijn niet alleen beperkt tot chattoepassingen, andere voorbeelden zijn videostreaming of aanroepende applicaties en live weer-applicaties, en NestJS biedt geweldige tooling voor het bouwen in realtime apps.