Using node CRON job to call own request - javascript

I have the following snippet:
const express = require('express')
const app = express()
const cronJob = require('cron').CronJob
app.get('/test', (req, res) => {
// Do something here
}
new cronJob('* * * * * *', () => {
// Call localhost:3000/test here
}, null, true, 'Asia/Manila')
app.listen(3000, () => console.log('Successfully listened to app 3000'))
Usually on node, localhost:3000/test runs if this is called on the browser right? I wanted to make the CRON run this without typing it on the browser once the node app starts. If possible also regardless of the hostname, whether it's localhost or not, the CRON should make the request without being typed on the browser. Can this be done?

I read the comments above on the questions itself and decided to add my thoughts even thought it seems like you got a solution.
In my opinion it will be much more clean for you to call the "method" itself instead of hitting "http" for getting the response you need.
You have 2 options:
Hitting the "domain.com/test" endpoint with a request call.
Simply calling the same method the above url is doing.
In this way, you will "save" the overhead of need to "set-up" a new request to the express app with response and request headers. (example below)
let's say this is your code:
const handleTestData = () => return 'something';
app.get('/test', (req, res) => {
const result = handleTestData();
res.send(result);
}
new cronJob('* * * * * *', () => {
// instead of calling http and getting the response
// and adding overhead, just call the function
const result = handleTestData();
// do what you need with the result
}, null, true, 'Asia/Manila')

Related

node js schedule cron job to call express route every week

I have a page that when you access it, it runs tests. I want my tests to run every week so Im trying to create a cron job that access my express route every week. Sort of like Im making a get request.
For testing sake I have a cron job that runs every 2 minutes:
//schedule job every 2 minutes
schedule.scheduleJob("*/2 * * * *", function () {
console.log('inside cron function')
});
router.get('/my_page_route_name', ensureAuthenticated, async function(req, res){
res.render('my_page_route_file', {
layout: 'dashboard.handlebars',
jsMain: 'my_page_route_js',
});
});
If I go in my url to http://localhost:1337/my_page_route_name It goes inside the router.get request just fine. But Is there a way I can trigger my cron job to call the same route and render the page every 2 minutes?
I'm unsure of how to do this because the router.get function uses res.render, and I have no res variable in my cron job
{{ EDIT }}
My cron jobs works and triggers a POST request to my route:
schedule.scheduleJob("*/10 * * * *", async function() {
console.log('inside cron function');
const resp = await fetch("http://localhost:1337/my_page_route_name/", {
"headers": {
"content-type": "application/json"
},
"method": "post",
"body": JSON.stringify({
"username":"exvar",
"password":"examplevar2"
})
});
});
and i created an express route to receive the POST request;
router.post('/my_page_route_name', async function(req, res){
res.render('my_page_route_name_html', {
layout: 'dashboard.handlebars',
jsMain: 'my_page_route_name_jsmain',
});
})
If I make a request in postman I can see the posr route returns the webpage html, but no scripts have been run, for example I have <script> document.querySelector('.breadcrumbs').append('[[ html loadded ]]') </script> inside my html file that gets loaded, but the code doesnt seem to be ran in the response I recieve
Use a fetch package in node as http requests get pretty complicated quickly.
const fetch = require('node-fetch');
//schedule job every 2 minutes
schedule.scheduleJob("*/2 * * * *", async function() {
const response = await fetch('https://yourdomain.tld/my_page_route_name');
const body = await response.json();
console.log('inside cron function', body);
});
router.get('/my_page_route_name', ensureAuthenticated, async function(req, res){
res.render('my_page_route_file', {
layout: 'dashboard.handlebars',
jsMain: 'my_page_route_js',
});
});

Node/Express - use API JSON response to (server-side) render the app

Preamble: I'm new to web dev so maybe this might be a very basic question for you vets.
I'm using MVC architecture pattern for this basic app. I've models (MongoDB), views (Express Handlebars), and controllers (functions that take in req, res, next and returns promises (.then > JSON is returned, .catch > error is returned). I'll be routing the paths reqs to their corresponding api endpoints in the controllers.
This makes sense (right?) when I'm purely working on API calls where JSON is the res. However, I also want to call these api endpoints > get their res.json > and use that to render my HTML using Handlebars. What is the best way to accomplish this? I can create same controllers and instead of resp being JSON, I can do render ("html view", res.json). But that seems like I'm repeating same code again just to change what to do with the response (return JSON or Render the JSON).
Hope I'm making sense, if not, do let me know. Please advise.
p.s. try to ELI5 things for me. (:
Edit:
//Model Example
const Schema = require('mongoose').Schema;
const testSchema = new Schema({
testText: { type: String, required: true },
});
const Test = mongoose.model('Test', testSchema);
module.exports = Test;
//Controller Example
const model = require('../models');
module.exports = {
getAll: function(req, res, next) {
model.Test.find(req.query)
.then((testItems) => {
!testItems.length
? res.status(404).json({ message: 'No Test Item Found' })
: res.status(200).json(testItems);
})
.catch((err) => next(err));
},
};
//Route Example
const router = require('express').Router(),
controller = require('../controllers');
router.get('/', controller.getAll);
module.exports = router;
I want the endpoints to return JSON and somehow manage whether to render (if the req comes from a browser) or stay with JSON (if called from Postman or an API web URL for example) without repeating the code. I'm trying to not create two endpoitns with 99% of the code being the same, the only difference being .then > res.status(200).json(testItems); vs .then > res.status(200).render('testPage', { testItems}).
For postman you could check the existence of postman-token in req.headers, then you could render accordingly, something like this:
req.headers['postman-token'] ? res.json({ /* json */ }) : render('view', {/ * json */});
If you want to go with checking postman token then you can use similar to method1.
if you want to check with query params in this case you can get json response or html even from browser for future use also and is not dependent on postman then use similar to method2 of the following example.
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
const port = 5000
app.get('/method1', (req, res) => {
const isJSONResp = req.headers['postman-token']
const resp = { status: "hello" }
if (isJSONResp) {
res.json(resp)
} else {
res.render('some.html', resp)
}
})
app.get('/method2', (req, res) => {
const params = req.params
const resp = { status: "hello" }
if (params.resp === 'json') {
res.json(resp)
} else {
res.render('some.html', resp)
}
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

KoaJS ctx.redirect() causing ERR_TOO_MANY_REDIRECTS in Chrome

I'm new to KoaJS. Playing a bit now. I'm trying to redirect all request to a particular URL using a middle-ware. This seems to product ERR_TOO_MANY_REDIRECTS in Chrome. I tried a lot to debug. Can't get what is wrong.
index.js
// App
const Koa = require('koa')
const app = new Koa()
// Parser
const bodyParser = require('koa-body')
app.use(bodyParser())
// Session
const session = require('koa-session')
app.keys = ['asdfasdf##$ASDf1#$#5rasdf']
app.use(session(app))
// THIS MIDDLEWARE
app.use(async (ctx, next) => {
ctx.session.user = '121' // This is all playground. No production stuff.
const s = ctx.session.user
if (s != '1213') {
ctx.redirect('/login')
}
await next()
})
// Router
const common = require('./routes')
app.use(common.routes())
// Server
app.listen(3000, () => { console.log('Listening on http://localhost:3000') })
routes.js
const Router = require('koa-router')
const router = new Router()
// const User = require('./user')
router.get('/', async ctx => {
ctx.body = 'Home Page'
})
router.get('/login', async ctx => {
ctx.body = 'Login Page'
})
module.exports = router
Consider your middleware:
app.use(async (ctx, next) => {
ctx.session.user = '121' // This is all playground. No production stuff.
const s = ctx.session.user
if (s != '1213') {
ctx.redirect('/login')
}
await next()
})
Because s != '1213' always evaluates to "true", ctx.redirect('/login') is executed for every request.
This will do two things:
set the HTTP response code to 302, telling the browser to perform a redirect
set the Location header to /login, telling the browser to location to redirect to
Considering that this happens for every request, you end up in a loop: a request to / is redirected to /login, which itself is redirected to /login, which is also redirected to /login, ad infinitum. At some point, the browser gives up and issues a ERR_TOO_MANY_REDIRECTS error.
FWIW, after calling ctx.redirect(), you typically end the request, for instance like this:
if (s != '1213') {
return ctx.redirect('/login')
}
In your case, you don't end the request, which means that it will be passed to the router.
To answer your comment, I assume you used this:
if (s != '1213') {
ctx.url = '/login';
}
You change the URL that the router will check to see which handler it should call. Sort of like an internal redirect, or a "rewrite": a request to / is handled internally as if it were a request for /login.
This is not something that you want though, because it may confuse the browser. The correct way is to issue a proper redirect, using ctx.redirect(), which will make the browser change the URL in the location bar and issue a new request.

How to read data from POST, test it with a condition and send a response back

I'm looking for an easy solution to front-end and back-end communication.
I want to write simple JS front-end client where a user can put a number between 1 an 10 000 to guess the number that server has generated.
So the client job is to send number that user is guessing. The server should test if secretNumber is higher or lower then that provided by the user and it should send back that info.
For now, my server only sends that secret number. I'm getting it inside my client console, so the connection is working.
My question is how should I modify my server code to read the number value from request, test it and then send the right response (example -> your number is higher than the secretNumber)?
This is my server:
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors());
app.use((request, response, next) => {
console.log(request.headers);
next();
});
app.use((request, response, next) => {
request.secretNumber = Math.floor(Math.random() * 10000) + 1;
next();
});
app.get('/', (request, response) => {
response.json({
secretNumber: request.secretNumber
});
});
app.listen(3001, () => console.log("Listening on 3001"));
Here is my front-end JS code (I'm using axios):
export function guessNumber(guessValue) {
return dispatch => {
dispatch({ type: GUESS_NUMBER });
axios
.post('/guess', {
isNumber: guessValue,
})
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
};
}
And I was here looking for answer, but maybe I'm to inexperiened and I need some real example...
First you need to persist the secretNumber between requests. At the moment you are generating a new value on each request.
Assuming just one client using the backend concurrently, you can do this by generating the secretNumber when the server starts and keep it in memory (assign it to a variable).
Then you can simply use route params to capture the client's guess:
app.get('/guess/:guess', (request, response) => {
const guess = params.guess;
// compare guess with secretNumber and respond accordingly
});
Alternatively you can use the request's body (https://expressjs.com/en/4x/api.html#req.body) instead of route params.

Emiting websocket message from routes

I'm trying to setup my server with websockets so that when I update something via my routes I can also emit a websocket message when something on that route is updated.
The idea is to save something to my Mongo db when someone hits the route /add-team-member for example then emit a message to everyone who is connected via websocket and is a part of whatever websocket room that corresponds with that team.
I've followed the documentation for socket.io to setup my app in the following way:
App.js
// there's a lot of code in here which sets what to use on my app but here's the important lines
const app = express();
const routes = require('./routes/index');
const sessionObj = {
secret: process.env.SECRET,
key: process.env.KEY,
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret : 'test',
cookie:{_expires : Number(process.env.COOKIETIME)}, // time im ms
}
app.use(session(sessionObj));
app.use(passport.initialize());
app.use(passport.session());
module.exports = {app,sessionObj};
start.js
const mongoose = require('mongoose');
const passportSocketIo = require("passport.socketio");
const cookieParser = require('cookie-parser');
// import environmental variables from our variables.env file
require('dotenv').config({ path: 'variables.env' });
// Connect to our Database and handle an bad connections
mongoose.connect(process.env.DATABASE);
// import mongo db models
require('./models/user');
require('./models/team');
// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const server = app.app.listen(app.app.get('port'), () => {
console.log(`Express running → PORT ${server.address().port}`);
});
const io = require('socket.io')(server);
io.set('authorization', passportSocketIo.authorize({
cookieParser: cookieParser,
key: app.sessionObj.key, // the name of the cookie where express/connect stores its session_id
secret: app.sessionObj.secret, // the session_secret to parse the cookie
store: app.sessionObj.store, // we NEED to use a sessionstore. no memorystore please
success: onAuthorizeSuccess, // *optional* callback on success - read more below
fail: onAuthorizeFail, // *optional* callback on fail/error - read more below
}));
function onAuthorizeSuccess(data, accept){}
function onAuthorizeFail(data, message, error, accept){}
io.on('connection', function(client) {
client.on('join', function(data) {
client.emit('messages',"server socket response!!");
});
client.on('getmessage', function(data) {
client.emit('messages',data);
});
});
My problem is that I have a lot of mongo DB save actions that are going on in my ./routes/index file and I would like to be able to emit message from my routes rather than from the end of start.js where socket.io is connected.
Is there any way that I could emit a websocket message from my ./routes/index file even though IO is setup further down the line in start.js?
for example something like this:
router.get('/add-team-member', (req, res) => {
// some io.emit action here
});
Maybe I need to move where i'm initializing the socket.io stuff but haven't been able to find any documentation on this or perhaps I can access socket.io from routes already somehow?
Thanks and appreciate the help, let me know if anything is unclear!
As mentioned above, io is in your global scope. If you do
router.get('/add-team-member', (req, res) => {
io.sockets.emit('AddTeamMember');
});
Then every client connected, if listening to that event AddTeamMember, will run it's associated .on function on their respective clients. This is probably the easiest solution, and unless you're expecting a huge wave of users without any plans of load balancing, this should be suitable for the time being.
Another alternative you can go:
socket.io lib has a rooms functionality where you can join and emit using the io object itself https://socket.io/docs/rooms-and-namespaces/ if you have a knack for this, it'd look something like this:
io.sockets.in('yourroom').broadcast('AddTeamMember');
This would essentially do the same thing as the top, only instead of broadcasting to every client, it'd only broadcast to those that are exclusive to that room. You'd have to basically figure out a way to get that users socket into the room //before// they made the get request, or in other words, make them exclusive. That way you can reduce the amount of load your server has to push out whenever that route request is made.
Lastly, if neither of the above options work for you, and you just absolutely have to send to that singular client when they initiate it, then it's going to get messy, because you have to have some sort of id to that person, and since you have no reference, you'd have to store all your sockets upon connection, and then make a comparison. I do not fully recommend something like this, because well, I haven't ever tested it, and don't know what type of repercussions could happen, but here is a jist of an idea I had:
app.set('trust proxy', true)
var SOCKETS = []
io.on('connection', function(client) {
SOCKETS.push(client);
client.on('join', function(data) {
client.emit('messages',"server socket response!!");
});
client.on('getmessage', function(data) {
client.emit('messages',data);
});
});
router.get('/add-team-member', (req, res) => {
for (let i=0; i< SOCKETS.length; i++){
if(SOCKETS[i].request.connection.remoteAddress == req.ip)
SOCKETS[i].emit('AddTeamMember');
}
});
Keep in mind, if you do go down this route, you're gonna need to maintain that array when users disconnect, and if you're doing session management, that's gonna get hairy really really quick.
Good luck, let us know your results.
Yes, it is possible, you just have to attach the instance of socket.io as long as you get a request on your server.
Looking to your file start.js you just have to replace your functions as:
// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const io = require('socket.io')(app.app);
const server = app.app.listen(app.app.get('port'), () => {
server.on('request', function(request, response){
request.io = io;
}
console.log(`Express running → PORT ${server.address().port}`);
});
now when you receive an event that you want to emit some message to the clients you can use your io instance from the request object.
router.get('/add-team-member', (req, res) => {
req.io.sockets.emit('addteammember', {member: 6});
//as you are doing a broadcast you just need broadcast msg
....
res.status(200)
res.end()
});
Doing that i also were able to integrate with test framework like mocha, and test the events emited too...
I did some integrations like that, and in my experience the last thing to do was emit the msg to instances in the socket.
As a good practice the very begining of middleware functions i had were doing data validation, data sanitization and cleaning data.
Here is my working example:
var app = require('../app');
var server = require('http').Server(app);
var io = require('socket.io')(server);
io.on('connection', function(client) {
client.emit('connected');
client.on('disconnect', function() {
console.log('disconnected', client.id);
});
});
server.on('request', function(request, response) {
request.io = io;
});
pg.initialize(app.config.DATABASEURL, function(err){
if(err){
throw err;
}
app.set('port', process.env.PORT || 3000);
var server1 = server.listen(app.get('port'), function(){
var host = 'localhost';
var port = server1.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
});
Your io is actually the socket object, you can emit events from this object to any specific user by -
io.to(userSocketId).emit('eventName', data);
Or you can broadcast by -
io.emit('eventName', data);
Just create require socket.io before using it :)
You can use emiter-adapter to emit data to client in other process/server. It use redis DB as backend for emitting messages.
I did something similar in the past, using namespaces.
Let's say your client connect to your server using "Frontend" as the namespace.
My solution was to create the instance of socket.io as a class in a separate file:
websockets/index.js
const socket = require('socket.io');
class websockets {
constructor(server) {
this.io = socket(server);
this.frontend = new Frontend(this.io);
this.io.use((socket, next) => {
// put here the logic to authorize your users..
// even better in a separate file :-)
next();
});
}
}
class Frontend {
constructor(io) {
this.nsp = io.of('/Frontend');
[ ... ]
}
}
module.exports = websockets;
Then in App.js
const app = require('express')();
const server = require('http').createServer(app);
const websockets = require('./websockets/index');
const WS = new websockets(server);
app.use('/', (req, res, next) => {
req.websocket = WS;
next();
}, require('./routes/index'));
[ ... ]
Finally, your routes can do:
routes/index.js
router.get('/add-team-member', (req, res) => {
req.websocket.frontend.nsp.emit('whatever', { ... });
[ ... ]
});

Categories

Resources