Scopri come creare un'API per chat in tempo reale sfruttando la potenza dei WebSocket utilizzando NestJS.

NestJS è un framework popolare per la creazione di applicazioni lato server con Node.js. Con il suo supporto per WebSocket, NestJS è adatto per lo sviluppo di applicazioni di chat in tempo reale.

Quindi, cosa sono i WebSocket e come puoi creare un'app di chat in tempo reale in NestJS?

Cosa sono i WebSocket?

I WebSocket sono un protocollo per la comunicazione persistente, in tempo reale e bidirezionale tra un client e un server.

A differenza di HTTP in cui una connessione viene chiusa quando viene completato un ciclo di richiesta tra il client e il server, una connessione WebSocket viene mantenuta aperta e non si chiude anche dopo che è stata restituita una risposta per a richiesta.

L'immagine seguente è una visualizzazione di come funziona una comunicazione WebSocket tra un server e un client:

Per stabilire una comunicazione bidirezionale, il client invia una richiesta di handshake WebSocket al server. Le intestazioni della richiesta contengono una chiave WebSocket sicura (

instagram viewer
Sec-WebSocket-Chiave), e un Aggiornamento: WebSocket intestazione che insieme al Connessione: aggiornamento header dice al server di aggiornare il protocollo da HTTP a WebSocket e mantenere aperta la connessione. Imparando WebSocket in JavaScript aiuta a capire ancora meglio il concetto.

Creazione di un'API di chat in tempo reale utilizzando il modulo NestJS WebSocket

Node.js fornisce due principali implementazioni di WebSocket. Il primo è ws che implementa WebSocket nudi. E il secondo è presa.io, che fornisce funzionalità più di alto livello.

NestJS ha moduli per entrambi presa.io E ws. Questo articolo utilizza il presa.io modulo per le funzionalità WebSocket dell'applicazione di esempio.

Il codice utilizzato in questo progetto è disponibile in a Deposito GitHub. Si consiglia di clonarlo localmente per comprendere meglio la struttura della directory e vedere come interagiscono tra loro tutti i codici.

Configurazione e installazione del progetto

Apri il tuo terminale e genera una nuova app NestJS utilizzando il file nido nuovo comando (es. nest nuova app di chat). Il comando genera una nuova directory che contiene i file di progetto. Ora sei pronto per iniziare il processo di sviluppo.

Imposta una connessione MongoDB

Per rendere persistenti i messaggi di chat nell'applicazione, è necessario un database. Questo articolo utilizza il database MongoDB per la nostra applicazione NestJS, e il modo più semplice per iniziare a correre è farlo configurare un cluster MongoDB nel cloud e ottieni il tuo URL MongoDB. Copia l'URL e memorizzalo come file MONGO_URI variabile nel tuo .env file.

Avresti anche bisogno di Mongoose in seguito quando effettui query a MongoDB. Installalo eseguendo npm installa mangusta nel tuo terminale.

Nel src cartella, creare un file chiamato mongo.config.ts e incollare il seguente codice al suo interno.

importare { registrati come } da'@nestjs/config';

/**
* Configurazione della connessione al database Mongo
*/

esportarepredefinito registra come('mongobb', () => {
cost { MONGO_URI } = processo.env; // dal file .env
ritorno {
uri:`${MONGO_URI}`,
};
});

Il tuo progetto main.ts il file dovrebbe assomigliare a questo:

importare {Fabbrica Nest} da'@nestjs/core';
importare {AppModule} da'./app.modulo';
importare * COME cookieParser da'analisi dei cookie'
importare casco da'casco'
importare {Logger, ValidationPipe} da'@nestjs/comune';
importare { setupSwagger } da'./utils/spavalderia';
importare { HttpExceptionFilter } da'./filtri/http-exception.filter';

asincronofunzionebootstrap() {
cost app = aspetta NestFactory.create (AppModule, { cor: VERO });
app.enableCors({
origine: '*',
credenziali: VERO
})
app.use (cookieParser())
app.useGlobalPipes(
nuovo ValidationPipe({
lista bianca: VERO
})
)
cost registratore = nuovo Registratore('Principale')

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

configurazioneSwagger (app)
app.use (casco())

aspetta app.listen (AppModule.port)

// registra i documenti
cost baseUrl = AppModule.getBaseUrl (app)
cost URL = `http://${baseUrl}:${AppModule.port}`
logger.log(`Documentazione API disponibile all'indirizzo ${URL}/docs`);
}
bootstrap();

