Heroku Node.js WebSocket Sever Chat Deployment - node.js

Websocket server not connecting after deployment on heroku:
I already went through the documentation and a heroku tutorial.
I am able to run the application locally but running into problems on heroku deployment. The UI part is working, but doesn't seem to be connecting to the back end. Procfile as been updated.
The client connection url would need to be changed to a localhost:8989 to work locally. When I hard coded the url in the client before deployment, the error in console is the same as when I don't run the websocket server running with npm start locally.
https://github.com/kbventures/chatTest
Front end:
import * as types from '../constants/ActionTypes';
import { addUser, messageReceived, populateUsersList } from '../actions';
const setupSocket = (dispatch, username) => {
const socket = new WebSocket('wss://boiling-brook-76849.herokuapp.com');
//const HOST = location.origin.replace(/^http/, 'ws');
//const socket = new WebSocket(HOST);
socket.onopen = () => {
socket.send(
JSON.stringify({
type: types.ADD_USER,
name: username
})
);
};
socket.onmessage = event => {
const data = JSON.parse(event.data);
switch (data.type) {
case types.ADD_MESSAGE:
dispatch(messageReceived(data.message, data.author));
break;
case types.ADD_USER:
dispatch(addUser(data.name));
break;
case types.USERS_LIST:
dispatch(populateUsersList(data.users));
break;
default:
break;
}
};
return socket;
};
export default setupSocket;
Back end:
const WebSocket = require('ws');
const port = process.env.PORT || 8989;
const wss = new WebSocket.Server({ port });
const users = [];
const broadcast = (data, ws) => {
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN && client !== ws) {
client.send(JSON.stringify(data));
}
});
};
wss.on('connection', ws => {
let index;
ws.on('message', message => {
const data = JSON.parse(message);
switch (data.type) {
case 'ADD_USER': {
index = users.length;
users.push({ name: data.name, id: index + 1 });
ws.send(
JSON.stringify({
type: 'USERS_LIST',
users
})
);
broadcast(
{
type: 'USERS_LIST',
users
},
ws
);
break;
}
case 'ADD_MESSAGE':
broadcast(
{
type: 'ADD_MESSAGE',
message: data.message,
author: data.author
},
ws
);
break;
default:
break;
}
});
ws.on('close', () => {
users.splice(index, 1);
broadcast(
{
type: 'USERS_LIST',
users
},
ws
);
});
});
Locally the user list would populate, update and there would no errors in the browser console.
In order to reproduce error go to :
https://boiling-brook-76849.herokuapp.com/
Type a message and then check the console for the following error:
index.js:2178 uncaught at handleNewMessage at handleNewMessage
at takeEvery
at
Error: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state
...
index.js:5 WebSocket connection to 'wss://boiling-brook-76849.herokuapp.com/' failed: Error during WebSocket handshake: Unexpected response code: 503

Related

Socket.IO is creating 2 socket IDS every time a new user joins a room instead of one

