2

I get confused on how to show json result I've got from api on my webpage

I am using express.js + node.js, and Bing API

here is my code

const https = require('https');
const express = require("express");
const app = express();
var port = 8000;

const SUBSCRIPTION_KEY = 'xxxxxxxxxxxxxxxxxxxxx'
if (!SUBSCRIPTION_KEY) {
  throw new Error('error no keys')
}

app.use(express.static("example.com"));

function bingWebSearch(query) {
  https.get({
    hostname: 'api.cognitive.microsoft.com',
    path:     '/bing/v7.0/search?q=' + encodeURIComponent(query),
    headers:  { 'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY },
  }, res => {
    let body = ''
    res.on('data', part => body += part)
    res.on('end', () => {

      // yes this could work and print out result json at my terminal
      console.dir(JSON.parse(body), { colors: false, depth: null })

      // but why this can not work with error msg I posted below
      res.setHeader('Content-Type', 'application/json');
      res.end(JSON.stringify({ a: 1 }));
      // I cant even output this on my webpage, and actually I want to print of course the result json

    })
    res.on('error', e => {
      console.log('Error: ' + e.message)
      throw e
    })
  })
}

app.get("/api", (req, res) => {

  const searchString = `${req.query.q}`;
  bingWebSearch(searchString);

});

app.listen(port, () => console.log('Your app is ready! Navigate to: http://localhost:' + port + '/.'));

and error msg here

/var/www/bing-web-search.js:27
  res.setHeader('Content-Type', 'application/json');
      ^
/var/www/bing-web-search.js:28
  res.end(JSON.stringify({ a: 1 }));
      ^
TypeError: res.setHeader is not a function
TypeError: res.end is not a function
    at IncomingMessage.res.on (/var/www/bing-web-search.js:31:11)
    at emitNone (events.js:111:20)
    at IncomingMessage.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)

p.s. if I move line 27,28 into app.get

app.get("/api", (req, res) => {

  const searchString = `${req.query.q}`;
  bingWebSearch(searchString);
  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify({ a: 1 }));

});

They worked fine to appear on my webpage

So I just don't know how to make my result also shown on my webpage

Any tip or help would be appreciate, thank you!

alexh1994
  • 25
  • 6
  • 1
    Using *https* is really low level, and not really recommended if not strictly necessary. You should look into using something like [got](https://www.npmjs.com/package/got) or similar module and use promises instead of callbacks. There are many modules which simplifies this process a lot. – Enslev Aug 04 '20 at 10:07
  • @Enslev Ah thank you, this was completing copied from Bing API document. I will try another way, really thank you for your kindly remind. – alexh1994 Aug 04 '20 at 14:07

3 Answers3

1

This is about a very basic concept of programming, called scope. And you are actually very close to the solution. You can read about scope in Javascript here.

Within calling of GET /api, another function bingWebSearch(searchString) gets called. bingWebSearch has its own scope. So the res variable from GET /api is not available there.

Also, within bingWebSearch you make a https.get request, and you're naming the return value res as well.

One solution would be to pass the res variable of GET /api as an argument:

bingWebSearch(searchString, res)

But then of course you need to rename the return value of https.get. You could call it response.

Would look like this:

app.get("/api", (req, res) => {
   bingWebSearch(searchString, res);
});

function bingWebSearch(query, res) {
   https.get({
      ...
   }, response => {
      let body = ''
      response.on('data', part => body += part)
      response.on('end', () => {
         res.setHeader('Content-Type', 'application/json');
         res.end(JSON.stringify({ a: 1 }));
      }
   }
}

Another solution would be to actually return the JSON string from bingWebSearch and then call res.setHeader and res.end within app.get("/api").

I think I'd prefer that one, as I think it is more readable. But then you'd have to consider that https.get gets called async. So if you try to just return its result, there won't be a result yet. But that is a topic on its own and not within the scope of your question. Quentin's answer has some hints on it.

Wu Wei
  • 1,827
  • 1
  • 15
  • 27
1

You have two different variables named res. They have completely different values.

The res defined here:

app.get("/api", (req, res) => {

… is the object representing the response you are going to send from Express to the client.

The res defined here:

}, res => {

… is the object representing the response you received from Bing.


You are trying to treat the response from Bing as if it was the response you are sending to the client.

Since it isn't, it fails. It fails in the particular way you are seeing because the APIs for the two objects are different.


You need to put the data from the bingWebSearch(searchString); response in the same place as the Express response object. Using different variable names for the two res variable wouldn't be a bad idea either, it would make the code less confusing.

Since you are using https.get you can do that by passing in a callback function which closes over the Express res variable, but it would probably be easier to wrap it in a promise instead. It would be even easier to replace https.get with an API that uses promises by default (e.g. Axios).

Further reading: How do I return the response from an asynchronous call?

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1
const https = require('https');
const express = require("express");
const app = express();
var port = 8000;

const SUBSCRIPTION_KEY = 'xxxxxxxxxxxxxxxxxxxxx'
if (!SUBSCRIPTION_KEY) {
  throw new Error('error no keys')
}

app.use(express.static("example.com"));

function bingWebSearch(query) {
  https.get({
    hostname: 'api.cognitive.microsoft.com',
    path:     '/bing/v7.0/search?q=' + encodeURIComponent(query),
    headers:  { 'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY },
  }, (req, res) => {
    let body = ''
    res.on('data', part => body += part)
    res.on('end', () => {

      // yes this could work and print out result json at my terminal
      console.dir(JSON.parse(body), { colors: false, depth: null })

      // but why this can not work with error msg I posted below
      res.setHeader('Content-Type', 'application/json');
      res.end(JSON.stringify({ a: 1 }));
      // I cant even output this on my webpage, and actually I want to print of course the result json

    })
    res.on('error', e => {
      console.log('Error: ' + e.message)
      throw e
    })
  })
}

app.get("/api", (req, res) => {

  const searchString = `${req.query.q}`;
  bingWebSearch(searchString);

});

app.listen(port, () => console.log('Your app is ready! Navigate to: http://localhost:' + port + '/.'));

express returns the response in the second parameter of the function not in the first. so I corrected it on line number 18.

Aman Gupta
  • 140
  • 2
  • 10
  • https.get only returns the response to the callback function, according to the docs: https://nodejs.org/api/https.html#https_https_get_url_options_callback. So `(req, res) => { }` will result in `res` being `undefined`. – Wu Wei Aug 04 '20 at 10:02