Issues with URL shortener - Aync issue?

Hi folks, having some issues with my ULR shortener.

At a high level I plan to implement:

  • submit url to be shortened
    → check if the url has already been shortened in database
    → if it has be shortened, return that record as json
    → else make a new shortened url to store in db
    -----> need to check the number of records in db first before assigning a number for the shortened url
    → return new shortened url as json
    → implement the action of directing to the shortened url (still trying to figure out above before thinking too much about this piece)

I am slowly piecing it together, but am having issues looking up the records in the db.

I have a finder function that will look up the number of records and return the results. Within the finder function, I can log the data that returns from the database find, but when I try to return that data through the finder function, it returns undefined, even if it found records.

I think there is some asynchronous workings going on that I don’t quite understand, or I am not using the callback correctly. Hoping for some feedback! Feel free to share feedback on the general high level implementation as well.

The project so far is on glitch, below are links to specifics parts that I am trying to figure out.
If you view the page with logs open, and trigger the post action, you will see the statements getting logged.

finder function (see lines 48-51)

location where undefined get returned(lines 48-50)

You should be using the query parameters in a “get” request instead of trying to get your URL from the req.body in a post request. The url you are trying to shorten is passed in as a query parameter to the route ‘/api/shorturl/new’.

From the Express documentation:

app.get('/user/:id', function(req, res) {
  res.send('user ' + req.params.id);
});

Thanks I’ll mess around with using GET and see how it behaves.

I’m a bit confused though, if form method is POST, shouldn’t the route be POST?

<form action="api/shorturl/new" method="POST"> 
          <label for="url_input">URL to be shortened</label>
          <input id="url_input" type="text" name="url" value="https://www.freecodecamp.com">
          <input type="submit" value="POST URL">
</form>form
app.post('/api/shorturl/new', function(req,res,next){
         // do things
        // mongoose mongodb call
})

Also, is there a way to pass next() to a mongoose db call if the route doesn’t have a next parameter. I don’t think I am using the middleware correctly.

Sorry, it looks like they changed how this project works since I did it on the old curriculum. In the old curriculum you just put the url you want to shorten in the address bar and passed it as a query parameter. In this version of the project, it looks like they want you to submit a form, so your post route is the way you want to handle this.

I can try to look at your project again later and answer your question on the next() mongoose db call.

No problem! Much appreciated!

I restarted the project under a new glitch in case anything else was interfering. Still same behavior.

Here is the code with issue:

app.post('/api/shorturl/new', function (req, res, next) {
  var tempVar;
  console.log("query = " + req.body.url); // logs the url from the form
  // Short is the mongo model
  Short.find({url:req.body.url}).exec(function(err,data){
    if (err) return next(err);
    else{
      console.log(data); // logs the record from the database
      tempVar = data;
      return next(null,data);}
  })
  console.log("tempVar = " + tempVar); // logs "tempVar = undefined
});

NOTE: removing the exec function, and just passing the callback to find() yields the same results.
Here is a link to the project, post route is on line 44:
post route

Same deal on this one, if you view the live page with the console open in another tab and hit POST URL without entering anything, you will see the console firing

Okay, I think I have some helpful feedback for you. I don’t use any “next” type of functions when trying to do database activities and express. Instead you can just stick the logic inside of the mongoose Short.find call. So, the project wants you to return a json object of a shortened url. You have no response to your post request right now, which was giving me a cannot post error in express. I have modified your code so at least it returns a json response:

app.post('/api/shorturl/new', function (req, res) {
  Short.find({url:req.body.url}).exec(function(err,data){
    if (err) return next(err);
      console.log(data); // logs the record from the database
      return res.json({'posted': data});
  })
});

Instead of returning just the “data” you can read the data and shorten it, save the shortened version to the database, and whatever, and then send the response. I hope this explanation makes sense. We can work on this more together if needed. Right now data is just an empty array when I do testing because you aren’t saving anything to the database.

Moving the logic inside of the database Short.find I think fixes whatever “asynch” problems you thought you were hitting.

Extremely helpful thank you very much!
I can successfully read and write to mongo without collision now!

The code is nasty looking though. I should be able to put the logic in a separate .js file and import, right? If you have any tips or feedback it would be much appreciated!

Code at the moment:

app.post('/api/shorturl/new', function (req, res, next) {
  var tempVar;
  Short.find({original_url:req.body.url}).exec(function(err,data){
    if (err) return next(err);
    // if data exists (length > 0), just return the record
    console.log(data);
    if(data.length > 0){
      return res.json({original_url:data[0].original_url,short_url:data[0].short_url})
    }
    // if it don't exist, check records in db and make a new record with shortUrl
    // as data.length+1
    else{
      Short.find({}).exec(function(err,data){
        if (err) return next(err)
        var shUrlJson = {original_url:req.body.url, short_url:data.length+1};
        var shorty = new Short(shUrlJson);
        shorty.save(function(err){
          if (err) return next(err)
          return res.json(shUrlJson);
        })
      })
    }
  })
});

Awesome, I am glad it was helpful!
If you try to move the logic around too much it’s more difficult to access the response in app.post, and you need that. You can experiment if you want, but I think it’s fine.

Thanks again!,

So the project generally works, expect for cases where the website doesn’t resolve via dns (is that the right way to phrase that?)

I have a dns.lookup triggering, but I’m running into the same issues with the mongo calls, it will resolve or error within the callback, but I can’t do anything with it outside of the callback. Is there something fundamental I am missing here?
I also fiddled with removing the next parameter in the post callback, but then I get a bunch of syntax errors where next isn’t found?
LINK
dns.lookup is near the top

app.post('/api/shorturl/new', function (req, res, next) {
  var doesResolve = true;
  // url validation check
  // if the dns lookup fails, set doesresolve to false and use that to return invalid url later
  dns.lookup(req.body.url.replace(/(^\w+:|^)\/\//, ''), function(err, address, family){
    if(err){
      console.log(err);
      doesResolve = false;
    }
  })
  console.log("doesresolve = " + doesResolve);
  // urlcheck is just a long regex for http(s)://www.example.domain/path
  // stored in seperate file
  if (!urlCheck.urlCheck(req.body.url) || !doesResolve){
    res.json({'error':'invalid url'});
  }
  else{
    Short.find({original_url:req.body.url}).exec(function(err,data){
      if (err) return next(err);
      // if data exists (length > 0), just return the record
      console.log(data);
      if(data.length > 0){
        return res.json({original_url:data[0].original_url,short_url:data[0].short_url})
      }
      // if it don't exist, check records in db and make a new record with shortUrl
      // as data.length+1
      else{
        Short.find({}).exec(function(err,data){
          if (err) return next(err)
          var shUrlJson = {original_url:req.body.url, short_url:data.length+1};
          var shorty = new Short(shUrlJson);
          shorty.save(function(err){
            if (err) return next(err)
            return res.json(shUrlJson);
          })
        })
      }
    })
  }
});

OK, I think I’ve figured it out, I have to nest everything within the dns.lookup callback, officially entering callback hell… if anyone has any feedback on the structure of the code, refactoring suggestions, etc, would be much appreciated!! Everything seems to work correctly now.

LINK

funny side note, asdasdasdasd.com may be available for purchase as a domain name :rofl:
(I thought testing this site would respond with an invalid url, but it doesn’t)

app.post('/api/shorturl/new', function (req, res, next) {
  // urlcheck is just a long regex for http(s)://www.example.domain/path
  // stored in seperate file
  // regex check the format
  if (!urlCheck.urlCheck(req.body.url)){
    res.json({'error':'invalid url'});
  }
  // else do dns lookup
  else{
    dns.lookup(req.body.url.replace(/(^\w+:|^)\/\//, ''), function(err, address, family){
      if(err){
        res.json({'error':'invalid url'})}
      else{
        Short.find({original_url:req.body.url}).exec(function(err,data){
          if (err) return next(err);
          // if data exists (length > 0), just return the record
          console.log(data);
          if(data.length > 0){
            return res.json({original_url:data[0].original_url,short_url:data[0].short_url})
          }
          // if it don't exist, check records in db and make a new record with shortUrl
          // as data.length+1
          else{
            Short.find({}).exec(function(err,data){
              if (err) return next(err)
              var shUrlJson = {original_url:req.body.url, short_url:data.length+1};
              var shorty = new Short(shUrlJson);
              shorty.save(function(err){
                if (err) return next(err)
                return res.json(shUrlJson);
              });
            });
          };
        });
      };
    });
  };
});
2 Likes