Caching in an app which consumes and serves an API - node.js

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.

Related

Location API not returning location id to server

I'm following an tutorial where i am supposed to consume an mongoose API using express and display location information to the view. However I am getting an 404 error on the front end everytime i click the button that calls the API request. The source code for this tutorial can be found here. https://github.com/cliveharber/gettingMean-2/tree/chapter-07
Here is the API request handler on the server side:
const getLocationInfo = (req, res, callback) => {
const path = `/api/locations/${req.params.locationid}`;
const requestOptions = {
url: `${apiOptions.server}${path}`,
method: 'GET',
json: {}
};
request(
requestOptions,
(err, {statusCode}, body) => {
const data = body;
if (statusCode === 200) {
data.coords = {
lng: body.coords[0],
lat: body.coords[1]
}
callback(req, res, data);
} else {
showError(req, res, statusCode);
}
}
);
};
const locationInfo = (req, res) => {
getLocationInfo(req, res,
(req, res, responseData) => renderDetailPage(req, res, responseData)
);
};
The Render handler of the page i am trying to reach:
const renderDetailPage = (req, res, location) => {
res.render('location-info',
{
title: location.name,
pageHeader: {
title: location.name,
},
sidebar: {
context: 'is on Loc8r because it has accessible wifi and space to sit down with your laptop and get some work done.',
callToAction: 'If you\'ve been and you like it - or if you don\'t - please leave a review to help other people just like you.'
},
location
}
);
};
This is the Mongoose API controller that retrieves the location data from the database:
const locationsReadOne = (req, res) => {
Loc
.findById(req.params.locationid)
.exec((err, location) => {
if (!location) {
return res
.status(404)
.json({
"message": "location not found"
});
} else if (err) {
return res
.status(404)
.json(err);
}
res
.status(200)
.json(location);
});
};
This is the express routing code:
const express = require('express');
const router = express.Router();
const ctrlLocations = require('../controllers/locations');
const ctrlOthers = require('../controllers/others');
router.get('/', ctrlLocations.homelist);
router.get('/locations/:location._id', ctrlLocations.locationInfo);
And finally this is a snippet of the Pug code that contains the button which calls the get request to retrieve the location id:
each location in locations
.card
.card-block
h4
a(href=`/locations/${location_id}`)= location.name
+outputRating(location.rating)
span.badge.badge-pill.badge-default.float-right= location.distance
p.address= location.address
.facilities
each facility in location.facilities
span.badge.badge-warning= facility
Let me know if i need to provide additional information. Appreciate any help.

Time Out in Api rest Express Nodejs

I want to put my api in time out. I tried to put it in a "global" way when I create the server but the answer only says that no data was returned, not a 504 error. I also have the option to place it for each route, I think the time out should go in the controller of my app .
function initialize() {
return new Promise((resolve, reject) => {
const app = express();
httpServer = http.createServer(app);
app.use(morgan('combined'));
app.use('/proyecto', router_proyecto);
app.use('/tramitacion',router_tramitacion);
httpServer.listen(webServerConfig.port, err => {
if (err) {
reject(err);
return;
}
//httpServer.timeout = 500;
console.log('Servidor web escuchando en puerto:'+webServerConfig.port);
resolve();
});
});
}
exaple of a route:
router_proyecto.route('/getProyecto/:proyid?')
.get(proyecto.getProyecto);
Controller(Time out should go here):
async function getProyecto(req, res, next) {
try {
const context = {};
context.proyid = parseInt(req.params.proyid, 10);
const rows = await proyectos.getProyecto_BD(context);
if (rows.length >0) {
res.status(200).json(rows);
} else {
res.status(404).end();
}
} catch (err) {
next(err);
}
}
module.exports.getProyecto = getProyecto;
What does "time out" mean to you? Because a a timeout is a well defined term in Javascript and your server should definitely not be using that. If you want your server to simply serve some content with a specific HTTP code, depending on some global flag, then you were on the right track except you need to write it as middleware.
let lock = Date.now();
function serveLockContent(req, res, next) {
if (lock <= Date.now()) {
return next();
}
res.status(503).write('Server unavailable');
}
...
app.use(serveLockContent);
...
app.post('/lock/:seconds', verifyAuth, otherMiddlerware, (req, res) => {
lock = Date.now() + (parseInt(req.params.howlong) || 0) * 1000;
res.write('locked');
});

node.js modify response and store request at the same time