I am creating a collaborative react app, in that every time a new user is joining the room the socket io is generating 2 id's for every user, I have followed the documentation code, in the same way, I am not sure why is this happening, below is the snippet of the server-side code (server.js).
const cors = require('cors');
const axios = require('axios');
const {Server} = require('socket.io');
const http = require('http');
const ACTIONS = require('../src/Actions');
const app = express(); // Create an instance of express
const server = http.createServer(app) // Create an instance of http server
const io = new Server(server); // Create an instance of socket.io server
// Storing a client list
const clients = new Map();
// Switching on the server socket to listen for connections
io.on('connection', (socket) => {
const clientSocketId = socket.id;
console.log(clientSocketId+' connected');
socket.on(ACTIONS.JOIN,({roomId,username})=>{
console.log(roomId,username)
clients.set(socket.id,{
roomId,
username,
socketId: socket.id,
})
socket.join(roomId);
const clientlist = Array.from(clients.values())
clientlist.forEach(client=>{
io.to(client.socketId).emit(ACTIONS.JOINED,{
clientlist,
username,
socketId: socket.id,
})
})
})
// The server is listening to two events Code Change and Code Sync
// Code Change is emitted when the user changes the code
// Code Sync is called when the user joins the room to sync the previously typed code
socket.on(ACTIONS.CODE_CHANGE, ({ roomId, code }) => {
socket.in(roomId).emit(ACTIONS.CODE_CHANGE, { code });
});
socket.on(ACTIONS.SYNC_CODE, ({ socketId, code }) => {
io.to(socketId).emit(ACTIONS.CODE_CHANGE, { code });
});
// Disconnecting the current socket
socket.on('disconnecting',()=>{
console.log(clientSocketId+' disconnected')
// Getting the list of all the present rooms
const rooms = Object.keys(socket.rooms);
rooms.forEach(roomId=>{
socket.in(roomId).emit(ACTIONS.DISCONNECTED,{
socketId: socket.id,
username: clients.get(socket.id).username,
})
})
clients.delete(socket.id);
socket.leave();
})
})
const PORT = process.env.SERVER_PORT || 5000;
server.listen(PORT,()=>{console.log('Listening on '+PORT)});
And below is how I have initialized the socket on the client-side
export const initSocket = async () => {
const options = {
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 'Infinity',
forceNew: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 10000,
autoConnect: true,
secure: true,
}
const socket = io(process.env.REACT_APP_SERVER_URL,options)
return socket
}
And in my Dashboard.js I have called the init function in UseEffect
React.useEffect(()=>{
// As the user joins the room we initialize the client socket which connects to the server
const init = async () => {
socketRef.current = await initSocket();
// Handling connection errors
socketRef.current.on('connect_error',(err)=>handleError(err))
socketRef.current.on('connect_failed',(err)=>handleError(err))
const handleError = (err)=>{
console.log(err)
toast({
title: 'Error connecting to the server',
status: 'error',
duration: 9000,
isClosable: true,
})
reactNavigater('/')
}
socketRef.current.emit(ACTIONS.JOIN,{
roomId: roomId,
username: location.state?.username,
});
// Listening for joined event when a even user joins
socketRef.current.on(ACTIONS.JOINED,({clientlist,username,socketId})=>{
if(username !== location.state?.username){
toast({
title: `${username} has joined the room`,
status: 'success',
duration: 9000,
isClosable: true,
})
}
setClientlist(clientlist)
socketRef.current.emit(ACTIONS.SYNC_CODE, {
socketId: socketRef.current.id,
code: codeRef.current,
});
})
// Listening for disconnected event when a even user disconnects
socketRef.current.on(ACTIONS.DISCONNECTED,({socketId,username})=>{
toast({
title: `${username} has disconnected`,
status: 'warning',
duration: 9000,
isClosable: true,
})
// Filter the clientlist to remove the disconnected client
setClientlist(Clientlist.filter(client=>client.socketId !== socketId))
}
)
}
init()
// Here we have multiple listeners, so we have to remove them when the component unmounts
return ()=>{
if(socketRef.current){
socketRef.current.disconnect()
socketRef.current.off(ACTIONS.JOINED)
socketRef.current.off(ACTIONS.DISCONNECTED)
}
}
},[])
Any help would be appreciated
If you have strict mode on, what's by default, then useEffect is called twice (from React 18). And your connection is created twice. And as every connection generates new Id, you get two id's.
https://stackoverflow.com/a/72238236/8522881
My React Component is rendering twice because of Strict Mode
You can just wrap your socket.io instance with a useRef so it will keep the same value between re-renders, like:
import React from 'react'
function MyComponent() {
const socket = React.useRef(null)
React.useEffect(() => {
if(!socket.current) {
socket.current = io('ws://my-url/');
}
}, [])
// now you can use socket.current and ensure you aways will have only one instance
return (
<p>hello</p>
)
}

