Access model in custom route boot script in loopback - node.js

I want to add custom Express route using boot script.
I want to check if the apiKey which is in the query string exists in our model.
So, I want to access the model.
But, it seems that the script doesnt get executed since I don't get the model (maybe due to the asynchronous thing).
So, How to do this?
P.S. this is my code
app.post('/webhook', line.middleware(config), (req, res) => {
if (req.query.apiKey) {
const Store = app.models.Store;
Store.find({where: {apiKey: req.query.apiKey}, limit: 1}, function(err, store) {
if (err || store == null) {
res.status(401).end();
}
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
.catch((err) => {
console.error(err);
res.status(500).end();
});
});
}

// server/boot/routes.js
const bodyParser = require('body-parser')
module.exports = function (app) {
const router = app.loopback.Router()
router.post('/webhook', bodyParser.raw({type: '*/*'}), function(req, res) {
// here you have full access to your models
app.models
}
}

Related

Caching in an app which consumes and serves an API

I don't know if this is the best place to ask but.
I am building a weather app which consumes an api using axios and then serves it using express. I wanted to know where should I add caching to improve the speed of the api? Would it be at the axios layer when I am consuming or at the express layer when I am serving.
Below is my code for a little context
import { weatherApiKey } from 'config';
import axios from 'axios';
const forecast = (location, service) => {
console.log('inside api calling location: ', location);
axios.get(`http://api.openweathermap.org/data/2.5/weather?q=${location}&appid=${weatherApiKey}`)
.then(res => {
service(undefined, res.data)
})
.catch(err => {
service('Error calling weather API');
})
}
module.exports = forecast;
I am then serving the consumed api via the following.
app.get('/weather', (req, res) => {
const locale = req.query.locale;
if(!locale) {
return res.send({
error: 'Please provide valid locale'
})
}
foreCast(locale, (err, weatherData) => {
if(err) {
console.log('error in calling weather API')
res.send({err});
}
console.log('returning weather data', weatherData)
res.send({weatherData})
});
})
Yes, generally there are lots of forms and layers to cache at. Given the fact that you're creating an API, I would expect some caching to be applied as close to the consumer as possible. This could be at the CDN level. However, a quick easy answer would be to add something as a cacheable middleware for your express app.
There are many approaches to populating and invalidating caches, and you take care to plan these specifically for your use case. Try not to prematurely optimise using caching where you can. It introduces complexity, dependencies and can contribute towards hard-to-debug problems when there are lots of caching layers being applied.
But a simple example would be something like:
'use strict'
var express = require('express');
var app = express();
var mcache = require('memory-cache');
app.set('view engine', 'jade');
var cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
app.get('/', cache(10), (req, res) => {
setTimeout(() => {
res.render('index', { title: 'Hey', message: 'Hello there', date: new Date()})
}, 5000) //setTimeout was used to simulate a slow processing request
})
app.get('/user/:id', cache(10), (req, res) => {
setTimeout(() => {
if (req.params.id == 1) {
res.json({ id: 1, name: "John"})
} else if (req.params.id == 2) {
res.json({ id: 2, name: "Bob"})
} else if (req.params.id == 3) {
res.json({ id: 3, name: "Stuart"})
}
}, 3000) //setTimeout was used to simulate a slow processing request
})
app.use((req, res) => {
res.status(404).send('') //not found
})
app.listen(process.env.PORT, function () {
console.log(`Example app listening on port ${process.env.PORT}!`)
})
Note: This is taken from https://medium.com/the-node-js-collection/simple-server-side-cache-for-express-js-with-node-js-45ff296ca0f0 using the memory-cache npm package.

KOA-Views Suddenly Stop Working after First Route

The full code for this project can be found here: https://github.com/AlexMercedCoder/KoaStarterBlog
The Video series I was making with this can be found here so you can see how things worked at different stages: https://www.youtube.com/watch?v=8_aWw7lfKKI&list=PLY6oTPmKnKbbF4t0Y9DcUVYi7f4kix7Qj
I actually illustrate the problem in the beginning part of this video and walk through the construction of all my routes: https://youtu.be/ltAxokJsaWE
So I built out this basic blog app using KoaJS. When I initially run the index.js the behavior is as follows.
- Root Route Works
- Create route works
- Admin route works
- The Delete Button works on the admin page
- The edit route just doesn't not (works on and off even though the code is just like the other routes)
Bigger Problem: After submitting a form by hitting the delete button or by creating a new post all the routes except the create route stop working and instead they just return (not found). At first I thought this was a problem being caused by ctx.redirect because they would always fail and be followed by the broken routes but while rendering a complete page seems to work initially typing root or admin route into the browser after form submission still breaks.
*Update: this happens after going to any route, every route works if its the first route accessed but then all other routes except create stop working afterwards. It's as if the first route creates some sort of limbo. The weird thing is the router still console logs everything the route should do up until either a ctx.render, ctx.redirect or ctx.body is to be returned.
below is the index.js code!
///////////////////////
//Initializing Environment Variables and other middleware
//npm i dotenv
//npm i koa-methodoverride
///////////////////////
require('dotenv').config();
const override = require('koa-methodoverride');
const parser = require('koa-bodyparser');
////////////////////////
//Connecting the DB
//npm i mongoose
////////////////////////
const mongoose = require('mongoose');
const db = mongoose.connection;
const host = process.env.host;
const dbupdate = {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false};
mongoose.connect(host, dbupdate);
db.on('error', (err) => console.log('Error, DB Not connected'));
db.on('connected', () => console.log ('connected to mongo'));
db.on('diconnected', () => console.log ('Mongo is disconnected'));
db.on('open', () =>console.log ('Connection Made!'));
////////////////////////////
//Model Schema
///////////////////////////
const Blog = require('./model/blog.js');
///////////////////////
//Create Our Server Object
//npm i koa
///////////////////////
const koa = require('koa');
const server = new koa();
//////////////////////////
//Create Our Static Folder
//npm i koa-static
//////////////////////////
const static = require('koa-static');
//////////////////////////
//Creating Our Router
//npm i koa-router
//////////////////////////
const Router = require('koa-router');
const route = new Router();
/////////////////////////////////
//initializing views
//npm i koa-views
//npm i nunjucks
////////////////////////////////;
const views = require('koa-views');
const nunj = require('nunjucks');
nunj.configure('./views', {autoescape: true});
///////////////////////////
//routes
// route.get - route.post - route.patch - post.put - route.delete
///////////////////////////
//root route
route.get('/', (ctx, next) => {
console.log('connected to root route');
return Blog.find({}, (error, results) => {
console.log(results)
ctx.render('index.njk', {
posts: results
});
});
});
//admin route
route.get('/admin', (ctx, next) => {
console.log('connected to admin route');
return Blog.find({}, (error, results) => {
console.log(results)
ctx.render('admin.njk', {
posts: results
});
});
});
//delete route
route.delete('/delete/:id', (ctx, next) => {
console.log('connected to delete route');
console.log(ctx.request.body)
if (ctx.request.body.pw === process.env.pw){
Blog.findByIdAndRemove(ctx.params.id, (err, result) => {
})
}else{
console.log('wrong password')
}
return ctx.render('complete.njk');
});
//edit route
route.get('/edit/:id', (ctx, next) => {
console.log('connected to edit route');
return Blog.findById(ctx.params.id, (err, results) => {
console.log(results);
ctx.render('edit.njk', {
post: results
});
});
});
route.put('/edit/:id', (ctx, next) => {
console.log('editing a post');
console.log(ctx.request.body)
if (ctx.request.body.pw === process.env.pw){
Blog.findByIdAndUpdate(ctx.params.id, ctx.request.body, {new:True}, (err, result) => {
console.log(result);
})
}else{
console.log('wrong password');
}
return ctx.render('complete.njk');
});
//create route
route.get('/create', (ctx, next) => {
console.log('connected to create route');
return ctx.render('create.njk');
});
route.post('/create', (ctx, next) => {
console.log('creating a post');
console.log(ctx.request.body)
if (ctx.request.body.pw === process.env.pw){
Blog.create(ctx.request.body, (err, result) => {
console.log(result);
})
}else{
console.log('wrong password');
;
}
return ctx.render('complete.njk');
});
////////////////////////////
//Async Functions
////////////////////////////
// const getPosts = async (query) => {
// const data = await Blog.find({query})
// return data;
// };
////////////////////////
//Middleware
/////////////////////////
server.use(parser());
server.use(override('_method'))
server.use(views('./views', {map: {njk: 'nunjucks'}}));
server.use(route.routes());
server.use(static('./public'));
/////////////////////
//Our Listener on Port 1985
/////////////////////
server.listen(1985,'localhost',() => console.log('Listening on port 1985'));
It looks like a http-server fault. Try to add an error handler.
Also I recomment to change error handling in code e.g.
route.put('/edit/:id', (ctx, next) => {
if (ctx.request.body.pw === process.env.pw){
Blog.findByIdAndUpdate(ctx.params.id, ctx.request.body, {new:True}, (err, result) => {
console.log(result);
})
} else {
console.log('wrong password');
}
return ctx.render('complete.njk');
});
replace by
route.put('/edit/:id', (ctx, next) => {
// Early stop to avoid brace ladder
if (ctx.request.body.pw != process.env.pw)
ctx.throw('Env. wrong password'); // Pass request to error-handler
Blog.findByIdAndUpdate(ctx.params.id, ctx.request.body, {new:True}, (err, result) => {
if (err)
ctx.throw('Db wrong password'); // or throw Error('Db wrong password');
ctx.render('complete.njk');
});
}
...
server.use(route.routes());
server.use(static('./public'));
// Error handler: catch 'wrong password'-error here.
app.on('error', (err, ctx) => {
console.error(err);
ctx.render('error.njk');
});
P.S. I use Express, not Koa. So maybe I made some mistakes.
Perhaps ctx.render requires async-await framing like below
route.get('/', async (ctx, next) => {
console.log('connected to root route');
console.log(ctx);
return Blog.find({}, (error, results) => {
console.log(results)
await ctx.render('index', {
posts: results
});
console.log('the view was rendered')
});
});
I downloaded your git and made this changes. And it works :)
I never figured out was causing this behavior but I was able to fix and committed the working code to the github repository. After several attempts I could not get the koa-views library to work right so I gave up on it and switched to koa-nunjucks-2 and initialized it exactly as it says in their docs.
Unfortunately... the same problem kept happening till I changed the path to relative path with using path.join then koa-nunjucks-2 worked perfectly!
Why, or what was happening, I still have no idea. It probably has to do with unresolved promises under the hood but I'm not really sure.

How to pass data from model to router in Node.js

I'm new to Node.js and am trying to pass some data from my DB model back to the router but I'm unable to find a solution. I have the following route file that makes a call to model:
Route file:
var express = require('express');
var router = express.Router();
var db = require('../db');
var customers = require('../models/customers');
db.connect(function(err) {
if (err) {
console.log('Unable to connect to MySQL.')
process.exit(1)
}
});
router.post('/', function(req, res) {
customers.checkPassword(req.body.cust_id, req.body.password);
res.sendStatus(200);
});
Model file:
var db = require('../db.js');
module.exports.checkPassword = function(cust_id, password) {
var sql = "SELECT Password FROM Shop.customers WHERE ID =" + cust_id;
db.get().query(sql, function (err, res, fields) {
result = res[0].Password;
if (err) throw err
});
};
My question is: how could I pass the queried result Password back to my Route file so that I can do this:
console.log('Password is', result);
I appreciate any help on this.
I'd use a promise
Model file
module.exports.checkPassword = function(cust_id, password) {
return new Promise(function(resolve, reject) {
const sql = "SELECT Password FROM Shop.customers WHERE ID =" + cust_id;
db.get().query(sql, function (err, res, fields) {
if (err) return reject(err)
result = res[0].Password;
return resolve(result);
});
});
};
Route file
var express = require('express');
var router = express.Router();
var db = require('../db');
var customers = require('../models/customers');
db.connect(function(err) {
if (err) {
console.log('Unable to connect to MySQL.')
process.exit(1)
}
});
router.post('/', function(req, res) {
customers.checkPassword(req.body.cust_id, req.body.password)
.then((result) => {
// DO: something with result
res.status(200).send();
})
.catch(console.log); // TODO: Handle errors
});
With async/await
router.post('/', async function(req, res) {
try {
const result = await customers.checkPassword(req.body.cust_id, req.body.password)
// DO: something with the result
} catch (e) {
console.log(e); // TODO: handle errors
} finally {
res.status(200).send();
}
});
I assume console.log('Password is', result); is just for test prupose, obviously you should never log a password! Also I suggest to move the callbabck of the routes do a different module, to improve code redability.
You might also find useful promise-module module on npm, basically a promise wrapper around mysql.
You can delegate the credential control to another function in your DB file where you can decide on what kind of data you want to return on success and failure to find such data. Then you can access it from where you are calling it.

Calling an API endpoint from within another route in Node / Express

I have myRoute.js with a route (GET) defined and I want to call an api endpoint from another route (api.js), and I'm not sure what the right way to do this is. The api.js route is working properly (image and code below).
api.js
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
works as expected:
myRoute.js
I would like when a user goes to localhost:3000/USER_ID that the route definition gets information from the api. Psuedo code below (someFunction).
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
let fromApi = someFunction(`localhost:3000/getAllGroups/${uid}`); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Not sure if i understand you correct but anyway i will try to help. So you have an api like
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
If you would like to reuse it you can extract a function from the code above like so:
async function getAllGroupsByUserId(uid){
const result = [];
try{
const data = await db.getAllGroups(uid);
for (i in data) {
result.push(data[i].groupname);
};
return result;
}
catch(e) {
return e;
}
}
And then reuse it in your api & anywhere you want:
router.get('/getGroups/:uid', async function(req, res, next) {
const uid = req.params.uid;
const groups = await getAllGroupsByUserId(uid);
res.status(200).send(groups);
})
Same you can do in your another route:
router.get('/:uid', async function(req, res, next) {
const uid = req.params.uid;
const fromApi = await getAllGroupsByUserId(uid); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Seems like pretty clear :)
I would use fetch for this. You can replace someFunction with fetch, and then put the res.render code in a .then(). So, you would get this:
const fetch = require("node-fetch");
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}').then(res => res.json()).then(function(data) {
returned = data.json();
console.log(returned); //expecting array
res.render('./personal/index.jade', {JSON.stringify(returned)});
});
});
A more robust way with error handling would be to write something like this:
const fetch = require("node-fetch");
function handleErrors(response) {
if(!response.ok) {
throw new Error("Request failed " + response.statusText);
}
return response;
}
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}')
.then(handleErrors)
.then(res => res.json())
.then(function(data) {
console.log(data) ; //expecting array
res.render('./personal/index.jade', {JSON.stringify(data)});
})
.catch(function(err) {
// handle the error here
})
});
The ideal way would be to abstract your code into a method so you aren't calling yourself, as The Reason said. However, if you really want to call yourself, this will work.

