Pass value from middleware to view in Koa 2 - node.js

I am trying to get var value from my Koa 2 middleware to display in my pug template (or other).
For example in koa-sessions I have:
app.use(ctx => {
// ignore favicon
if (ctx.path === '/favicon.ico') return;
let n = ctx.session.views || 0;
ctx.session.views = ++n; // how can I use this?
ctx.body = n + ' views'; // works, but in body directly
ctx.state.views = n + ' views'; // not working
});
Another example, with response time:
app.use(async (ctx, next) => {
const start = Date.now();
ctx.state.start = start
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); // this shows response
ctx.state.ms = await ms>0 // I have no idea what I'm doing :)
});
As per original instruction this works, but instead of using body/console, I would like to use it as template variable, so in my router/controller I would have:
...
return ctx.render("posts/index", {
title: 'Posts',
posts: posts,
ms: ctx.state.ms,
views: ctx.session.views // or views: ctx.state.views
});
None of this works. Is it related to async/await, so it does not get the value in time or it is some syntactic issue? Please be gentle as I am new in this. :)

You need to call next() in your "session" middleware, same way as in the "response time" example.
Like that:
app.use((ctx, next) => {
let n = ctx.session.views || 0;
ctx.session.views = ++n;
next();
});
app.use(ctx => {
ctx.body = 'Hello ' + ctx.session.views;
// or you can return rendering result here
});
For more info take a look at the Cascading section of their documentation

Related

Prevent multiple callback error when client send multiple requests

I am building my first web application with node v12.18.1 and express v4.17.1. Since the start of development, I have the same error on all routes: when i quickly click a link multiple times the server crashes with this error: screenshot of the error. It can be fixed in the front-end by disabling events on user input after a click, but I prefer to know what is wrong with my code.
Route of the index page :
let express = require('express');
let router = express.Router();
let controller_index = require("../controller/controller_index.js")
router.get('/', controller_index.get_index);
router.get('/rubrique/:category', controller_index.get_category);
module.exports = router;
Controller of the index page :
const Query = require("../lib/dbs.js");
const ObjectId = require('mongodb').ObjectId;
const utils = require('../lib/utils');
exports.get_index = async (req, res, next) => {
try {
let user = await Query.findOne("users", "_id", ObjectId(req.user_id));
let notification;
if (user) {
notification = await Query.findSortToArray("notifications", "for_user", ObjectId(user._id));
notification.count = notification.filter((notif => !notif.hasOwnProperty("readedAt"))).length;
}
if (!req.query.q) {
let page = req.query.page > 1 ? (req.query.page * 10) - 10 : req.query.page <= 0 ? undefined : 0;
let current_page = req.query.page ? Number(req.query.page) : 1;
let [countDocuments, docs] = await Promise.all([Query.countAll("articles"), Query.findAll(page)]);
let nb_pages = Math.ceil(countDocuments / 10);
if (!docs.length && current_page !== 1) {
next();
}
else {
docs = await documents_processing(docs, user, req);
res.render("../views/index.pug", { docs: docs, user: user, notification: notification, nb_pages: nb_pages, current_page: current_page })
};
}
else if (req.query.q) {
let page = req.query.page > 1 ? (req.query.page * 10) - 10 : req.query.page <= 0 ? undefined : 0;
let current_page = req.query.page ? Number(req.query.page) : 1;
let [countDocuments, docs] = await Promise.all([Query.countAllBySearch("articles", req.query.q), Query.findAllBySearch(req.query.q, page)]);
let nb_pages = Math.ceil(countDocuments / 10);
if (!docs.length && current_page !== 1) {
next();
}
else {
docs = await documents_processing(docs, user, req);
res.render("../views/index.pug", { docs: docs, search: req.query.q, user: user, notification: notification, nb_pages: nb_pages, current_page: current_page })
};
};
}
catch (err) {
console.error(err);
return next(err);
};
};
Example of static function of the query object :
const connect = require("../index.js")
module.exports = class Query {
static async findOne(collection, field, item) {
const result = await connect.client.db("blog_db").collection(collection).findOne({ [`${field}`]: item })
return result;
};
static async findOneAndUpdateOrInsertOnUser(collection, field, itemToSearch, updateItem) {
const result = await connect.client.db("blog_db").collection(collection).findOneAndUpdate({ [`${field}`]: itemToSearch }, { $set: updateItem }, { upsert: true, returnOriginal: false });
return result;
};
static async findSortToArray(collection, field, item) {
const results = await connect.client.db("blog_db").collection(collection).find({ [`${field}`]: item }).sort({ date: -1 }).toArray()
return results;
};
};
I'm fairly new to programming so any advice is welcome, thank you in advance!
----- EDIT -----
Kind of solution :
I have found people who have talked about this error on node v12 and newer, with a downgrade to v10 the issue was resolved without any clear explanation yet.
The error in the screenshot you've shared shows that the error is "Callback called multiple times", but I don't see anywhere obvious in the code you've shared where this is happening. As you're saying this bug only happens when multiple requests are made rapidly one after the other, it suggests there might be a global variable which is being shared between requests, which is something that should be avoided.
To debug the error you're seeing I would recommend commenting out all of the code in the get_index function and gradually uncommenting it in small chunks until you see the error happen again. You will probably want to do the same with the code that is called by the get_index controller function e.g. documents_processing, as the issue might possibly lie there.
Express only support callback-style, and you're using async function to handle the logic.
Basically, with async function, you call a function without waiting for it to resolve the logics. Hence, it creates too many callbacks in the event loop when that route has a large amount of concurrent coming requests.
function asyncWrapper(fn) {
return (req, res, next) => {
return Promise.resolve(fn(req))
.then((result) => res.send(result))
.catch((err) => next(err))
}
};
router.get('/', asyncWrapper(controller_index.get_index));

:ERR_EMPTY_RESPONSE after two minutes timeout -axios-post - nodejs

I'm trying do make an upload of a file in a nodejs / react application.
The upload of the file work well, but I have some issues when I want to have the response of my await.post() request. When I upload the file I have made a spinner to show that the file is uploading, and I want this spinner to stop when the upload is finish and then display a message to inform the user that the loading is finish.
I explain the problems, because it's a little bit complicated.
When i work in local :
After two minutes (I timed it to understand), the console of my browser display the error message net::ERR_EMPTY_RESPONSE.
But in server side everything continue and the request ended without a hitch. But in front side my spinner never stop turning even when the request is over and when my post request is sending the res message to my client side.
I thought this was about the timeout of my server, so I've done a lot of test, by putting setTimeout in my server :
var server = app.listen(PORT, () =>
console.log(`Server started on port ${PORT}`)
);
server.timeout = 3840000;
And also in my route request :
router.post('/', async (req, res) => {
req.setTimeout(3600000);
And even i try to change the timeout of my axios.post function in the client side :
const res = await axios.post('/api/students', newFile, {
timeout: 3600000,
});
But nothing handle my problem.
And what is strange is that when the application is hosted, the problem is different!
In fact there are no error message but after 1 minutes the spinner stop with no reason.
I have made a lot of research but nothing answer my problem, it's been a few day now that i'm on it and i don't understand why. I thought maybe it's a proxy problem or a browser timeout, but I don't know...
If you have even a small lead, it could help me a lot! Thank you for your help !
UPDATE :
The code of my /api/student route
const express = require('express');
const router = express.Router();
const fileUpload = require('express-fileupload');
const Student = require('../../models/Student');
const CSVToJSON = require('csvtojson');
const utf8 = require('utf8');
var accents = require('remove-accents');
const NodeGeocoder = require('node-geocoder');
const sleep = require('util').promisify(setTimeout);
let msg = 'Etudiants ajoutés à la base de données';
//The column that we want to keep in the database
const goodColumn = [
'Civilite',
'Nom_patronymique',
'Prenom',
'Date_naissance',
'No_etudiant',
'Libelle_nationalite',
'Telephone_portable',
'Mailum',
'Adresse_fixe_postal',
'Adresse_fixe_ville',
'Libelle_etape',
];
// Set up the environnement for geocoding the adress
const options = {
provider: 'openstreetmap',
};
const geoCoder = NodeGeocoder(options);
//FUNCTION TO VERIFY IF A STRING HAS A NUMBER IN IT
function hasNumber(myString) {
return /\d/.test(myString);
}
router.post('/', (req, res, next) => {
// This should be BEFORE `fileUpload`
req.setTimeout(0);
next();
});
router.use(fileUpload());
//#route POST api/students
//#desc Fill the database with the json information
//#access Public
router.post('/', async (req, res, next) => {
//FORMER FILES ROUTES
//take the information
const buf = Buffer.from(req.files.file.data).toString();
//CONVERSION CSV STRING TO JSON
CSVToJSON()
.fromString(buf)
.then((source) => {
for (let i = 0; i < source.length; i++) {
for (let j = 0; j < Object.keys(source[i]).length; j++) {
const columnName = Object.keys(source[i]);
columnName.forEach((element) => {
if (!goodColumn.includes(element)) {
delete source[i][element];
}
if (element == 'Libelle_etape') {
const str = source[i]['Libelle_etape'];
const result = accents.remove(utf8.decode(str));
source[i]['Libelle_etape'] = result;
}
});
}
}
data = JSON.stringify(source);
datajson = JSON.parse(data);
//USE THE FUNCTION TO PUT THE DATA IN THE DB
insertIntoDataBase(datajson);
});
// CLEAR TABLE BEFORE ADD NEW STUDENTS FROM FILE
Student.deleteMany({}, function (err) {});
//ROUTES STUDENTS - FUNCTION TO PUT THE JSON DATA IN THE DATABASE
async function insertIntoDataBase(jsonString) {
for (let i = 0; i < jsonString.length; i++) {
console.log(`boucle ${i}`);
try {
//READ DATA FROM DE CSV FILE (already convert into json data) AND PUT IT INTO VARIABLES
let {
Civilite,
Nom_patronymique,
Prenom,
Date_naissance,
No_etudiant,
Libelle_nationalite,
Telephone_portable,
Mailum,
Adresse_fixe_postal,
Adresse_fixe_ville,
Libelle_etape,
} = jsonString[i];
console.log(Nom_patronymique + ' ' + Prenom);
// VERIFICATION VILLE STRING FORMAT ( AVOIR NUMBER OR ..EME)
if (hasNumber(Adresse_fixe_ville)) {
Adresse_fixe_ville = Adresse_fixe_ville.replace(/[0-9]/g, '');
if (Adresse_fixe_ville.endsWith(' EME')) {
Adresse_fixe_ville = Adresse_fixe_ville.substring(
0,
Adresse_fixe_ville.length - 4
);
}
}
//VERIFICATION OF THE PHONE NUMBER - if empty attributes a default value
if (Telephone_portable !== undefined && Telephone_portable.length < 1) {
Telephone_portable = '0000000000';
}
// GEOCODING THE ADDRESS TO CONVERT INTO LATITUDE AND LONGITUDE
geoCoder
.geocode({
city: Adresse_fixe_ville,
zipcode: Adresse_fixe_postal,
})
.then(async (res) => {
//TEST
var Latitude;
var Longitude;
if (res[0] !== undefined) {
Latitude = res[0].latitude;
Longitude = res[0].longitude;
} else {
console.log(Adresse_fixe_ville);
Latitude = 0.0;
Longitude = 0.0;
}
//CREATE A STUDENT WITH THE INFO + LAT AND LONG
student = new Student({
Civilite,
Nom_patronymique,
Prenom,
Date_naissance,
No_etudiant,
Libelle_nationalite,
Telephone_portable,
Mailum,
Adresse_fixe_postal,
Adresse_fixe_ville,
Libelle_etape,
Latitude,
Longitude,
});
//VERIFICATION IF ALL THE ATTRIBUTE OF THE STUDENT ARE OK - IF NOT : undefined
if (
!(
student.Civilite === undefined ||
student.Nom_patronymique === undefined ||
student.Prenom === undefined ||
student.Date_naissance === undefined ||
student.No_etudiant === undefined ||
student.Libelle_nationalite === undefined ||
student.Telephone_portable === undefined ||
student.Mailum === undefined ||
student.Adresse_fixe_postal === undefined ||
student.Adresse_fixe_ville === undefined ||
student.Libelle_etape === undefined
)
) {
//SAVE THE STUDENT IN THE DATABASE
await student.save();
} else {
res.status(500);
msg =
'Le fichier csv téléchargé est au mauvais format de données';
}
})
.catch((err) => {
console.log(err);
});
} catch (err) {
console.log(err.message);
res.status(500);
msg =
'Erreur serveur, veuillez réessayer avec un fichier csv au bon format';
return;
}
//WAIT FOR GEOCODER - 1,2 second
await sleep(1200);
}
//COUNT A MANY STUDENT WE HAVE IN THE DATABASE
Student.find().exec(function (err, results) {
var count = results.length;
res.json({ msg: msg, count: count });
});
}
});
//#route GET api/students
//#desc Return all the students in the database (all information / attributes)
//#access Public
router.get('/', function (req, res, next) {
Student.find(function (err, students) {
if (err) {
res.send(err);
}
res.json(students);
});
});
module.exports = router;
In Node < 13.0.0 the default socket timeout is 2 minutes, so after two minutes the socket will be closed and you'll get ERR_EMPTY_RESPONSE on the client-side since the socket was closed without allowing you to respond to that request.
From Node 13.0.0 the default timeout was set to 0 (no timeout), and that's maybe the reason it works when you deployed the application, your server may be running Node >= 13.0.0.
If after using req.setTimeout with a value greater to 2 minutes, and you still have that issue, it may be caused because you're not ending the request correctly or you have used req.setTimeout in the wrong place.
It a huge file, it's a csv with 1300 ligne, it's take approximatly
30min to upload
From your comment, I can tell you that the req.setTimeout you have in your route is not doing what you want, because you have a middleware before that, probably multer from seeing at your code that is not setting the req.setTimeout to >30 min.
So the request is timing out before it reaches your route. You should do something like this:
app.post(
'/',
(req, res, next) => {
// Set request setTimeout BEFORE any other middlewares
req.setTimeout(ms('35m')); // using `ms` package
next();
},
upload.single('file'), // or whatever multer setup you have
(req, res, next) => {
// Your route
}
)
With this code, you have 35 minutes to upload & end your response, since it won't timeout at multer middleware after 2 minutes. Of course if you don't know how much time it will take, most likely the case, you can use req.setTimeout(0) or whatever value you consider appropiate.
Update
For your code, you'll have to do the following:
// or router.use((req, res, next) => ...)
router.post('/', (req, res, next) => {
// This should be BEFORE `fileUpload`
req.setTimeout(0);
next();
});
router.use(fileUpload());
//#route POST api/students
//#desc Fill the database with the json information
//#access Public
router.post('/', async (req, res, next) => {
// ...rest of your code
})

NodeJS RESTful API - How to handle 'undefined' request variables properly?

I am developing a RESTful API using NodeJS and Express.
I noticed that incoming requests sometimes lack of some expected variables, which cause the program to crash, saying it couldn't set the value of a variable, to an 'undefined' value - as no value arrived with the request.
Example:
The application is expecting variableY, but instead variableX is being sent:
formData: { variableX: 'valueX' }
The program is expecting to receive variableY, with the following code:
const checkVariables = Joi.validate({
variableY: req.body.variableY,
}, schema);
The application crashes with the following error:
TypeError: Cannot read property 'variableY' of undefined
I thought about a few ways to handle that, including declaration of variables upon application initiation and using them along, using try-catch.
Another way will be to use if-else, if-chaining, or case-switch, but as you understood of course I am looking for the cleanest way to achieve that.
Any ideas?
Thank you.
** EDIT **
Progressed and managed to achieve the result using the object only. Once trying to reach any of it's inner fields the error will be thrown anyway, example:
if(req.body.variableY == undefined){console.log('The expected variable is undefined');} //true
When the validation addresses a field inside the 'undefined' object:
if(req.body.variableY.dataId == undefined){console.log('The expected variable is undefined');} //crashes
The following error is being thrown again:
TypeError: Cannot read property 'variableX' of undefined
After doing some more digging around, found this Stackoverflow thread:
How to check if object property exists with a variable holding the property name?
Tried using hasOwnProperty, but the same kind of error is being thrown:
TypeError: Cannot read property 'hasOwnProperty' of undefined
Tried wrapping variable declaration using try-catch, still didn't work:
try{
var variableX = req.body.variableX
var variableXDataId = req.body.variableX.dataId
}
catch(e){
res.status(400).send('Wrong request error: Please check your request variables and try again');
}
As this is a really basic validation that should be addressed by most of the RESTful APIs (validating that you get the expected incoming variables inside the request, so the program won't crash by having errors it can't handle - what is the common solution for such problems (expected / unexpected request validation)?
Thank you.
You can take another approach, check req.body before you reach checkVariables:
let body = req.body;
// data - your req.body
// requiredKeys - is an array of strings , [ key1, key2 ... keyN] | string[]
const setKeys = ( data, requiredKeys )=>{
if( !typeof requiredKeys.length ){
requiredKeys = [];
}
if(requiredKeys.length) requiredKeys.forEach( k =>{
k = k.replace(/\+/g,'/');
let keysList = [];
if( /\/+/g.test(k)){
keysList = k.split('/');
}else{
keysList = [k];
}
let [firstKey, ...rest] = keysList;
if( typeof data[firstKey] === 'undefined' ){
data[firstKey] = {};
}
if( rest.length ){
data[firstKey] = setKeys(data[firstKey], [rest.join('/')] );
}
})
return data;
}
let checkedData= setKeys(body, ['variableT','variableP/noname/emptyObj','custom/object/does/not/exist/but/it/will/be/created/here']);
const checkVariables = Joi.validate(checkedData, schema);
UPDATE
Below you will find an working example on how things should work during a /(let's say /usersStatus/:id ) request:
const express = require('express')
const app = express()
const port = 3000
const setKeys = (data, requiredKeys) => {
if (!typeof requiredKeys.length) {
requiredKeys = [];
}
if (requiredKeys.length) requiredKeys.forEach(k => {
k = k.replace(/\+/g, '/');
let keysList = [];
if (/\/+/g.test(k)) {
keysList = k.split('/');
} else {
keysList = [k];
}
let [firstKey, ...rest] = keysList;
if (typeof data[firstKey] === 'undefined') {
data[firstKey] = {};
}
if (rest.length) {
data[firstKey] = setKeys(data[firstKey], [rest.join('/')]);
}
})
return data;
}
/**
* Mock some data
*/
const getUserData = (req, res, next) => {
if (typeof req.body === 'undefined') {
req.body = {};
}
req.body = {
variableY: {
someName: 23
},
variableZ: {
name: 3,
type: {
id: 5,
typeName: 'something',
tags: ['a', 'b', 'c']
}
}
};
console.log('Middleware 1 getUserData');
next();
}
/**
* 1. Setup our middleware for checking keys
* "requiredKeys" is an array of strings
*/
const middlewareSetKeys = (requiredKeys, wrappedMiddleware) => {
return (req, res, next) => {
console.log('Middleware 2 middlewareSetKeys');
if (typeof req.body === "undefined") {
console.log('Leaving Middleware 2 since we don\'t have req.body');
next();
}
/**
* Update "req.body" with keys that we want to have available
* in our next middleware
*/
req.body = setKeys(req.body, requiredKeys);
if (typeof wrappedMiddleware === 'function') {
return wrappedMiddleware.call(this, req, res, next);
} else {
next();
}
}
}
/**
* 2. Let's assume a "user status" situation
* 2.1. We need userInfo from database
* 2.2. Some info won't be retrieved, unless the user accesed some parts of the website to trigger some mechanisms that allows those fields to be exposed, therefore the lack of keys
* 2.3. But we know those keys/objects, and we still want to be present so our code won't crash.
*/
// lets call our getUserData
app.get(
'/', // this path is for some userInfo
getUserData, // this returns userInfo and appends it to `req.data`
middlewareSetKeys([
'userActivity/daily/jobs', // these won't exist in getUserData because the user is lazy and he didn't apply for any JOBS
'userStatus/active/two-weeks-ago', // these won't exist in getUserData because the user joined two days ago. BUT WE STILL NEED IT coz reazons.
]), // We set our desired-later-to-use keys
(req, res, next) => {
/**
* 3. Now our req.body will have our keys
* even if they didn't exist in the getUserData middleware
*/
console.log('Middleware 3 Your middleware');
console.log(req.body);
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(req.body, null, 2))
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
you can use express validator https://www.npmjs.com/package/express-validator
to validate incoming request.Then add this to your controller where a,b,c ,d are parameters you want to valaidate
const nonEmptyFields = ['a', 'b', 'c', 'd'];
nonEmptyFields.forEach(field => req.assert(field, `${field} cannot be blank`).notEmpty());
const errors = req.validationErrors();
if (errors) {
return res.status(400).send(errors);
}
for validating a field inside a field you can try doing this
typeof(req.body && req.body.name !== undefined)
A solution will be to set a default empty object to replace undefined at a parent level:
// checking for body.variableX.variableZ with object destructuring ES6
const {body = {}} = request;
const {variableX = {}, variableY} = body;
const {variableZ} = variableX.variableZ;
// or prior ES6
var body = request.body || {};
var variableX = body.variableX || {};
var variableY = variableX.variableY;
// or in a statement
var variableY = request.body && request.body.variableX ? request.body.variableX.variableY : undefined;
Based on that you can create your own function like getValue(request, 'body.variableX.variableY') to return null if any parent or the end value is undefined:
// asumes the value in the path is either object or undefined
function getValue(rootObj, path = '') {
const parts = key.split('.');
let value = rootObj || {};
let part;
while ((part = parts.shift()) && value !== null) {
value = value[part] || null;
}
return value;
};

How do I get data out of a Node http(s) request?

How do I get the data from a https request outside of its scope?
Update
I've seen Where is body in a nodejs http.get response?, but it doesn't answer this question. In fact, that question isn't answered accurately, either. In the accepted answer (posted by the asker), a third party library is used. Since the library returns an object different from that returned by http.get() it doesn't answer the question.
I tried to set a variable to the return value of http.get() using await, but that returns a http.clientRequest and doesn't give me access to the response data that I need.
I'm using Node v8.9.4 with Express and the https module to request data from Google's Custom Search.
I have two routes. One for a GET request and one for a POST request used when submitting a form on the front page. They both basically serve the same purpose... request the data from CSE and present the data as a simple JSON string. Rather than repeat myself, I want to put my code for the CSE request into a function and just call the function within the callback for either route.
I thought about returning all the way up from the innermost callback, but that won't work because it wouldn't get to the request's error event handler or the necessary .end() call.
Here's a subset of the actual code:
app.get('/api/imagesearch/:query', newQuery)
app.post('/', newQuery)
function newQuery (req, res) {
let query = req.body.query || req.params.query
console.log(`Search Query: ${query}`)
res.status(200)
res.set('Content-Type', 'application/json')
// This doesn't work
let searchResults = JSON.stringify(cseSearch(req))
res.end(searchResults)
}
function cseSearch (request) {
let cseParams = '' +
`?q=${request.params.query}` +
`&cx=${process.env.CSE_ID}` +
`&key=${process.env.API_KEY}` +
'&num=10' +
'&safe=high' +
'&searchType=image' +
`&start=${request.query.offset || 1}`
let options = {
hostname: 'www.googleapis.com',
path: '/customsearch/v1' + encodeURI(cseParams)
}
let cseRequest = https.request(options, cseResponse => {
let jsonString = ''
let searchResults = []
cseResponse.on('data', data => {
jsonString += data
})
cseResponse.on('end', () => {
let cseResult = JSON.parse(jsonString)
let items = cseResult.items
items.map(item => {
let resultItem = {
url: item.link,
snippet: item.title,
thumbnail: item.image.thumbnailLink,
context: item.image.contextLink
}
searchResults.push(resultItem)
})
// This doesn't work... wrong scope, two callbacks deep
return searchResults
})
})
cseRequest.on('error', e => {
console.log(e)
})
cseRequest.end()
}
If you're curious, it's for a freeCodeCamp project: Image Search Abstraction Layer
using promise method solve this issue.
cseSearch(req).then(searchResults=>{
res.end(searchResults)
}).catch(err=>{
res.status(500).end(searchResults)
})
function cseSearch (request) {
return new Promise((resolve, reject)=>{
...your http request code
cseResponse.on('end', () => {
let cseResult = JSON.parse(jsonString)
let items = cseResult.items
items.map(item => {
let resultItem = {
url: item.link,
snippet: item.title,
thumbnail: item.image.thumbnailLink,
context: item.image.contextLink
}
searchResults.push(resultItem)
})
resolve(searchResults);
})
})
}
Based on what I explained in the comments, to give you an idea how compact your code could be using the request-promise library, here's what you could use:
const rp = require('request-promise-native');
app.get('/api/imagesearch/:query', newQuery)
app.post('/', newQuery)
function newQuery (req, res) {
let query = req.body.query || req.params.query
console.log(`Search Query: ${query}`)
cseSearch(req).then(results => {
res.json(results);
}).catch(err => {
console.log("newQueryError ", err);
res.sendStatus(500);
});
}
function cseSearch (request) {
let cseParams = '' +
`?q=${request.params.query}` +
`&cx=${process.env.CSE_ID}` +
`&key=${process.env.API_KEY}` +
'&num=10' +
'&safe=high' +
'&searchType=image' +
`&start=${request.query.offset || 1}`
let options = {
hostname: 'www.googleapis.com',
path: '/customsearch/v1' + encodeURI(cseParams),
json: true
};
return rp(options).then(data => {
return data.items.map(item => {
return {
url: item.link,
snippet: item.title,
thumbnail: item.image.thumbnailLink,
context: item.image.contextLink
};
});
});

Why ctx.state did not pass to another middleware?

use koa2 ejs koa-router, ejs template how to use another middleware's ctx.state
localhost:3000/admin/usermsg
admin.get('/usermsg', async(ctx) => {
ctx.state.userMsg = {
page: Number(ctx.query.page),
limit: 4,
pages: 0,
count: count
}
var userMsg = ctx.state.userMsg;
ctx.state.users = await new Promise(function(resolve, reject){
userMsg.pages = Math.ceil(userMsg.count / userMsg.limit);
userMsg.page = userMsg.page > userMsg.pages ? userMsg.pages : userMsg.page;
userMsg.page = userMsg.page < 1 ? 1 : userMsg.page;
var skip = (userMsg.page - 1) * userMsg.limit;
User.find().limit(userMsg.limit).skip(skip).exec(function(err, doc){
if(doc){
resolve(doc);
}
if(err){
reject(err);
}
})
})
await ctx.render('admin/usermsg');
})
localhost:3000/damin/category
admin.get('/category', async(ctx) => {
await ctx.render('admin/category');
})
in the category template,can not get ctx.state.userMsg.
how should i get ctx.state.userMsg in category template?
Well, assuming userMsg is something you use a lot in your views, you could make a dedicated middleware just to obtain that value.
Middleware work in 'stacks': by calling next(), you can pass control to the next one in the stack (with access to the modified ctx.state). A trivial example:
const setUserMsg = async (ctx, next) => {
ctx.state.userMsg = await myFuncThatReturnsAPromise()
await next()
}
router.get('/someroute',
setUserMsg,
ctx => { ctx.body = ctx.state.userMsg })
router.get('/someotherroute',
setUserMsg,
ctx => { ctx.body = ctx.state.userMsg })
Here, setUserMsg's sole purpose is to extract a value (presumably from the database) and add it to the context.

Resources