GCP provided code snippets to both subscribe and publish mqtt in the same app doesn't work

In my Node.js app, I can successfully publish telemetry/state topics or subscribe to config/command topics, but can't both publish and subscribe.
Both Node.js code snippets that appear below are from
https://cloud.google.com/iot/docs/how-tos/mqtt-bridge
The subscribe code is as follows -
// const deviceId = `myDevice`;
// const registryId = `myRegistry`;
// const region = `us-central1`;
// const algorithm = `RS256`;
// const privateKeyFile = `./rsa_private.pem`;
// const serverCertFile = `./roots.pem`;
// const mqttBridgeHostname = `mqtt.googleapis.com`;
// const mqttBridgePort = 8883;
// const messageType = `events`;
// const numMessages = 5;
// The mqttClientId is a unique string that identifies this device. For Google
// Cloud IoT Core, it must be in the format below.
const mqttClientId = `projects/${projectId}/locations/${region}/registries/${registryId}/devices/${deviceId}`;
// With Google Cloud IoT Core, the username field is ignored, however it must be
// non-empty. The password field is used to transmit a JWT to authorize the
// device. The "mqtts" protocol causes the library to connect using SSL, which
// is required for Cloud IoT Core.
const connectionArgs = {
host: mqttBridgeHostname,
port: mqttBridgePort,
clientId: mqttClientId,
username: 'unused',
password: createJwt(projectId, privateKeyFile, algorithm),
protocol: 'mqtts',
secureProtocol: 'TLSv1_2_method',
ca: [readFileSync(serverCertFile)],
};
// Create a client, and connect to the Google MQTT bridge.
const iatTime = parseInt(Date.now() / 1000);
const client = mqtt.connect(connectionArgs);
// Subscribe to the /devices/{device-id}/config topic to receive config updates.
// Config updates are recommended to use QoS 1 (at least once delivery)
client.subscribe(`/devices/${deviceId}/config`, {qos: 1});
// Subscribe to the /devices/{device-id}/commands/# topic to receive all
// commands or to the /devices/{device-id}/commands/<subfolder> to just receive
// messages published to a specific commands folder; we recommend you use
// QoS 0 (at most once delivery)
client.subscribe(`/devices/${deviceId}/commands/#`, {qos: 0});
// The MQTT topic that this device will publish data to. The MQTT topic name is
// required to be in the format below. The topic name must end in 'state' to
// publish state and 'events' to publish telemetry. Note that this is not the
// same as the device registry's Cloud Pub/Sub topic.
const mqttTopic = `/devices/${deviceId}/${messageType}`;
client.on('connect', success => {
console.log('connect');
if (!success) {
console.log('Client not connected...');
} else if (!publishChainInProgress) {
publishAsync(mqttTopic, client, iatTime, 1, numMessages, connectionArgs);
}
});
client.on('close', () => {
console.log('close');
shouldBackoff = true;
});
client.on('error', err => {
console.log('error', err);
});
client.on('message', (topic, message) => {
let messageStr = 'Message received: ';
if (topic === `/devices/${deviceId}/config`) {
messageStr = 'Config message received: ';
} else if (topic.startsWith(`/devices/${deviceId}/commands`)) {
messageStr = 'Command message received: ';
}
messageStr += Buffer.from(message, 'base64').toString('ascii');
console.log(messageStr);
});
client.on('packetsend', () => {
// Note: logging packet send is very verbose
});
// Once all of the messages have been published, the connection to Google Cloud
// IoT will be closed and the process will exit. See the publishAsync method.
and the publish code is -
const publishAsync = (
mqttTopic,
client,
iatTime,
messagesSent,
numMessages,
connectionArgs
) => {
// If we have published enough messages or backed off too many times, stop.
if (messagesSent > numMessages || backoffTime >= MAXIMUM_BACKOFF_TIME) {
if (backoffTime >= MAXIMUM_BACKOFF_TIME) {
console.log('Backoff time is too high. Giving up.');
}
console.log('Closing connection to MQTT. Goodbye!');
client.end();
publishChainInProgress = false;
return;
}
// Publish and schedule the next publish.
publishChainInProgress = true;
let publishDelayMs = 0;
if (shouldBackoff) {
publishDelayMs = 1000 * (backoffTime + Math.random());
backoffTime *= 2;
console.log(`Backing off for ${publishDelayMs}ms before publishing.`);
}
setTimeout(() => {
const payload = `${argv.registryId}/${argv.deviceId}-payload-${messagesSent}`;
// Publish "payload" to the MQTT topic. qos=1 means at least once delivery.
// Cloud IoT Core also supports qos=0 for at most once delivery.
console.log('Publishing message:', payload);
client.publish(mqttTopic, payload, {qos: 1}, err => {
if (!err) {
shouldBackoff = false;
backoffTime = MINIMUM_BACKOFF_TIME;
}
});
const schedulePublishDelayMs = argv.messageType === 'events' ? 1000 : 2000;
setTimeout(() => {
const secsFromIssue = parseInt(Date.now() / 1000) - iatTime;
if (secsFromIssue > argv.tokenExpMins * 60) {
iatTime = parseInt(Date.now() / 1000);
console.log(`\tRefreshing token after ${secsFromIssue} seconds.`);
client.end();
connectionArgs.password = createJwt(
argv.projectId,
argv.privateKeyFile,
argv.algorithm
);
connectionArgs.protocolId = 'MQTT';
connectionArgs.protocolVersion = 4;
connectionArgs.clean = true;
client = mqtt.connect(connectionArgs);
client.on('connect', success => {
console.log('connect');
if (!success) {
console.log('Client not connected...');
} else if (!publishChainInProgress) {
publishAsync(
mqttTopic,
client,
iatTime,
messagesSent,
numMessages,
connectionArgs
);
}
});
client.on('close', () => {
console.log('close');
shouldBackoff = true;
});
client.on('error', err => {
console.log('error', err);
});
client.on('message', (topic, message) => {
console.log(
'message received: ',
Buffer.from(message, 'base64').toString('ascii')
);
});
client.on('packetsend', () => {
// Note: logging packet send is very verbose
});
}
publishAsync(
mqttTopic,
client,
iatTime,
messagesSent + 1,
numMessages,
connectionArgs
);
}, schedulePublishDelayMs);
}, publishDelayMs);
};
I am wondering if anyone has gotten their Node.js app to both successfully publish and subscribe with Google Cloud. If so, what might I be missing?

