I have a React front-end that makes requests to the github API. I initially wrote the project to make use of an Express proxy server to help keep some environment variables private. I have the front-end of the project deployed on Netlify and since my little server only has 2 routes (for fetching a user's profile and repos, independently) I didn't think it made sense to deploy the back end somewhere else, and figured I'd just use the serverless-http package to wrap my server and make it essentially a Netlify Lambda function.
The issue: I have the project working locally when I set the NODE_ENV to both production and development, with a slightly modified webpack.config proxy setting (that is to say, it's slightly different than I need it to be to run it locally directly using the Express server). Further, I can query the routes on the deployed project with Postman and it appears to correctly return the data from the endpoint at the GitHub API. At present, the frontend is showing an error that indicates that there is a JSON parsing error (unexpected character at column 1)... I have had this error in local development when trying to make this work there and it was because the request initiating on the frontend was not making its way to the proxy server. Realistically my error handling for the project is dog-shit and so I don't have more info at present.
As the code is written, requests are still proxied through the serverless functions.
My feeling is this is an issue with how I have deployed things. I've spent a few hours going through the docs and I really don't know what I've done wrong, so I'm hoping someone familiar with Netlify and their implementation of serverless functions has an idea about why this isn't working.
I'm using Node v11.10.0.
For reference, the Express API I wrote is as follows:
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const serverless = require('serverless-http');
require('dotenv').config();
// Initialize app
const app = express();
const router = express.Router();
const port = process.env.PORT || 3000;
// Configuration
app.use(bodyParser.json({ type: 'application/json' }));
const routerBasePath = (process.env.NODE_ENV === 'development')
? `/index`
: `/.netlify/functions/index`
app.use(routerBasePath, router);
console.warn('Node environment: ', process.env.NODE_ENV)
// Constants
const id = process.env.GITHUB_CLIENT_ID;
const secret = process.env.GITHUB_CLIENT_SECRET;
const params = `?client_id=${id}&client_secret=${secret}`
// Route(s)
router.post('/profile', (req, res) => {
const { username } = req.body;
const endpoint = `https://api.github.com/users/${username}${params}`;
axios({
method: 'get',
url: endpoint,
})
.then(response => {
res.status(200)
res.send(response.data)
})
.catch(error => {
console.log('Error with Axios profile res: ', error)
res.send({ error })
})
return
})
router.post('/repos', (req, res) => {
const { username } = req.body;
const endpoint = `https://api.github.com/users/${username}/repos${params}&per_page=100`;
axios({
method: 'get',
url: endpoint,
})
.then(response => {
res.status(200)
res.send(response.data)
})
.catch(error => {
console.log('Error with Axios repos res: ', error)
res.send({ error })
})
return
})
// DJ, spin that shit:
app.listen(port, () => console.log(`App is running on port ${port}`));
process.on('SIGINT', () => {
console.log(`Process on port ${port} successfully terminated.`);
process.exit();
})
// Serverless
// app.use('/.netlify/functions/index', router);
module.exports.handler = serverless(app);
The relevant portion of webpack.config:
mode: process.env.NODE_ENV === 'production'
? 'production'
: 'development',
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/api': '.netlify/functions/index' }
}
},
historyApiFallback: true
},
The relevant code from the front-end:
function getProfile(username) {
return fetch('/api/profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ 'username': username })
})
.then(res => {
return res.json()
})
.then(profile => {
if (profile.message) {
throw new Error(getErrorMsg(profile.message, username))
}
return profile
})
}
function getRepos(username) {
return fetch('/api/repos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ 'username': username })
})
.then(res => {
return res.json()
})
.then(repos => {
if (repos.message) {
throw new Error(getErrorMsg(repos.message, username))
}
return repos
})
}
And my package.json:
"scripts": {
"build": "NODE_ENV='production' webpack",
"build-for-windows": "SET NODE_ENV='production' && webpack",
"build-for-netlify": "NODE_ENV='production' webpack && npm run build:lambda",
"start": "concurrently \"webpack-dev-server\" \"npm run start:lambda\"",
"server": "nodemon server/index.js",
"start:lambda": "NODE_ENV='production' netlify-lambda serve ./server",
"build:lambda": "netlify-lambda build ./server"
},
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"syntax-dynamic-import"
]
},
"proxy": "http://localhost:3000"
[build]
command = "npm install && npm run build-for-netlify"
functions = "functions"
publish = "dist"
To test this locally, I've run the npm run start script, changing the NODE_ENV between production and development. When it's in development mode, the routes are accessible via /index (as seen in the express API) and when it's in production they're accessed via ./netlify/functions/index, and the behavior is as expected both in the local environment and from Postman.
I'm fairly new to setting up this kind of environment so I'm sure there are some dumb errors but I'm at a loss for what to search for at this point. I did see something about needing to bundle your Netlify Functions for deploy but I am not sure if the build step handles that or not.
Thanks in advance all, this has been a headscratcher for me.
EDIT1: Added netlify.toml configuration as well.
EDIT2: Added Node version info.
EDIT3: Adding info about error