URL Shortener Microservice - Failed to submit the project solution (but it's all working well)

My project code it’s actually working well. If you test manually, it can do what the challenge tells to do. From POST a URL and GET a ShortURL.

But when I tried to submit the solution, it always failed.

Here’s the code (or you can go to my project’s link below):

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
const validUrl = require('valid-url');
const mongoose = require('mongoose');

// Basic Configuration
const port = process.env.PORT || 3000;

app.use(cors());

app.use('/public', express.static(`${process.cwd()}/public`));

app.get('/', function(req, res) {
  res.sendFile(process.cwd() + '/views/index.html');
});

// Your first API endpoint
app.get('/api/hello', function(req, res) {
  res.json({ greeting: 'hello API' });
});

// Get our cluster URI
const MONGO_URI = process.env.MONGO_URI;

// Connect our mongoose
mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

// Create a URL schema
let shortUrlSchema = new mongoose.Schema({
  original_url: {
    type: String,
    required: true
  },
  short_url: {
    type: Number,
    required: true
  }
})

// Create shortUrl Model from shorUrl schema
const ShortUrl = mongoose.model('ShortUrl', shortUrlSchema);

// Create counter schema; to handle increment sequence value
const counterSchema = new mongoose.Schema({
  id: {
    type: String
  },
  sequence_value: {
    type: Number
  }
})

// Create counter model
const Counter = mongoose.model('Counter', counterSchema);

// Function to update our counter record
const updateCounter = () => {
  return new Promise((resolve, reject) => {
    // Find by id, increment sequence_value field by 1
    Counter.findOneAndUpdate(
      {id:"counterid"},
      {"$inc": {"sequence_value": 1}},
      {new:true}, async (err, data) => {
        // Declare variable to store increment value from record
        let sequenceId;
        
        // IF there's no document in counter record yet, set and save it. Which sequence_value to 1
        // ELSE just get sequence_value field, and save to sequenceId variable above
        if (data == null) {
          const firstRecord = new Counter({id: "counterid", sequence_value: 1});
          await firstRecord.save();
          sequenceId = 1;
        } else {
          sequenceId = data.sequence_value;
        }

        // Resolve by return sequenceId variable
        resolve(sequenceId);
      }
    )
  })
}

// Validate POST URL using RegEx
const isValidUrl = (url) => {
  var urlPattern = new RegExp("((http|https)://)(www.)?[a-zA-Z0-9@:%._\\+~#?&//=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*)");
  /*
    The URL must start with either http or https and
    then followed by :// and
    then it must contain www. and
    then followed by subdomain of length (2, 256) and
    last part contains top level domain like .com, .org etc.
  */
  
	return !!urlPattern.test(url);
}

// Parser body to post
app.use(bodyParser.urlencoded({extended: false}));

// Get shorturl key method to redirect to it's url
app.get('/api/shorturl/:shorturl', async(req, res) => {
  // Get key from /shorturl/<shorturl>
  const shortid = req.params.shorturl;
  // Find record by shorturl key
  const record = await ShortUrl.findOne({ short_url: shortid })

  // If doesn't exist yet, send Not Found
  if (!record) return res.sendStatus(404);

  // Go to original url
  res.redirect(record.original_url);
})

// Post url method
app.post('/api/shorturl', async(req, res) => {
  // Get post url
  const url = req.body.url;
  let urlRecordData;

  // First, validate URL format
  // IF not, send Invalid URL. Otherwise, saved it as new record
  if (!isValidUrl(url)) {
    res.send({error: 'Invalid URL'});
  } else {
      // First, check if POST URL already exist in record
      const checkRecord = await ShortUrl.findOne({original_url: url});
    
      // If NOT exist yet, increment sequence_value field in counter record to use it as short url
      if(!checkRecord) {
        await updateCounter()
          .then(async (res) => {
            // Create new record with res which sequenceId for shortUrl
            const record = new ShortUrl({original_url: url, short_url: res});
            await record.save();
          })
      }

    // If URL already in record, respond the data
    // OR also after create it
    urlRecordData = await ShortUrl.findOne({original_url: url}, {original_url: 1, short_url: 1, _id: 0});
    res.send(urlRecordData);
  }
})
  
app.listen(port, function() {
  console.log(`Listening on port ${port}`);
});

exports.shortUrlModel = ShortUrl;
exports.counterModel = Counter;
exports.updateCounter = updateCounter;

And this is the message error that shows in replit console whenever I submit the solution link. This message error doesn’t show when I RUN the project manually:

/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/query.js:4780
  const castError = new CastError();
                    ^

CastError: Cast to Number failed for value "undefined" (type string) at path "short_url" for model "ShortUrl"
    at model.Query.exec (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/query.js:4780:21)
    at model.Query.Query.then (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/query.js:4879:15)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  messageFormat: undefined,
  stringValue: '"undefined"',
  kind: 'Number',
  value: 'undefined',
  path: 'short_url',
  reason: AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
  
    assert.ok(!isNaN(val))
  
      at castNumber (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/cast/number.js:27:10)
      at SchemaNumber.cast (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/schema/number.js:376:12)
      at SchemaNumber.SchemaType.applySetters (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/schematype.js:1188:12)
      at SchemaNumber.SchemaType._castForQuery (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/schematype.js:1622:15)
      at SchemaNumber.castForQuery (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/schema/number.js:430:14)
      at SchemaNumber.SchemaType.castForQueryWrapper (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/schematype.js:1589:20)
      at cast (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/cast.js:344:32)
      at model.Query.Query.cast (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/query.js:5202:12)
      at model.Query.Query._castConditions (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/query.js:2176:10)
      at model.Query.<anonymous> (/home/runner/boilerplate-project-urlshortener/node_modules/mongoose/lib/query.js:2475:8) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: false,
    expected: true,
    operator: '=='
  },
  valueType: 'string'
}
[nodemon] app crashed - waiting for file changes before starting

I get stucked. Cause I found nothing wrong in my code. And I hope you guys can give the solution. Thank You.

Here’s my project link(s)

solution: boilerplate-project-urlshortener - Replit

Link to the challenge:

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36

Challenge: Back End Development and APIs Projects - URL Shortener Microservice

Your URL validation isn’t working.

var urlPattern = new RegExp("((http|https)://)(www.)?[a-zA-Z0-9@:%._\\+~#?&//=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*)");
!!urlPattern.test('https://boilerplate-project-urlshortener.lasjorg.repl.co/?v=1658006333032');
// false

I’d try using dns.lookup as is suggested on the challenge page. You can use the URL constructor to get the hostname and pass that to dns.lookup()

const isValidUrl = (url) => {
  var urlPattern = new RegExp("((http|https)://)(www.)?[a-zA-Z0-9@:%._\\+~#?&//=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*)");  
  return !!urlPattern.test(url);
}

// call site
if (!isValidUrl(url)) {
    res.send({error: 'Invalid URL'});
  }

So if !!urlPattern.test(url); is false, it is true at the call site.


Just as an aside.

Sometimes boolean logic just isn’t that great. It looks clean enough and it is easy to switch the type from true to false and vice versa but that can also be its downfall when you do not get the values you are expecting.

Compare that to a known string value like 'valid' and 'invalid'. Sometimes checking for a known fixed value is easier to reason about.