So what I'm trying to do is to manualy overwrite response when there is a request to my json-server. I'm fine with that, but I dont know how to add a bit to also store the original request in database.
Here is my code (i'm sending as response the name from request and static uuid)
The next(); in if step is failing complaining it cannot setup headers.
module.exports = (req, res, next) => {
if (req.path == "/business") {
res.status(201);
res.jsonp({
id: "a23e1b13-cf69-461c-aa8a-a0eb99e41350",
name: req.body['name'],
revision: "1"
});
next();
}
else {
next();
}
}
If you want to pass data from middleware to any routes you can assign value with req object rather than res object.
app.use(function(req.res)) {
req.custom_data = {
id: "a23e1b13-cf69-461c-aa8a-a0eb99e41350",
name: req.body['name'],
revision: "1"
};
next();
}
From other location you can do like this.
module.exports = (req, res, next) => {
res.jsonp(req.custom_data)//something like this
}
It might help you.
You are right.
For example, we cannot:
server.js
const jsonServer = require("json-server")
const server = jsonServer.create()
const router = jsonServer.router("db.json")
server.use(jsonServer.defaults())
server.use(jsonServer.bodyParser)
server.use((req, res, next) => {
if (req.method === "POST" && req.url === "/business") {
res.status(201)
res.jsonp({
id: "a23e1b13-cf69-461c-aa8a-a0eb99e41350",
name: req.body["name"],
revision: "1"
})
}
next()
})
server.use(router)
server.listen(3000, () => {
console.log("JSON Server is running")
})
Or we get: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client when we run node server.js, because we have already set and sent the headers when we call res.jsonp, so we cannot do this again by calling server.use(router).
We have a couple options.
Option 1.
server.js
const jsonServer = require("json-server")
const server = jsonServer.create()
const router = jsonServer.router("db.json")
server.use(jsonServer.defaults())
server.use(router)
server.listen(3000, () => {
console.log("JSON Server is running")
})
db.json
{
"business": []
}
Now we can POST to our http://localhost:3000/business endpoint and save our request body to our database. However, we cannot amend the 201 status, nor the request body to something else.
Option 2.
server.js
const jsonServer = require("json-server")
const server = jsonServer.create()
server.use(jsonServer.defaults())
server.use(jsonServer.bodyParser)
server.post("/business", (req, res, next) => {
res.status(201)
res.jsonp({
id: "a23e1b13-cf69-461c-aa8a-a0eb99e41350",
name: req.body["name"],
revision: "1"
})
next()
})
server.listen(3000, () => {
console.log("JSON Server is running")
})
Now we get the correct response, but we can no longer save to our database business key, because we are calling server.post("/business"...
You could however use this in your own code and save it somewhere else (or write to your own database), as this response will be returned from a Promise.

Access model in custom route boot script in loopback

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
}
}

Nodejs best practice encapsulating send middleware

Ok so I am currently learning more node.js and decided to try out some basic middleware in a small api I created. I was wondering how I would wrap a successfull request. This is my approach.
Example Controller
exports.getTask = async function (req, res, next) {
try {
const task = await db.Task.findOne(
{
where: {
id: req.params.taskId,
userId: req.params.userId
}
});
if (task) {
req.locals.data = task;
res.status(httpStatus.OK);
next();
}
res.status(httpStatus.NOT_FOUND);
next();
} catch (err) {
next(err);
}
};
Middleware
exports.success = function(req, res, next) {
const success = res.statusCode < 400;
const successResponse = {
timestamp: new Date().toUTCString(),
success: success,
status: res.statusCode
};
if (success) {
successResponse.data = req.locals.data;
}
res.send(successResponse);
next();
};
I dont think its very good having to set req.locals.data for every requst and then calling next res.status(status) maybe I just approached the situation the wrong way?
How could you make this better?
In this case, probably using the express middleware concept (calling next()) will be an overkill.
I'd approach this by creating an abstraction for the success path. Consider something like this:
const resWithSuccess = (req, res, data) => {
res.json({
data: data,
timestamp: new Date().toUTCString(),
// success: res.statusCode < 400, // --> actually you don't need this,
// since it will always be true
// status: res.statusCode // --> or whatever else "meta" info you need
});
};
Then, as soon as you need to respond with success, go for it:
exports.getTask = async function (req, res, next) {
// .... bla bla
if (task) {
resWithSuccess(tank);
}
};
PS: ... and you can use the express middleware concept (calling next()) for the error path.

Resources