Costruire il modulo di chat

Per iniziare con la funzione di chat in tempo reale, il primo passo è installare i pacchetti NestJS WebSockets. Questo può essere fatto eseguendo il seguente comando nel terminale.

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

Dopo aver installato i pacchetti, è necessario generare il modulo chat eseguendo i seguenti comandi

chat del modulo nest g
chat del controller Nest G
chat di servizio nest g

Una volta terminata la generazione del modulo, il passaggio successivo consiste nel creare una connessione WebSockets in NestJS. Creare un chat.gateway.ts file all'interno del file chat cartella, qui è implementato il gateway che invia e riceve i messaggi.

Incolla il seguente codice in chat.gateway.ts.

importare {
Corpo del messaggio,
IscrivitiMessaggio,
Gateway WebSocket,
Server WebSocket,
} da'@nestjs/websocket';
importare { Server } da'socket.io';

@WebSocketGateway()
esportareclasseChatGateway{
@WebSocketServer()
server: server;
// ascolta gli eventi send_message
@SubscribeMessage('invia messaggio')
listenForMessages(@MessageBody() messaggio: stringa) {
Questo.server.sockets.emit('ricevi_messaggio', Messaggio);
}
}

Autenticazione degli utenti connessi

L'autenticazione è una parte essenziale delle applicazioni Web e non è diversa per un'applicazione di chat. La funzione per autenticare le connessioni client al socket si trova in chat.service.ts come mostrato qui:

@Injectable()
esportareclasseServizio Chat{
costruttore(servizio di autenticazione privato: servizio di autenticazione) {}

asincrono getUserFromSocket (socket: Socket) {
permettere auth_token = socket.handshake.headers.authorization;
// ottieni il token stesso senza "Bearer"
auth_token = auth_token.split(' ')[1];

cost utente = Questo.authService.getUserFromAuthenticationToken(
auth_token
);

Se (!utente) {
gettarenuovo WsException('Credenziali non valide.');
}
ritorno utente;
}
}

IL getUserFromSocket metodo utilizza getUserFromAuthenticationToken per ottenere l'utente attualmente connesso dal token JWT estraendo il token Bearer. IL getUserFromAuthenticationToken funzione è implementata nel auth.service.ts file come mostrato qui:

pubblico asincrono getUserFromAuthenticationToken (token: stringa) {
cost carico utile: JwtPayload = Questo.jwtService.verify (token, {
segreto: Questo.configService.get('JWT_ACCESS_TOKEN_SECRET'),
});

cost userId = payload.sub

Se (ID utente) {
ritornoQuesto.usersService.findById (userId);
}
}

Il socket corrente viene passato come parametro a getUserFromSocket quando il handleConnection metodo di ChatGateway implementa il OnGatewayConnection interfaccia. Ciò consente di ricevere messaggi e informazioni sull'utente attualmente connesso.

Il codice seguente lo dimostra:

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

costruttore(chat privateServizio: ChatsService) {}

asincrono handleConnection (socket: Socket) {
aspettaQuesto.chatsService.getUserFromSocket (socket)
}

@SubscribeMessage('invia messaggio')
asincrono listenForMessages(@MessageBody() messaggio: stringa, @ConnectedSocket() socket: Socket) {

cost utente = aspettaQuesto.chatsService.getUserFromSocket (socket)
Questo.server.sockets.emit('ricevi_messaggio', {
Messaggio,
utente
});
}
}

È possibile fare riferimento ai file coinvolti nel sistema di autenticazione di cui sopra nel file Deposito GitHub per vedere i codici completi (comprese le importazioni), per una migliore comprensione dell'implementazione.

Chat persistenti al database

Affinché gli utenti possano visualizzare la cronologia dei messaggi, è necessario uno schema per archiviare i messaggi. Crea un nuovo file chiamato messaggio.schema.ts e incolla il codice qui sotto (ricorda di importare il tuo file schema utente o controlla il repository per uno).