node js not behaving the same way on local and Google App Egine

I'm developing an app to upload .las file to cesium ion.
I have modified this code https://github.com/CesiumGS/cesium-ion-rest-api-examples/blob/main/tutorials/rest-api/index.js
To pass a file from the browser.
It's working flawlessly when I run npm start on my local env.
When I try the same thing on the app Engine, I do not get the message about where the process is at. It's does upload the file though. It's just I can't monitor what is going on.
To explain what is going on below, I send a file from the client, then it's catch by app.post("/upload"
Then, it create asset on Ion, and then upload to S3, then it tell ion it's finished, then it monitor the tiling on Ion.
Then I call every second app.post("/progress" That is sending back the stat of things to the client.
I think there is probably a logic I'm missing, something basic I make totally wrong. I'm using one single service for both the backend and the frontend. Can this be a part of the problem ?
const express = require('express');
const app = express();
const port = process.env.PORT || 3001;
const fileUpload = require("express-fileupload");
const cors = require('cors');
var path = require('path');
const AWS = require('aws-sdk');
const fs = require('fs');
const rawdatafr = require('./lang/fr.json');
const rawdataen = require('./lang/en.json');
const axios = require('axios').default;
const accessToken = process.env.REACT_APP_ION_TOKEN;
const environment = process.env.NODE_ENV || 'production';
var urlLang = (environment === 'development') ? 'client/src/lang/' : 'lang/';
console.log('urlLang ? '+urlLang);
app.use(cors());
app.use(fileUpload({
useTempFiles: true,
safeFileNames: false,
preserveExtension: true,
tempFileDir: 'temp/'
}));
'use strict';
var messageFromLoc = rawdataen;
var input = null;
var filename = null;
var srcType = 'POINT_CLOUD';
var message = null;
var needMonitoring = false;
var assetMetadata = null;
var finished = null;
function resetGlobalvar(){
message = null;
needMonitoring = false;
assetMetadata = null;
finished = null;
input = null;
filename = null;
srcType = 'POINT_CLOUD';
}
async function creat_asset(){
finished = false;
message = 'create asset';
axios.post('https://api.cesium.com/v1/assets', {
name: filename,
description: '',
type: '3DTILES',
options: {
position:[ 2.29, 48.85, 0.1],
sourceType: srcType,
}
},{
headers: { Authorization: `Bearer ${accessToken}` }
})
.then(function (response) {
message = 'created successfully :> send to s3';
sendtos3(response.data);
})
.catch(function (error) {
console.log(error);
message = error;
});
}
async function sendtos3(response){
console.log('Asset created.');
message = 'send to s3';
try{
const uploadLocation = response.uploadLocation;
const s3 = new AWS.S3({
apiVersion: '2006-03-01',
region: 'us-east-1',
signatureVersion: 'v4',
endpoint: uploadLocation.endpoint,
credentials: new AWS.Credentials(
uploadLocation.accessKey,
uploadLocation.secretAccessKey,
uploadLocation.sessionToken)
});
let params = {
Body: fs.createReadStream(input),
Bucket: uploadLocation.bucket,
Key: uploadLocation.prefix+filename
};
let s3Response = await s3.upload(params).on('httpUploadProgress', function (progress) {
message = `${messageFromLoc.upload}: ${((progress.loaded / progress.total) * 100).toFixed(2)}%`;
console.log(`Upload: ${((progress.loaded / progress.total) * 100).toFixed(2)}%`);
}).promise();
// request successed
console.log(`File uploaded to S3 at ${s3Response.Bucket} bucket. File location: ${s3Response.Location}`);
message = `File uploaded to S3 at ${s3Response.Bucket} bucket. File location: ${s3Response.Location}`;
step3(response);
// return s3Response.Location;
}
// request failed
catch (ex) {
console.error(ex);
message = ex;
}
}
async function step3(response){
const onComplete = response.onComplete;
assetMetadata = response.assetMetadata;
message = 'step3';
axios.post(onComplete.url, onComplete.fields,{
headers: { Authorization: `Bearer ${accessToken}` }
})
.then(function (response) {
message = 'step3 done';
monitorTiling(assetMetadata);
})
.catch(function (error) {
console.log(error);
message = error;
});
}
async function monitorTiling(assetMetadata){
// console.log(response);
const assetId = assetMetadata.id;
message = 'monitorTiling';
axios.get(`https://api.cesium.com/v1/assets/${assetId}`,{headers: { Authorization: `Bearer ${accessToken}` }})
.then(function (response) {
// handle success
console.log('monitorTiling - success');
var status = response.data.status;
message = 'Tiling - success';
if (status === 'COMPLETE') {
console.log('Asset tiled successfully');
console.log(`View in ion: https://cesium.com/ion/assets/${assetMetadata.id}`);
message = 'Asset tiled successfully';
needMonitoring = false;
finished = true;
} else if (status === 'DATA_ERROR') {
console.log('ion detected a problem with the uploaded data.');
message = 'ion detected a problem with the uploaded data.';
needMonitoring = false;
finished = true;
} else if (status === 'ERROR') {
console.log('An unknown tiling error occurred, please contact support#cesium.com.');
message = 'An unknown tiling error occurred, please contact support#cesium.com.';
needMonitoring = false;
finished = true;
} else {
needMonitoring = true;
if (status === 'NOT_STARTED') {
console.log('Tiling pipeline initializing.');
message = 'Tiling pipeline initializing.';
} else { // IN_PROGRESS
console.log(`Asset is ${assetMetadata.percentComplete}% complete.`);
message = `Asset is ${assetMetadata.percentComplete}% complete.`;
}
}
})
.catch(function (error) {
// handle error
console.log(error);
message =error;
})
}
/*------- LISTEN FOR CALL TO UPLOAD AND START THE UPLOAD PROCESS ----------*/
app.post("/upload", (req, res) => {
if (!req.files) {
res.send("File was not found");
message = 'File was not found';
return;
}
input = req.files.file.tempFilePath;
filename = req.files.file.name;
emptyTempFolder('temp', input.replace('temp/', ''));
var ext = path.extname(filename);
if(ext=='.zip'){
srcType = 'CITYGML';
}
/*------- START UPLOAD PROCESS ----------*/
creat_asset();
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
/*------- LISTEN FOR PROGRESS TO UPLOAD ASSET ----------*/
app.get("/progress", (req, res) => {
// lang = req.get('Accept-Language').substring(0, 2).toLowerCase();
// if(lang=='fr'){
// messageFromLoc = rawdatafr;
// }
console.log('message ='+message);
if(needMonitoring){
monitorTiling(assetMetadata);
}
res.json({ message: message, done: finished, myAssetMetadata: assetMetadata });
if(finished){
resetGlobalvar();
}
});
/*--------------STATIC ----------------*/
app.use(express.static( path.join(__dirname, 'build' )));
And my app.yaml is like this :
runtime: nodejs14
env: standard
includes:
- env_variables.yaml
instance_class: B1
service: my-app
basic_scaling:
max_instances: 25
idle_timeout: 60m
I think this is from your instance(s). You're using basic scaling with up to 25 instances.
It looks like a combination of the following is happening
a) When you send a request to /progress, a new instance of your App is created which means all of the global variables are starting from their default values (the initial value of message is null).
b) Other times, a request to /progress is handled by an existing instance which was already processing an upload request and that request has completed and so the message says completed
You don't have this problem on your local environment because only 1 instance runs.
To test this theory, modify your app.yaml and set max_instances: 1. This is supposed to force the App to only use 1 instance which means subsequent requests should use an existing instance (which has the updated state of your global variables)

reconnecting to nodejs websocket on failure

This is my first practice after reading some tutorials and videos. Basically, I need message to be sent from the server (nodejs) to the client (Angular 6). At first tho, when client app is booted, it sends user id to the server for authentication. The server then will send data based on that user.
Now my problem is on first load and a few calls, the connection does work. But then on refresh or so, the connection drops. My client does console out "retrying" but it never succeeds. It works only if I manually restart the server and reload the client so a new connection could be established.
How can I maintain a fairly stable connection throughout the lifetime of a client? At times the readyState stays at 3 on the server i.e. connecting, which I am confused with because the client does try to reconnect...just fails.
My Server is simple. index.js (Tried to put it up on stackblitz but failed...would appreciate if someone can figure out the dependency file: nodejs websocket server)
const express = require('express');
const bodyParser = require('body-parser');
const pg = require ('pg');
var ws = require('./ws')
var app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(3000, function () {
console.log('We are running on port 3000!')
})
ws.js:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'error.log'})
]
});
var WebSocketServer = require("ws").Server,
wss = new WebSocketServer({
port: 40511
});
let data = {
'packet': ['amy1', 'amy2', 'amy3']
}
const mx = 2;
const mn = 0;
wss.on("connection", function(ws) {
ws.on("message", function(user) {
// client has called now. If the connection
// fails, the client does try to connection again and again -- no limit but it simply doesn't seem to have effect. When connecting, it simply sends user name
console.log("received: %s", user);
setInterval(function(){
var random = Math.floor(Math.random() * (mx - mn + 1) + mn);
if (ws.readyState == ws.OPEN){
ws.send(data['packet'][random]);
}
}, 3000);
});
});
My front end is: service.ts
import { Observable} from 'rxjs';
export class WebSocketService {
socket: WebSocket;
constructor() { }
initConnection(): void {
if(!this.socket){
this.socket = new WebSocket('ws://localhost:40511');
// when connection is open, send user id
this.socket.onopen = () => this.socket.send(2);
}
}
manageState() : Observable<any>{
const vm = this;
return new Observable(observer => {
this.socket.onerror = (e) => {
// close it
this.socket.close();
observer.next('web socket communication closed due to error')
};
this.socket.onclose = (e) => {
//socket closed for any reason
setTimeout(function() {
console.log('try to connect')
vm.initConnection();
observer.next('still trying')
}, 1000);
}
});
}
onMessage(): Observable<any> {
// when message arrives:
return new Observable(observer => {
this.socket.onmessage = (e) => {
console.log(e.data);
observer.next(e.data)
};
});
}
}
component.ts:
// initialize the connection
this.service.initConnection();
this.service.onMessage().subscribe(
data => {
// we have got data
console.log('data came ', data)
},
err => {
console.log("error websocking service ", err);
}
);
// track state of the communication, letting the service to reconnect if connection is dropped
this.service.manageState().subscribe(data => {
console.log(data);
});