Express: Show value from external request as response

I have the following function where I am using the cryptocompare npm package:
getPrice: function(coin){
cc.price(coin, 'USD')
.then(prices => {
console.log(prices);
return prices;
}).catch(console.error)
}
// https://github.com/markusdanek/crypto-api/blob/master/server/helper/cryptocompare.js
Now I want to set up an Express server to open http://localhost:9000/current and to display the current "price".
So I have my controller which looks like this:
module.exports = {
getCurrentPrice: function(req, res, next) {
getPrice('ETH', function(price);
}
};
// https://github.com/markusdanek/crypto-api/blob/master/server/controllers/CryptoController.jshttps://github.com/markusdanek/crypto-api/blob/master/server/controllers/CryptoController.js
My route:
var controllers = require('../controllers'),
app = require('express').Router();
module.exports = function(app) {
app.get('/current', controllers.crypto.getCurrentPrice);
};
When I open now http://localhost:9000/current I only get the current price in my console, but not in my browser.
How can I also set the response to the value?
I tried this but failed:
module.exports = {
getCurrentPrice: function(req, res, next) {
getPrice('ETH', function(price){
res.status(200).json(price);
});
}
};
I guess thats the wrong way to call a callback.. do I have to modify my helper function or anything else?
My project is also on Github for further references: https://github.com/markusdanek/crypto-api
below may help you
module.exports = {
getCurrentPrice: function(req, res, next) {
cc.price('ETH', 'USD')
.then(prices => {
console.log(prices);
res.json(prices)
})
.catch(err=>{
console.error(err)
return next(err);
})
}
};

Resources