importare { Utente } da'./../utenti/schemi/utente.schema';
importare { Prop, Schema, SchemaFactory } da"@nestjs/mangusta";
importare mangusta, { Documento } da"mangusta";

esportare type MessageDocument = Messaggio e documento;

@Schema({
aJSON: {
getter: VERO,
virtuali: VERO,
},
timestamp: VERO,
})
esportareclasseMessaggio{
@Puntello({ necessario: VERO, unico: VERO })
messaggio: stringa

@Puntello({ tipo: mangusta. Schema. Tipi. IDoggetto, rif: 'Utente' })
utente: Utente
}

cost MessageSchema = SchemaFactory.createForClass (Messaggio)

esportare {Schema messaggio};

Di seguito è riportata un'implementazione dei servizi per creare un nuovo messaggio e ricevere tutti i messaggi chat.service.ts.

importare {Messaggio, MessaggioDocumento} da'./messaggio.schema'; 
importare { PRESA } da'socket.io';
importare { Servizio di autenticazione } da'./../auth/auth.servizio';
importare { Iniettabile } da'@nestjs/comune';
importare { WsException } da'@nestjs/websocket';
importare {IniettareModello} da'@nestjs/mangusta';
importare { Modello } da'mangusta';
importare { MessaggioDto } da'./dto/messaggio.dto';

@Injectable()
esportareclasseServizio Chat{
costruttore(private authService: AuthService, @InjectModel (Message.name) private messageModel: Model) {}
...
asincrono createMessage (messaggio: MessageDto, ID utente: corda) {
cost nuovoMessaggio = nuovoQuesto.messageModel({...messaggio, userId})
aspetta newMessage.save
ritorno nuovo messaggio
}
asincrono getTuttiMessaggi() {
ritornoQuesto.messageModel.find().populate('utente')
}
}

IL MessaggioDto è implementato in a messaggio.dto.ts file nel dto cartella in chat directory. Puoi anche trovarlo nel repository.

Devi aggiungere il Messaggio modello e schema all'elenco delle importazioni in chats.module.ts.

importare { messaggio, schema messaggio } da'./messaggio.schema';
importare { Modulo } da'@nestjs/comune';
importare {Gateway chat} da'./chat.gateway';
importare {Servizio chat} da'./chat.servizio';
importare {Modulo mangusta} da'@nestjs/mangusta';

@Modulo({
importazioni: [MongooseModule.forFeature([
{ nome: Messaggio.nome, schema: MessageSchema }
])],
controllori: [],
fornitori: [ChatsService, ChatGateway]
})
esportareclasseModulo chat{}

Infine il get_all_messages gestore di eventi viene aggiunto al ChatGateway classe dentro chat.gateway.ts come si vede nel seguente codice:

// importa...

@WebSocketGateway()
esportareclasseChatGatewayimplementaOnGatewayConnection{
...

@SubscribeMessage('ottieni_tutti_i_messaggi')
asincrono getAllMessages(@ConnectedSocket() socket: Socket) {

aspettaQuesto.chatsService.getUserFromSocket (socket)
cost messaggi = aspettaQuesto.chatsService.getAllMessages()

Questo.server.sockets.emit('ricevi_messaggio', messaggi);

ritorno messaggi
}
}

Quando un client connesso (utente) emette il file get_all_messages evento, tutti i loro messaggi verranno recuperati e quando emetteranno invia messaggio, un messaggio viene creato e memorizzato nel database, quindi inviato a tutti gli altri client connessi.

Una volta completati tutti i passaggi precedenti, puoi avviare l'applicazione utilizzando inizio esecuzione npm: deve testalo con un client WebSocket come Postman.

Creazione di applicazioni in tempo reale con NestJS

Sebbene esistano altre tecnologie per la creazione di sistemi in tempo reale, i WebSocket sono molto diffusi e facili da implementare in molti casi e rappresentano l'opzione migliore per le applicazioni di chat.

Le applicazioni in tempo reale non si limitano solo alle applicazioni di chat, altri esempi includono lo streaming video o applicazioni di chiamata e applicazioni meteo in tempo reale e NestJS fornisce ottimi strumenti per la creazione in tempo reale app.