Check mongoose connection state without creating new connection

I have some tests - namely Supertest - that load my Express app. This app creates a Mongoose connection. I would like to know how to check the status of that connection from within my test.
In app.js
mongoose.connect(...)
In test.js
console.log(mongoose.connection.readyState);
How to access the app.js connection? If I connect using the same parameters in test.js will that create a new connection or look for existing one?
Since the mongoose module exports a singleton object, you don't have to connect in your test.js to check the state of the connection:
// test.js
require('./app.js'); // which executes 'mongoose.connect()'
var mongoose = require('mongoose');
console.log(mongoose.connection.readyState);
ready states being:
0: disconnected
1: connected
2: connecting
3: disconnecting
I use this for my Express Server mongoDB status, where I use the express-healthcheck middleware
// Define server status
const mongoose = require('mongoose');
const serverStatus = () => {
return {
state: 'up',
dbState: mongoose.STATES[mongoose.connection.readyState]
}
};
// Plug into middleware.
api.use('/api/uptime', require('express-healthcheck')({
healthy: serverStatus
}));
Gives this in a Postman request when the DB is connected.
{
"state": "up",
"dbState": "connected"
}
Gives this response when the database was shutdown.
{
"state": "up",
"dbState": "disconnected"
}
(The "up" in the responses represent my Express Server status)
Easy to read (no numbers to interpret)
As stated before "readyState" is good. "ping" is also good admin utility for doing so as well. It will return { ok: 1 } if it can accept commands.
const mongoose = require('mongoose')
// From where ever your making your connection
const connection = await mongoose.createConnection(
CONNECT_URI,
CONNECT_OPTS
)
async function connectionIsUp(): Promise<boolean> {
try {
const adminUtil = connection.db.admin()
const result = await adminUtil.ping()
console.log('result: ', result) // { ok: 1 }
return !!result?.ok === 1
} catch(err) {
return false
}
}
Or if you you want it short.
async function connectionIsUp(): Promise<boolean> {
try {
return await connection.db.admin().ping().then(res => !!res?.ok === 1)
} catch (err) {
return false
}
}
var dbState = [{
value: 0,
label: "disconnected"
},
{
value: 1,
label: "connected"
},
{
value: 2,
label: "connecting"
},
{
value: 3,
label: "disconnecting"
}];
mongoose.connect(CONNECTIONSTRING, {
useNewUrlParser: true
},
() => {
const state = Number(mongoose.connection.readyState);
console.log(dbState.find(f => f.value == state).label, "to db"); // connected to db
});

Resources