I'm confused about the callback here. We return the client in the promise, but how are we executing that in the callback? If the client object is the callback then isn't the client calling the client? What is the callback equivalent to?
const mongodb = require('mongodb');
const MongoClient = mongodb.MongoClient;
const mongoConnect = callback => {
MongoClient.connect(
'mongodb+srv://<user>:<password>#cluster0-ntrwp.mongodb.net/test?retryWrites=true'
)
.then(client => {
console.log('Connected!');
callback(client);
})
.catch(err => {
console.log(err);
});
};
module.exports = mongoConnect;
(app.js)
mongoConnect(client => {
console.log(client);
app.listen(3000);
});
Isn't this the expected output. I'm a little confused about the callback here.
const mongodb = require('mongodb');
const MongoClient = mongodb.MongoClient;
const mongoConnect = client => {
console.log(client);
app.listen(3000); => {
MongoClient.connect(
'mongodb+srv://<user>:<password>#cluster0-ntrwp.mongodb.net/test?retryWrites=true'
)
.then(client => {
console.log('Connected!');
client => {
console.log(client);
app.listen(3000);(client);
})
.catch(err => {
console.log(err);
});
};
module.exports = mongoConnect;
(app.js)
mongoConnect(client => {
console.log(client);
app.listen(3000);
});
I'm guessing you're asking how the 1st code works. I rewrote the 1st code to use old school function() instead of the ES6 fat arrow and put in some logging to make it clearer what's going on:
const express = require('express');
const mongodb = require('mongodb');
const MongoClient = mongodb.MongoClient;
const app = express();
function mongoConnect(callback) {
MongoClient.connect('mongodb://localhost:27017')
.then(client => {
console.log('[mongoConnect] Connected!');
console.log('[mongoConnect] callback type: ' + typeof(callback));
console.log('[mongoConnect] client type: ' + typeof(client));
console.log('[mongoConnect] client is: ' + client.constructor.name);
callback(client);
})
.catch(err => {
console.log(err);
});
};
mongoConnect(function(client) {
console.log('[main] client type: ' + typeof(client));
console.log('[main] client is: ' + client.constructor.name);
app.listen(3000, function() {
console.log('[main] listening on 3000');
});
});
Running the code:
[mongoConnect] Connected!
[mongoConnect] callback type: function
[mongoConnect] client type: object
[mongoConnect] client is: MongoClient
[main] client type: object
[main] client is: MongoClient
[main] listening on 3000
As to your question:
If the client object is the callback then isn't the client calling the client? What is the callback equivalent to?
To unravel what's going on:
client is an object, and callback is a function.
mongoConnect() accepts a function as parameter.
The function that gets passed into mongoConnect() accepts client (an object) as parameter. Technically this is not needed. I think this is only there for audit/logging purposes. You can remove all mentions of client in that part of the code and see that it still works.
For example, you can change that bit of code to be:
mongoConnect(function() {
app.listen(3000, function() {
console.log('[main] listening on 3000');
});
});
and it will still work the same way.
The execution sequence is:
mongoConnect(function(client)) is called
Inside mongoConnect, MongoClient.connect() is called
Once connected, the callback (function(client)) is called
The callback executes app.listen()
This flow sequence ensures that the database is connected before the app goes to listen mode. Without this, the app can listen before any database connection is confirmed (e.g. due to slower than expected database connection, etc.). You can confirm this flow by observing that [mongoConnect] log lines are always printed before any [main] log lines.
Related
I am working on an app that involves the use of WebSockets. I have the following code as a part of the application:
io.on('connection', socket => {
let PLAYER = {};
// Listener for event 1
// Listener for event 2
// ...
socket.on('setName', ({name, role, room}) => {
PLAYER.name = name;
PLAYER.role = role;
PLAYER.room = room;
try{
joinRoom(PLAYER); // This function can throw an error
socket.emit('roomJoin');
}catch(e){
socket.emit('error', e.message);
return;
}
});
});
Now, when the joinRoom function does throw an error, Node just crashes with the following exception:
events.js:306
throw err; // Unhandled 'error' event
^
Error [ERR_UNHANDLED_ERROR]: Unhandled error. ('error message')
The error event is never emitted. What has made the try catch block fail?
Note: I am using TypeScript, but have removed type declarations in the above snippet as they shouldn't have anything to do with the problem.
Edit 1:
I have recreated the issue with simpler code as follows:
import express from 'express';
import socketio from 'socket.io';
import http from 'http';
const PORT = process.env.PORT || 8000;
const app = express();
const server = http.createServer(app);
const io = socketio(server);
server.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
const checkLength = (bar) => {
if(bar.length > 14){
throw Error('word is too long!');
}
// ...
}
io.on('connection', socket => {
console.log('new socket connection');
socket.on('foo', ({bar}) => {
try{
checkLength(bar);
}catch(e){
socket.emit('error', e.message);
return;
}
console.log('app did not crash!');
});
}
When the length of bar is greater than 14, the app crashes with the same error mentioned above.
After replicating the server on my local machine, I realized you were calling emit on a reserved keyword for socket.io. When you call socket.emit('error', e.message), there is an expected listener to exist. One like this:
socket.on('error', err => {
// An error occurred.
})
You should use a different phrase for your emit. I personally use oops on my socket servers. socket.emit('oops', {error}). You can read more about error handling on their page.
To add, socket.emit('error') does not pass the error or any data down to the client and instead is handled by the server, which is why you need to use a different phrase.
You can use the promisses, and than you can profite from the then() method which also returns a Promise. It takes up to two arguments: callback functions for the success and failure cases of the Promise.
So your code should be something like this:
import express from 'express';
import socketio from 'socket.io';
import http from 'http';
const PORT = process.env.PORT || 8000;
const app = express();
const server = http.createServer(app);
const io = socketio(server);
server.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
const checkLength = (bar) => {
return new Promise((resolve, reject)=>{
if(bar.length > 14){
// throw Error('word is too long!');
reject('word is too long!')
} else {
resolve('app did not crash!')
}
});
}
io.on('connection', socket => {
console.log('new socket connection');
socket.on('foo', ({bar}) => {
checkLength(bar).then((result) => {
console.log('app did not crash!');
}).catch(err => {
console.log("app did crash with this error:", err);
});
});
}
I noticed you hadn't initialized your PLAYER variable.
let PLAYER = {};
#EDIT 1:
try also adding an error handler for your socket server and log it to get an idea of what is causing the error.
socket.on('error', (error) => {
console.log(error);
});
I'm trying to execute a stored procedure and when I run the code below, the code in the function for the query is not being run at all. There is also no error. I am thinking somehow SQL Server is interpreting the query for the stored procedure incorrectly and stalling because if I use a regular query e.g. select top 1 * from Custom.ExampleTable it works fine.
var express = require('express');
var app = express();
const sql = require("mssql/msnodesqlv8");
// example db connection...
const connectionString = "server=dev\\instance1;database=123456;user=ORG\\NAME;Trusted_Connection=Yes;"
sql.connect(connectionString, err => {
console.log("connected: " + connectionString)
try {
new sql.Request().input('var', sql.VarChar, 2222).execute('dbo.check_ex', (err, results) => {
// code not being run...
console.dir("test")
if (err) {
console.log(err);
}
console.log(rows);
})
} catch (err) {
console.log(err);
}
})
var server = app.listen(5000, function () {
console.log('Server is running..');
});
Output below:
Server is running..
connected: server=dev\\instance1;database=123456;user=ORG\\NAME;Trusted_Connection=Yes;
I'm actually trying to make a real-time connection between two different apps. I've found a bunch of tutorials about how to make a chat using socket.io, but that doesn't really help me since it's just the same app duplicated in multiple windows.
I'm making a pick & ban overlay for League of Legends in local development. My first thought was to display the empty overlay on one hand and create an interface to manually update it on the other hand. Socket.io seems to be the right thing to use in my case since it can provide new data without having to reload the component.
This is what I wrote in both apps :
const express = require('express');
const socket = require('socket.io');
// App setup
const app = express();
const server = app.listen(4200, function () {
console.log('Listening to requests on port 4200')
});
// Static files
app.use(express.static('public'));
// Socket setup
const io = socket(server);
io.on('connection', function (socket) {
console.log('Made socket connection', socket.id);
socket.on('change', function (data) {
io.sockets.emit('change', data);
});
});
But I fail to connect them as they have to listen to the same port. What am I doing wrong?
(Forgive my bad English and lack of syntax, I'm doing my best here. :p)
I am certainly not an expert on network programming, but as far as I know you need to have one listening app (backend) and another one to connect to it (client). And you define what happens with all the data (messages) that backend recieves (for example sending the messages it recieves to all the clients in the same chat room).
If I am correct to assume you are trying to connect two listening apps?
simple google search of "nodejs socket server client example" revealed this https://www.dev2qa.com/node-js-tcp-socket-client-server-example/ might wanna take your research in this direction
u can try something like this way
var express = require('express');
var socket = require('socket.io');
// App setup
var app = express();
var server = app.listen(8080, () => {
console.log('App started')
})
// Static file
app.use(express.static('public'))
// Socket SetUp
var io = socket(server);
io.on('connection', socket => {
console.log('made the connection')
socket.on('chat',data => {
io.sockets.emit('chat',data)
});
socket.on('typing',data => {
socket.broadcast.emit('typing',data);
});
})
create another file and
var socket = io.connect('http://localhost:8080')
// Elenment
var message = document.getElementById('message');
handle = document.getElementById('handle');
btn = document.getElementById('send');
output = document.getElementById('output');
feedback = document.getElementById('feedback');
// Emit Events
btn.addEventListener('click', () => {
socket.emit('chat', {
message: message.value,
handle: handle.value
})
})
message.addEventListener('keypress', () => {
socket.emit('typing', handle.value)
})
socket.on('chat',data => {
feedback.innerHTML = '';
output.innerHTML += '<p><strong>' + data.handle +': </strong>' +
data.message + '</p>'
})
socket.on('typing', data => {
feedback.innerHTML = '<p><emp>' + data + ' is typing a message... </emp></p>'
})
details are given here node socket chat app
Ok, figured it out. Here's how it works using express and vue together :
First, setup socket.io in your express server js file :
const express = require('express')
const { Server } = require('socket.io')
const http = require('http')
const app = express()
const server = http.createServer(app)
const io = new Server(server, {
cors: {
origin: '*',
methods: ['GET', 'POST', 'REMOVE']
}
})
const PORT = process.env.PORT || 8080
io.on('connection', (socket) => {
console.log('New socket user')
socket.on('SEND_MESSAGE', data => {
console.log('received message in back')
io.emit('MESSAGE', data)
})
})
server.listen(PORT, () => { console.log(`Server started on port : ${PORT}`)})
As you can see we received from the client "SEND_MESSAGE" and we trigger MESSAGE from the server to forward the information to all the clients. The point I was missing is that we bind SEND_MESSAGE on the socked created from the connection but we emit from the io server.
Now you vue part :
import io from 'socket.io-client'
export default {
data() {
return {
messages: [],
inputMessage: null,
socket: io('http://localhost:8080')
}
},
mounted() {
this.socket.on('MESSAGE', data => {
this.messages.push(data)
})
},
methods: {
sendMessage() {
const message = {
senderID: this.myID,
message: this.inputMessage,
sendAt: new Date()
}
this.socket.emit('SEND_MESSAGE', message)
this.inputMessage = null
},
},
}
I have an express app with a few endpoints and am currently testing it using mocha, chai, and chai-http. This was working fine until I added logic for a pooled mongo connection, and started building endpoints that depended on a DB connection. Basically, before I import my API routes and start the app, I want to make sure I'm connected to mongo.
My problem is that I'm having trouble understanding how I can export my app for chai-http but also make sure there is a DB connection before testing any endpoints.
Here, I am connecting to mongo, then in a callback applying my API and starting the app. The problem with this example is that my tests will start before a connection to the database is made, and before any endpoints are defined. I could move app.listen and api(app) outside of the MongoPool.connect() callback, but then I still have the problem of there being no DB connection when tests are running, so my endpoints will fail.
server.js
import express from 'express';
import api from './api';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 3000;
MongoPool.connect((err, success) => {
if (err) throw err;
if (success) {
console.log("Connected to db.")
// apply express router endpoints to app
api(app);
app.listen(port, () => {
console.log(`App listening on port ${port}`);
})
} else {
throw "Couldnt connect to db";
}
})
export default app;
How can I test my endpoints using chai-http while making sure there is a pooled connection before tests are actually executed? It feels dirty writing my application in a way that conforms to the tests I'm using. Is this a design problem with my pool implementation? Is there a better way to test my endpoints with chai-http?
Here is the test I'm running
test.js
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server').default;;
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Forecast', () => {
/*
* Test the /GET route
*/
describe('/GET forecast', () => {
it('it should GET the forecast', (done) => {
chai.request(server)
.get('/api/forecast?type=grid&lat=39.2667&long=-81.5615')
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
});
And this is the endpoint I'm testing
/api/forecast.js
import express from 'express';
import MongoPool from '../lib/MongoPool';
let router = express.Router();
let db = MongoPool.db();
router.get('/forecast', (req, res) => {
// do something with DB here
})
export default router;
Thank you for any help
After receiving some good feedback, I found this solution works best for me, based on Gomzy's answer and Vikash Singh's answer.
In server.js I'm connecting to the mongo pool, then emitting the 'ready' event on the express app. Then in the test, I can use before() to wait for 'ready' event to be emitted on the app. Once that happens, I'm good to start executing the test.
server.js
import express from 'express';
import bodyParser from 'body-parser';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 5000;
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
(async () => {
await MongoPool.connect();
console.log("Connected to db.");
require('./api').default(app);
app.listen(port, () => {
console.log(`Listening on port ${port}.`)
app.emit("ready");
});
})();
export default app;
test.js
//Require the dev-dependencies
import chai from 'chai';
import chaiHttp from 'chai-http';
import server from '../src/server';
let should = chai.should();
chai.use(chaiHttp);
before(done => {
server.on("ready", () => {
done();
})
})
describe('Forecast', () => {
describe('/GET forecast', () => {
it('it should GET the forecast', (done) => {
chai.request(server)
.get('/api/forecast?type=grid&lat=39.2667&long=-81.5615')
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
});
Express app is an instance of EventEmitter so we can easily subscribe to events. i.e app can listen for the 'ready' event.
Your server.js file will look like below,
import express from 'express';
import api from './api';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 3000;
app.on('ready', function() {
app.listen(3000, function() {
console.log('app is ready');
});
});
MongoPool.connect((err, success) => {
if (err) throw err;
if (success) {
console.log('Connected to db.');
// apply express router endpoints to app
api(app);
// All OK - fire (emit) a ready event.
app.emit('ready');
} else {
throw 'Couldnt connect to db';
}
});
export default app;
Just create a function below to connect to mongo and, make it returns a promise.
then use await to wait for it to connect and return. the function could be like that
function dbconnect(){
return new Promise(function(resolve, reject){
MongoPool.connect((err, success) => {
if (err) reject(err);
if (success) {
resolve({'status' : true})
} else {
reject(new Error({'status' : false}))
}
})
})
}
And then, use
await dbconnect();
api(app);
app.listen(port, () => {
console.log(`App listening on port ${port}`);
})
now await line will wait for the function to connect to DB and then return success or error in case of failure.
This is a kind of solution you can use, but I would not recommend you to do this, what we actually do is.
create services and use those services in routes, don't write DB code directly in routes.
and
while writing tests for routes mock/stub those services, and test services separately in other test cases, where you just pass DB object and service will add functions on that DB objects, so in tests you can connect to DB and pass that object to those services to test functions, it will give you additional benefit, if you want to use dummy/test DB for testing you can set that in test cases.
Use Before function in your tests like below :
describe('Forecast', () => {
before(function(done){
checkMongoPool(done); // this function should wait and ensure mongo connection is established.
});
it('/GET forecast', function(cb){
// write test code here ...
});
});
And you can check mongodb connection like this below methods:
Method 1: just check the readyState property -
mongoose.connection.readyState == 0; // not connected
mongoose.connection.readyState == 1; // connected`
Method 2: use events
mongoose.connection.on('connected', function(){});
mongoose.connection.on('error', function(){});
mongoose.connection.on('disconnected', function(){});
You can use running server instead of a express instance.
Start your server with a private port, then take tests on the running server.
ex: PORT=9876 node server.js
In your test block, use chai.request('http://localhost:9876') (replace with your protocol, server ip...) instead of chai.request(server).
If you're using native mongodb client you could implement reusable pool like:
MongoPool.js
// This creates a pool with default size of 5
// This gives client; You can add few lines to get db if you wish
// connection is a promise
let connection;
module.exports.getConnection = () => {
connection = MongoClient(url).connect()
}
module.exports.getClient = () => connection
Now in your test you could,
const { getConnection } = require('./MongoPool')
...
describe('Forecast', () => {
// get client connection
getConnection()
...
In your route:
...
const { getClient } = require('./MongoPool')
router.get('/forecast', (req, res) => {
// if you made sure you called getConnection() elsewhere in your code, client is a promise (which resolves to mongodb connection pool)
const client = getClient()
// do something with DB here
// then you could do something like client.db('db-name').then(//more).catch()
})
This is similar to Ensuring Express App is running before each Mocha Test , but the specified solution still isnt working + i'm using a websocket server
in short , i'm using a websocket framework called socketcluster and this is my server file
import {SocketCluster} from 'socketcluster';
const socketCluster = new SocketCluster({
workers:1,
brokers:1,
port: 3000,
appName:null,
initController: __dirname + '/init.js',
workerController: __dirname + '/worker.js',
brokerController: __dirname + '/broker.js',
socketChannelLimit: 1000,
crashWorkerOnError: true
})
export default socketCluster
running node server.js starts the worker process specified in worker.js
export const run = (worker) => {
console.log(' >> worker PID: ',process.pid);
const app = express();
const httpServer = worker.httpServer;
const scServer = worker.scServer;
app.use(cookieParser())
httpServer.on('request', app);
app.get('/',(req,res) => {
console.log('recieved')
res.send('Hello world')
})
}
I want to test the server , but the tests are finishing (and failing) way before the server actually starts. is there a way i can force the server to fully load before going ahead with tests? this is what i have so far
describe('Express server',() =>{
beforeEach((done) => {
require('../../server/server')
done()
})
it('should return "Hello World"',(done) => {
http.get('http://127.0.0.1:3000',(res) => {
expect(res).to.contain('wtf world')
done()
})
})
})
the above doesnt seem to work. the server doesnt fully load in the before block despite providing the done() call as well.
edit - i've tried splitting the server.js file to invoke a different server based on how its imported.
const main = () => {
console.log('main server')
new SocketCluster({
workers:1,
brokers:1,
port: 3000,
appName:null,
initController: __dirname + '/init.js',
workerController: __dirname + '/worker.js',
brokerController: __dirname + '/broker.js',
socketChannelLimit: 1000,
crashWorkerOnError: true
})
}
export const test = (port,done) => {
console.log('testing server')
new SocketCluster({
workers:1,
brokers:1,
port: port,
appName:null,
initController: __dirname + '/init.js',
workerController: __dirname + '/worker.js',
brokerController: __dirname + '/broker.js',
socketChannelLimit: 1000,
crashWorkerOnError: true
})
done()
}
if (require.main === module){
main()
}
and in test.js , i do this - still doesnt seem to work though
import {expect} from 'chai';
import {test} from '../../server/server'
describe('Express server',() =>{
before(function(done){
test(3000,done)
})
it('should return "Hello World"',(done) => {
http.get('http://127.0.0.1:3000',(res) => {
expect(res).to.contain('world')
done()
})
})
})
edit:2 - trie another way by returning a promise from the server.js file. still doesnt work
export const test = (port) => {
console.log('testing server')
return Promise.resolve(new SocketCluster({
workers:1,
brokers:1,
port: port,
appName:null,
initController: __dirname + '/init.js',
workerController: __dirname + '/worker.js',
brokerController: __dirname + '/broker.js',
socketChannelLimit: 1000,
crashWorkerOnError: true
}))
}
and in the before hook
before(function(done,port){
test(3000).then(function(){
console.log('arguments: ',arguments)
done()
})
})
Your server module doesn't have a callback, so it could not be ready when you call done() in your beforeEach method.
First, export your app in your server module.
Then, do something like:
const app = require('../../server/server').app;
let server;
before(done => {
server = app.listen(3000, done);
});
/**
...
your tests here
...
**/
/** and, if you need to close the server after the test **/
after(done => {
server.close(done);
});
This way, done() will be called in the listen callback, so in your tests the server will be listening correctly. Then, remember to close it after tests end (useful if server is required in one or more test suites).
The solution explained here worked for me, in particular:
At the end of server.js ( or app.js ):
app.listen( port, ip, function()
{
console.log( 'Server running on http://%s:%s', ip, port )
app.emit( "app_started" )
})
module.exports = app
and in test.js:
var server = require( '../server' )
before( done =>
{
server.on( "app_started", function()
{
done()
})
})
In this case, app sends an "app_started" event when it is listening, and the test code waits for it. The provided URL contains more details.
Hope it helps !
You need to wait until the server actually listens on the given port.
This could be accomplished by exporting some kind of init function in your server.js, which takes the done callback from mocha.
In your server.js
let initCallback;
[..]
app.listen(port, function() {
if (initCallback) {
initCallback();
}
});
exports = {
init: function(cb) {
initCallback = cb;
}
}
In your test
beforeEach((done) => {
require('../../server/server').init(done)
})
Also see: How to know when node.js express server is up and ready to use
I combined the first two posts and it worked for mine.
First, make sure you have init code in your app.js or server.js
// put this in the beginning of your app.js/server.js
let initCallback;
//put this in the end of your app.js/server.js
if (initCallback) {
// if this file was called with init function then initCallback will be used as call back for listen
app.listen(app.get('port'),"0.0.0.0",(initCallback)=>{
console.log("Server started on port "+app.get('port'));
});
}
else{
// if this file was not called with init function then we dont need call back for listen
app.listen(app.get('port'),"0.0.0.0",()=>{
console.log("Server started on port "+app.get('port'));
});
}
//now export the init function so initCallback can be changed to something else when called "init"
module.exports = {
init: function(cb) {
initCallback = cb;
}
}
Next in your test.js you will need this
//beginning of your code
const app = require("../../server/server").app;
before(done => {
require("../app").init();
done();
});
//end of your code
after(done => {
done();
});
I am no expert in javascript but this works for me. Hope it helps!