Aprenda a crear una API de chat en tiempo real aprovechando el poder de WebSockets con NestJS.
NestJS es un marco popular para crear aplicaciones del lado del servidor con Node.js. Con su soporte para WebSockets, NestJS es ideal para desarrollar aplicaciones de chat en tiempo real.
Entonces, ¿qué son los WebSockets y cómo puede crear una aplicación de chat en tiempo real en NestJS?
¿Qué son los WebSockets?
Los WebSockets son un protocolo para la comunicación persistente, en tiempo real y bidireccional entre un cliente y un servidor.
A diferencia de HTTP, donde se cierra una conexión cuando se completa un ciclo de solicitud entre el cliente y el servidor, una conexión WebSocket se mantiene abierta y no se cierra incluso después de que se haya devuelto una respuesta para un pedido.
La siguiente imagen es una visualización de cómo funciona una comunicación WebSocket entre un servidor y un cliente:
Para establecer una comunicación bidireccional, el cliente envía una solicitud de reconocimiento de WebSocket al servidor. Los encabezados de solicitud contienen una clave WebSocket segura (
Sec-WebSocket-Key), y un Actualizar: WebSocket encabezado que junto con el Conexión: Actualizar El encabezado le dice al servidor que actualice el protocolo de HTTP a WebSocket y mantenga la conexión abierta. Aprendiendo acerca de WebSockets en JavaScript ayuda a entender el concepto aún mejor.Creación de una API de chat en tiempo real con el módulo WebSocket de NestJS
Node.js proporciona dos implementaciones principales de WebSockets. El primero es ws que implementa WebSockets desnudos. Y el segundo es socket.io, que proporciona más características de alto nivel.
NestJS tiene módulos para ambos socket.io y ws. Este artículo utiliza el socket.io módulo para las características de WebSocket de la aplicación de muestra.
El código utilizado en este proyecto está disponible en un repositorio GitHub. Se recomienda clonarlo localmente para comprender mejor la estructura del directorio y ver cómo todos los códigos interactúan entre sí.
Configuración e instalación del proyecto
Abra su terminal y genere una nueva aplicación NestJS usando el nido nuevo comando (por ej. anidar nueva aplicación de chat). El comando genera un nuevo directorio que contiene los archivos del proyecto. Ahora está listo para comenzar el proceso de desarrollo.
Configurar una conexión MongoDB
Para conservar los mensajes de chat en la aplicación, necesita una base de datos. Este artículo utiliza la base de datos MongoDB para nuestra aplicación NestJS, y la forma más fácil de empezar a correr es configurar un clúster de MongoDB en la nube y obtenga su URL de MongoDB. Copie la URL y guárdela como el MONGO_URI variable en su .env archivo.
También necesitará Mongoose más adelante cuando realice consultas a MongoDB. Instálelo ejecutando npm instalar mangosta en tu terminal.
En el origen carpeta, cree un archivo llamado mongo.config.ts y pegue el siguiente código en él.
importar { registrarse como } de'@nestjs/config';
/**
* Configuración de conexión de base de datos Mongo
*/
exportarpor defecto registrarse como ('mongodb', () => {
constante { MONGO_URI } = proceso.env; // del archivo .env
devolver {
Uri:`${MONGO_URI}`,
};
});
el de tu proyecto principal.ts El archivo debería verse así:
importar { Fábrica de nidos } de'@nestjs/núcleo';
importar { Módulo de aplicación } de'./aplicación.módulo';
importar * como analizador de cookies de'analizador de cookies'
importar casco de'casco'
importar { Registrador, ValidationPipe } de'@nestjs/común';
importar { configurarSwagger } de'./utils/swagger';
importar { Filtro de excepción Http } de'./filtros/http-exception.filter';asíncronofunciónoreja() {
constante aplicación = esperar NestFactory.create (módulo de aplicación, { corazones: verdadero });
app.enableCors({
origen: '*',
cartas credenciales: verdadero
})
aplicación.uso (cookieParser())
aplicación.useGlobalPipes(
nuevo Canal de validación ({
lista blanca: verdadero
})
)
constante registrador = nuevo registrador ('Principal')aplicación.setGlobalPrefix('api/v1')
app.useGlobalFilters(nuevo HttpExcepciónFiltro());setupSwagger (aplicación)
app.use (casco())esperar app.listen (AppModule.port)
// registrar documentos
constante baseUrl = AppModule.getBaseUrl (aplicación)
constante dirección URL = `http://${baseUrl}:${AppModule.puerto}`
registrador.log(`Documentación API disponible en ${url}/docs`);
}
oreja();
Creación del módulo de chat
Para comenzar con la función de chat en tiempo real, el primer paso es instalar los paquetes NestJS WebSockets. Esto se puede hacer ejecutando el siguiente comando en la terminal.
npm install @nestjs/websockets @nestjs/platform-socket.io @types/socket.io
Después de instalar los paquetes, debe generar el módulo de chats ejecutando los siguientes comandos
chats del módulo nest g
chats del controlador nest g
chats de servicio nest g
Una vez que haya terminado de generar el módulo, el siguiente paso es crear una conexión WebSockets en NestJS. Crear un chat.gateway.ts archivo dentro del charlas carpeta, aquí es donde se implementa la puerta de enlace que envía y recibe mensajes.
Pegue el siguiente código en chat.gateway.ts.
importar {
Cuerpo del mensaje,
Mensaje de suscripción,
puerta de enlace WebSocket,
servidor websocket,
} de'@nestjs/websockets';
importar { Servidor } de'socket.io';
@WebSocketGateway()
exportarclasePasarela de chat{
@WebSocketServer()
servidor: Servidor;
// escucha los eventos send_message
@SuscribirMensaje('enviar mensaje')
listenForMessages(@MessageBody() mensaje: cadena) {
este.servidor.sockets.emit('recibir_mensaje', mensaje);
}
}
Autenticación de usuarios conectados
La autenticación es una parte esencial de las aplicaciones web y no es diferente para una aplicación de chat. La función para autenticar las conexiones del cliente al socket se encuentra en chats.servicio.ts como se muestra aquí:
@Inyectable()
exportarclaseChatsServicio{
constructor(Servicio de autenticación privado: Servicio de autenticación) {}asíncrono getUserFromSocket (socket: Socket) {
dejar auth_token = socket.handshake.headers.autorización;
// obtener el token en sí mismo sin "Bearer"
auth_token = auth_token.split(' ')[1];constante usuario = este.authService.getUserFromAuthenticationToken(
auth_token
);
si (!usuario) {
tirarnuevo WsException('Credenciales no válidas.');
}
devolver usuario;
}
}
El getUserFromSocket usos del método getUserFromAuthenticationToken para obtener el usuario conectado actualmente del token JWT extrayendo el token Bearer. El getUserFromAuthenticationToken La función se implementa en el auth.service.ts archivo como se muestra aquí:
público asíncrono getUserFromAuthenticationToken (token: cadena) {
constante carga útil: JwtPayload = este.jwtService.verify (token, {
secreto: este.configService.get('JWT_ACCESS_TOKEN_SECRET'),
});constante ID de usuario = carga útil.sub
si (ID de usuario) {
devolvereste.usersService.findById (ID de usuario);
}
}
El socket actual se pasa como un parámetro a getUserFromSocket cuando el manijaConexión método de Pasarela de chat implementa el OnGatewayConnection interfaz. Esto hace posible recibir mensajes e información sobre el usuario actualmente conectado.
El siguiente código demuestra esto:
// chat.gateway.ts
@WebSocketGateway()
exportarclasePasarela de chatimplementosOnGatewayConnection{
@WebSocketServer()
servidor: Servidor;constructor(Servicio de chat privado: ChatsService) {}
asíncrono handleConnection (socket: Socket) {
esperareste.chatsService.getUserFromSocket (socket)
}@SuscribirMensaje('enviar mensaje')
asíncrono listenForMessages(@MessageBody() mensaje: cadena, @ConnectedSocket() socket: Socket) {
constante usuario = esperareste.chatsService.getUserFromSocket (socket)
este.servidor.sockets.emit('recibir_mensaje', {
mensaje,
usuario
});
}
}
Puede hacer referencia a los archivos involucrados en el sistema de autenticación anterior en el repositorio GitHub para ver los códigos completos (incluidas las importaciones), para una mejor comprensión de la implementación.
Chats persistentes en la base de datos
Para que los usuarios vean su historial de mensajes, necesita un esquema para almacenar mensajes. Crear un nuevo archivo llamado mensaje.esquema.ts y pegue el código de abajo (recuerde importar su esquema de usuario o echa un vistazo al repositorio de uno).
importar { Usuario } de'./../usuarios/esquemas/usuario.esquema';
importar { Objeto, Esquema, SchemaFactory } de"@nestjs/mangosta";
importar mangosta, { Documento } de"mangosta";exportar escriba MessageDocument = Mensaje y documento;
@Esquema({
aJSON: {
captadores: verdadero,
virtuales: verdadero,
},
marcas de tiempo: verdadero,
})
exportarclaseMensaje{
@Apuntalar({ requerido: verdadero, único: verdadero })
mensaje: cadena@Apuntalar({ tipo: mangosta. Esquema. Tipos. Id. de objeto, árbitro: 'Usuario' })
usuario: Usuario
}constante MessageSchema = SchemaFactory.createForClass (Mensaje)
exportar { Esquema de mensaje };
A continuación se muestra una implementación de servicios para crear un nuevo mensaje y obtener todos los mensajes en chats.servicio.ts.
importar { Mensaje, Documento de mensaje } de'./mensaje.esquema';
importar { Zócalo } de'socket.io';
importar { Servicio de autenticación } de'./../auth/auth.servicio';
importar { Inyectable } de'@nestjs/común';
importar { Excepción Ws } de'@nestjs/websockets';
importar { Inyectar modelo } de'@nestjs/mangosta';
importar { Modelo } de'mangosta';
importar { MensajeDto } de'./dto/mensaje.dto';
@Inyectable()
exportarclaseChatsServicio{
constructor(private authService: AuthService, @InjectModel (Message.name) private messageModel: Model) {}
...
asíncrono createMessage (mensaje: MessageDto, ID de usuario: cadena) {
constante mensaje nuevo = nuevoeste.messageModel({...mensaje, ID de usuario})
esperar nuevoMensaje.guardar
devolver nuevo mensaje
}
asíncrono obtenerTodosLosMensajes() {
devolvereste.modelo de mensaje.buscar().poblar('usuario')
}
}
El MensajeDto se implementa en un mensaje.dto.ts archivo en el dto carpeta en el charlas directorio. También puedes encontrarlo en el repositorio.
Necesitas agregar el mensaje modelo y esquema a la lista de importaciones en chats.módulo.ts.
importar { Mensaje, esquema de mensaje } de'./mensaje.esquema';
importar { Módulo } de'@nestjs/común';
importar { Pasarela de chat } de'./chats.gateway';
importar { Servicio de chats } de'./chats.servicio';
importar { Módulo Mongoose } de'@nestjs/mangosta';
@Módulo({
importaciones: [MongooseModule.forFeature([
{ nombre: Mensaje.nombre, esquema: esquema de mensaje }
])],
controladores: [],
proveedores: [ChatsService, ChatGateway]
})
exportarclaseMódulo Chats{}
Finalmente, el obtener_todos_los_mensajes controlador de eventos se agrega a la Pasarela de chat clase en chat.gateway.ts como se ve en el siguiente código:
// importaciones...
@WebSocketGateway()
exportarclasePasarela de chatimplementosOnGatewayConnection{
...@SuscribirMensaje('obtener_todos_los_mensajes')
asíncrono getAllMessages(@ConnectedSocket() socket: Socket) {esperareste.chatsService.getUserFromSocket (socket)
constante mensajes = esperareste.chatsService.getAllMessages()este.servidor.sockets.emit('recibir_mensaje', mensajes);
devolver mensajes
}
}
Cuando un cliente conectado (usuario) emite el obtener_todos_los_mensajes evento, todos sus mensajes serán recuperados, y cuando emitan enviar mensaje, se crea un mensaje y se almacena en la base de datos, y luego se envía a todos los demás clientes conectados.
Una vez que haya terminado con todos los pasos anteriores, puede iniciar su aplicación usando inicio de ejecución de npm: devy pruébelo con un cliente WebSocket como Postman.
Creación de aplicaciones en tiempo real con NestJS
Aunque existen otras tecnologías para construir sistemas en tiempo real, los WebSockets son muy populares y fáciles de implementar en muchos casos, y son la mejor opción para las aplicaciones de chat.
Las aplicaciones en tiempo real no se limitan únicamente a las aplicaciones de chat, otros ejemplos incluyen transmisión de video o aplicaciones de llamadas y aplicaciones meteorológicas en vivo, y NestJS proporciona excelentes herramientas para construir en tiempo real aplicaciones