Lambda function REST API end point fails when deployed using node/express/mongodb

url:
https://pedantic-hermann-961116.netlify.app/

When deploying my express/mongoDB app to Netlify I keep running into a 404 error when hitting my endpoint. When testing the post request locally (http://localhost:5000/.netlify/functions/api/shorten) and

{
  "longUrl": "https://www.katalon.com/resources-center/blog/ci-cd-tools/"
}

in the body. I am getting the correct response back:

{
"_id":"60e6026e67a0b24e5fd228bc",
"longUrl":"https://www.katalon.com/resources-center/blog/ci-cd-tools/",
"shortUrl":"http://localhost:5000/U-MwiSyGa",
"urlCode":"U-MwiSyGa",
"date":"Wed Jul 07 2021 15:37:18 GMT-0400 (Eastern Daylight Time)",
"__v":0
}

When I preview the function in the dashboard it is running.

However if I open up the endpoint in a browser. I get the error below.

{"errorType":"Error","errorMessage":"Configuration property \"mongoURI\" is not defined","trace":["Error: Configuration property \"mongoURI\" is not defined"," at w.get (/var/task/index.js:1624:1824)"," at Object.<anonymous> (/var/task/index.js:1783:1669)"," at n (/var/task/index.js:1:158)"," at Object.<anonymous> (/var/task/index.js:1624:13555)"," at n (/var/task/index.js:1:158)"," at /var/task/index.js:1:957"," at Object.<anonymous> (/var/task/index.js:1:968)"," at Module._compile (internal/modules/cjs/loader.js:999:30)"," at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)"," at Module.load (internal/modules/cjs/loader.js:863:32)"," at Function.Module._load (internal/modules/cjs/loader.js:708:14)"," at Module.require (internal/modules/cjs/loader.js:887:19)"," at require (internal/modules/cjs/helpers.js:74:18)"," at _tryRequire (/var/runtime/UserFunction.js:75:12)"," at _loadUserApp (/var/runtime/UserFunction.js:95:12)"," at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)"]}

config/db.js

const mongoose = require('mongoose');
const config = require('config');
const db = config.get('mongoURI');

const connectDB = async () => {
  try {
    await mongoose.connect(db, {
      useNewUrlParser: true
    });

    console.log('MongoDB Connected...');
  } catch (err) {
    console.error(err.message);
    process.exit(1);
  }
};

module.exports = connectDB;

config/default.json

{
  "mongoURI": "mongodb+srv://user:password@cluster0.eqzr789.mongodb.net/myFirstDatabase?retryWrites=true&w=majority",
  "baseUrl": "https://pedantic-hermann-961116.netlify.app/"
}

dist/
empty index.html

models/Url.js

const mongoose = require('mongoose');

const urlSchema = new mongoose.Schema({
  urlCode: String,
  longUrl: String,
  shortUrl: String,
  date: { type: String, default: Date.now }
});

module.exports = mongoose.model('Url', urlSchema);

routes/index.js

const express = require('express');
const router = express.Router();

const Url = require('../models/Url');

// @route     GET /:code
// @desc      Redirect to long/original URL
router.get('/:code', async (req, res) => {
  try {
    const url = await Url.findOne({ urlCode: req.params.code });

    if (url) {
      return res.redirect(url.longUrl);
    } else {
      return res.status(404).json('No url found');
    }
  } catch (err) {
    console.error(err);
    res.status(500).json('Server error');
  }
});

module.exports = router;

routes/url.js


const express = require('express');
const router = express.Router();
const validUrl = require('valid-url');
const shortid = require('shortid');
const config = require('config');

const Url = require('../models/Url');

// @route     POST /api/url/shorten
// @desc      Create short URL
router.post('/shorten', async (req, res) => {
  const { longUrl } = req.body;
  const baseUrl = config.get('baseUrl');

  // Check base url
  if (!validUrl.isUri(baseUrl)) {
    return res.status(401).json('Invalid base url');
  }

  // Create url code
  const urlCode = shortid.generate();

  // Check long url
  if (validUrl.isUri(longUrl)) {
    try {
      let url = await Url.findOne({ longUrl });

      if (url) {
        res.json(url);
      } else {
        const shortUrl = baseUrl + '/' + urlCode;

        url = new Url({
          longUrl,
          shortUrl,
          urlCode,
          date: new Date()
        });

        await url.save();

        res.json(url);
      }
    } catch (err) {
      console.error(err);
      res.status(500).json('Server error');
    }
  } else {
    res.status(401).json('Invalid long url');
  }
});

module.exports = router;

src/index.js

const express = require('express');

const connectDB = require('../config/db');

const serverless = require("serverless-http");

const app = express();

// Connect to database

connectDB();

app.use(express.json());

// Define Routes

app.use('/', require('../routes/index'));

app.use('/.netlify/functions/api', require('../routes/url'));

const PORT = 5000;

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

module.exports = app;

module.exports.handler = serverless(app);

package.json

{
  "name": "url_shortner_service",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index",
    "dev": "nodemon src/index",
    "build": "NODE_ENV=production ./node_modules/.bin/netlify-lambda build src"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "config": "^3.1.0",
    "express": "^4.17.1",
    "mongodb-client-encryption": "^1.2.6",
    "mongoose": "^5.6.4",
    "netlify-lambda": "^2.0.11",
    "serverless-http": "^2.7.0",
    "shortid": "^2.2.14",
    "valid-url": "^1.0.9"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

netlify.toml

[build]
  functions = "functions"

[dev]
  publish = "dist"

Hi @JephJ,

Thank you for your patience on this. I think the problem might be because the JSON file that you need is probably not bundled in the final code as it’s never referenced in your code directly.

What you could try is setting the value as an environment variable Build environment variables | Netlify Docs, and access it using process.env.mongoURI.

Still no luck:

Default.json

{
  "mongoURI": "${process.env.mongoURI}",
  "baseUrl": "https://pedantic-hermann-961116.netlify.app/"
}

I meant, avoid the JSON altogether. In your code, instead of const db = config.get('mongoURI') use const db = process.env.mongoURI

Ah, gotcha. For some reason it just wants to default to “index” in the functions.

https://pedantic-hermann-961116.netlify.app/.netlify/functions/index

throws error:
Cannot GET /.netlify/functions/index

netlify.toml:

[build]
  functions = "functions/api"

[dev]
  publish = "dist"

src/index.js


const express = require('express');

const connectDB = require('../config/db');

const serverless = require("serverless-http");

const app = express();

// Connect to database

connectDB();

app.use(express.json());

// Define Routes

app.use('/', require('../routes/index'));

app.use('/.netlify/functions/api', require('../routes/url'));

const PORT = 5000;

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

module.exports = app;

module.exports.handler = serverless(app);

Could we see a repo that’s reproducing this? Just as a note, if the above index.js is client side code, this probably won’t work.

I’m not seeing the functions folder here:

Do I need one? What goes in it?

Your netlify.toml file says functions folder is configured as functions. So, all your functions would go in that. If the folder doesn’t exist the functions won’t exist.