Netlify Lambda using Express with create-react-app

Hello,

I am trying to get my express app working in production, it is called server.js and located in my functions folder:

// functions/server.js
const connectDB = require('../config/db');
const express = require('express');
const serverless = require('serverless-http');
const app = express();
const basePath = '/.netlify/functions/server';

// Connect Database
connectDB();

// Init Middleware
app.use(express.json({ extended: false }));

// Define routes
app.use(`${basePath}/auth`, require('./routes/auth'));

module.exports.handler = serverless(app);

And here is config/db.js

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

const options = {
  useFindAndModify: false,
  useCreateIndex: true,
  useNewUrlParser: true,
  useUnifiedTopology: true,
};

const connectDB = async () => {
  try {
    await mongoose.connect(db, options);
    console.log('MongoDB connected.');
  } catch(err) {
    console.error(err.message);
    process.exit(1);
  };
};

module.exports = connectDB;

This is config/default.json

{
  "mongoURI": "Value removed for post",
  "jwtSecret": "Value removed for post"
}

And finally, a very simplified version of functions/routes/auth.js

// functions/routes/auth.js
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const auth = require('../middleware/auth');
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const config = require('config');
const { check, validationResult } = require('express-validator');

// @route   GET /.netlify/functions/server/auth
// @desc    Authenticate provided user credentials
// @access  Public
router.get('/', auth, async (req, res) => {
  try {
    const user = await User.findById(req.user.id).select('-password');
    res.json(user);
  } catch(err) {
    res.status(500).send('Server error.');
  }
});

This is my /netlify.toml file where the built-functions folder is called ‘lambda’

[build]
command = "yarn build"
functions = "lambda"
publish = "build"

My /jsconfig.json file in the project root:

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

And start/build/deploy scripts in /package.json

"scripts": {
    "start": "run-p start:**",
    "start:app": "react-scripts start",
    "start:server": "netlify-lambda serve functions",
    "build": "run-s build:**",
    "build:app": "react-scripts build",
    "build:server": "netlify-lambda build functions",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "deploy": "yarn build && netlify deploy --prod"
  }

Edit: Almost forgot if you want a 1:1 to get up and running locally with basic create-react-app, this is src/setupProxy.js

// src/setupProxy.js
const proxy = require('http-proxy-middleware');

module.exports = app => {
  const basePath = '/.netlify/functions/server';

  app.use(
    basePath,
    proxy({ target: 'http://localhost:9000' })
  );
};

I am facing two issues:

  1. my /config folder in my local project root does not seem to be available after deployment to Netlify – this path was being used to get my mongoURI and jwtSecret when connecting the db via express – the app was bootstrapped with Create React App, if that helps and is not ejected.

Edit: In production, response status code is 502 with the following:
errorMessage: "Configuration property "mongoURI" is not defined"

  1. After manually adding database values directly in the express portion, to temporarily bypass issue 1, the API times out after 10 seconds every time the express app lambda function is invoked in production (/.netlify/functions/server). This does not occur in the local development environment, where everything works as expected.

Edit:In production, response status code is 502 with the following:
errorMessage: "2020-01-02T22:27:59.500Z 5d1db543-e1e4-4ade-8f15-fe05c534757b Task timed out after 10.01 seconds"

Please keep in mind this is Express/mongoose and not using the native lambda event, context, callback.

Edit: Using Node v10.16.3

Any insight will be helpful – thanks.

1 Like

I was able to solve issue 1:
errorMessage: "Configuration property "mongoURI" is not defined"

by removing the third-party ‘config’ package and folder (including default.json/production.json) and moving config/db.js to functions/connectDB/db.js – then, instead used an environment variable:

// New location: functions/connectDB/db.js
const mongoose = require('mongoose');

require('dotenv').config();
const { REACT_APP_MONGO_URI: db } = process.env;

const options = {
  useFindAndModify: false,
  useCreateIndex: true,
  useNewUrlParser: true,
  useUnifiedTopology: true,
};

const connectDB = async () => {
  try {
    await mongoose.connect(db, options);
    console.log('MongoDB connected.');
  } catch(err) {
    console.error(err.message);
    process.exit(1);
  };
};

module.exports = connectDB;

The same is true for anywhere jwtSecret was needed:

require('dotenv').config();
const { REACT_APP_JWT_SECRET: jwtSecret } = process.env;

This got issue 1 solved locally, but to solve it in production I also had to go to my Netlify site settings > Build and Deploy > Environment and added the two environment variables: REACT_APP_MONGO_URI and REACT_APP_JWT_SECRET with their appropriate values.

Now all that’s left is the lambda function timeout issue in production.

Still unable to figure out why the timeout is occuring – anyone?

Well, I figured out the timeout issue – don’t be a dumb-dumb like me and forget to open up your db for CDN’s to access. Disabling my white list IP in MongoDB Atlas did the trick.

At least the set-up posted above can possibly help future people as it works out of the box – just remember to use your environment variables :stuck_out_tongue_winking_eye:

1 Like

Update for the proxy. http-proxy-middleware has updated its proxy function and is now called { createProxyMiddleware } and not ‘proxy’

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
    app.use(
        createProxyMiddleware('/.netlify/functions', {
            target: 'http://localhost:9000/',
            pathRewrite: {
                '^/\\.netlify/functions': '',
            },
            changeOrigin: true,
        })
    );
};
3 Likes

just wanted to say thanks - reading here about the renaming of “proxy” to “createProxyMiddleware” probably saved me an hour of frustration

1 Like

Just wanted to say thanks for the pathRewrite , because I couldn’t figure out how to override that path!

1 Like

Glad this thread got you on the right track-- happy building :rocket: