Reputation: 9
I've been stuck on this error for a couple days now, so here I am looking for some insight. I have a PERN stack application that I am trying to deploy for the first time to Heroku. My app builds seemingly fine. My app runs perfectly fine locally in development. I can run it serving static files from react build and I can run the front and backend on two ports and it works. I think the issue has too do with how Sequelize-CLI tries to parse the DATABASE_URL config variable that Heroku sets in production. If I log process.env.DATABASE_URL I get the URI string (noted in error stack below) from Heroku.
I keep getting this error stack:
enter code here2020-07-19T00:56:44.000000+00:00 app[api]: Build succeeded
2020-07-19T00:57:10.671364+00:00 heroku[web.1]: Starting process with command `npm start`
2020-07-19T00:57:14.245382+00:00 app[web.1]:
2020-07-19T00:57:14.245410+00:00 app[web.1]: > [email protected] start /app
2020-07-19T00:57:14.245411+00:00 app[web.1]: > node server.js
2020-07-19T00:57:14.245411+00:00 app[web.1]:
// this is the console.log(process.env.DATABASE_URL)
2020-07-19T01:09:25.213184+00:00 app[web.1]: postgres://<user>:<password><omitted-info>:<port>/<app>
2020-07-19T00:57:14.903836+00:00 app[web.1]: internal/validators.js:107
2020-07-19T00:57:14.903838+00:00 app[web.1]: throw new ERR_INVALID_ARG_TYPE(name, 'string', value);
2020-07-19T00:57:14.903839+00:00 app[web.1]: ^
2020-07-19T00:57:14.903839+00:00 app[web.1]:
2020-07-19T00:57:14.903841+00:00 app[web.1]: TypeError [ERR_INVALID_ARG_TYPE]: The "url" argument must be of type string. Received type undefined
2020-07-19T00:57:14.903841+00:00 app[web.1]: at validateString (internal/validators.js:107:11)
2020-07-19T00:57:14.903841+00:00 app[web.1]: at Url.parse (url.js:155:3)
2020-07-19T00:57:14.903842+00:00 app[web.1]: at Object.urlParse [as parse] (url.js:150:13)
2020-07-19T00:57:14.903843+00:00 app[web.1]: at new Sequelize (/app/node_modules/sequelize/lib/sequelize.js:187:28)
2020-07-19T00:57:14.903843+00:00 app[web.1]: at Object.<anonymous> (/app/models/index.js:14:15)
2020-07-19T00:57:14.903844+00:00 app[web.1]: at Module._compile (internal/modules/cjs/loader.js:759:30)
2020-07-19T00:57:14.903844+00:00 app[web.1]: at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
2020-07-19T00:57:14.903845+00:00 app[web.1]: at Module.load (internal/modules/cjs/loader.js:628:32)
2020-07-19T00:57:14.903845+00:00 app[web.1]: at Function.Module._load (internal/modules/cjs/loader.js:555:12)
2020-07-19T00:57:14.903845+00:00 app[web.1]: at Module.require (internal/modules/cjs/loader.js:666:19)
2020-07-19T00:57:14.903846+00:00 app[web.1]: at require (internal/modules/cjs/helpers.js:16:16)
2020-07-19T00:57:14.903846+00:00 app[web.1]: at Object.<anonymous> (/app/server.js:11:16)
2020-07-19T00:57:14.903847+00:00 app[web.1]: at Module._compile (internal/modules/cjs/loader.js:759:30)
2020-07-19T00:57:14.903847+00:00 app[web.1]: at Object.Module._extensions..js (internal/modules/cjs/loader.js:770:10)
2020-07-19T00:57:14.903847+00:00 app[web.1]: at Module.load (internal/modules/cjs/loader.js:628:32)
2020-07-19T00:57:14.903848+00:00 app[web.1]: at Function.Module._load (internal/modules/cjs/loader.js:555:12)
2020-07-19T00:57:14.903848+00:00 app[web.1]: at Function.Module.runMain (internal/modules/cjs/loader.js:826:10)
2020-07-19T00:57:14.903849+00:00 app[web.1]: at internal/main/run_main_module.js:17:11
2020-07-19T00:57:14.915610+00:00 app[web.1]: npm ERR! code ELIFECYCLE
2020-07-19T00:57:14.916028+00:00 app[web.1]: npm ERR! errno 1
2020-07-19T00:57:14.917598+00:00 app[web.1]: npm ERR! [email protected] start: `node server.js`
2020-07-19T00:57:14.917834+00:00 app[web.1]: npm ERR! Exit status 1
2020-07-19T00:57:14.918113+00:00 app[web.1]: npm ERR!
2020-07-19T00:57:14.918349+00:00 app[web.1]: npm ERR! Failed at the [email protected] start script.
2020-07-19T00:57:14.918598+00:00 app[web.1]: npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
2020-07-19T00:57:14.952443+00:00 app[web.1]:
2020-07-19T00:57:14.952709+00:00 app[web.1]: npm ERR! A complete log of this run can be found in:
2020-07-19T00:57:14.952798+00:00 app[web.1]: npm ERR! /app/.npm/_logs/2020-07-19T00_57_14_920Z-debug.log
2020-07-19T00:57:15.047526+00:00 heroku[web.1]: Process exited with status 1
2020-07-19T00:57:15.094041+00:00 heroku[web.1]: State changed from starting to crashed
2020-07-19T00:57:15.951511+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=locals-deploy.herokuapp.com request_id=f27a1f1b-5d62-4d98-9383-6d5e46242789 fwd="71.92.89.136" dyno= connect= service= status=503 bytes= protocol=https
Now if I try to run a db:migrate on heroku in production mode I get another error regarding the URL.
heroku run sequelize db:migrate --env production --app locals-deploy
Error:
Running sequelize db:migrate on ⬢ locals-deploy... up, run.7436 (Free)
Sequelize CLI [Node: 12.0.0, CLI: 5.5.1, ORM: 5.22.3]
Loaded configuration file "config/config.js".
Using environment "production".
ERROR: Error parsing url: undefined
package.json
{
"name": "locals",
"version": "1.0.0",
"description": "A place for travelers to meet locals.",
"main": "server.js",
"scripts": {
"start": "node server.js",
"heroku-postbuild": "cd client && npm install && npm run build"
},
"author": "Name Name",
"license": "MIT",
"dependencies": {
"axios": "^0.19.2",
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"cloudinary": "^1.22.0",
"config": "^3.3.1",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-fileupload": "^1.1.7-alpha.3",
"express-validator": "^6.5.0",
"jsonwebtoken": "^8.5.1",
"nodemon": "^2.0.4",
"pg": "^8.2.1",
"pg-hstore": "^2.3.3",
"sequelize": "^5.21.11",
"sequelize-cli": "^5.5.1"
},
"engines": {
"node": "12.0.0"
}
}
server.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const PORT = process.env.PORT || 5000;
const upload = require('express-fileupload');
app.use(upload({useTempFiles: true}));
const cors = require('cors');
const path = require('path');
const models = require('./models');
// variable to enable global error logging
const enableGlobalErrorLogging =
process.env.ENABLE_GLOBAL_ERROR_LOGGING === 'true';
// Enable All CORS Requests
app.use(cors());
//Init Middleware
app.use(bodyParser.json());
app.use(
bodyParser.urlencoded({
extended: true,
})
);
//app.use(express.static(path.join(__dirname, 'client/build')));
console.log(process.env.NODE_ENV);
//if running in production mode then it serves static files from build in client
if (process.env.NODE_ENV === 'production') {
//points to index.js in client
app.use(express.static(path.join(__dirname, 'client/build')));
}
//Routes
app.use('/users', require('./routes/users'));
app.use('/auth', require('./routes/auth'));
app.use('/posts', require('./routes/posts'));
app.use('/api/profile', require('./routes/profile'));
app.use('/adventure', require('./routes/adventure'));
app.use('/review', require('./routes/review'));
app.use('/favorites', require('./routes/favorites'));
app.use('/upload', require('./routes/uploadImage'));
//catch all method redirects to build folder
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'client/build', 'index.html'));
});
// send 404 if no other route matched
app.use((req, res) => {
res.status(404).json({
message: 'Route Not Found',
});
});
// setup a global error handler
app.use((err, req, res, next) => {
console.log('I am catching the error');
if (enableGlobalErrorLogging) {
console.error(`Global error handler: ${JSON.stringify(err.stack)}`);
}
if (err.name === 'SequelizeUniqueConstraintError') {
return res.status(400).json({
errors: ['Email for user already exists'],
});
}
// if (err.name === 'SequelizeDatabaseError') {
// return res.status(404).json({errors: 'Oh no! Page not found.'});
// }
res.status(err.status || 500).json({
message: err.message,
name: err.name,
error: {},
});
});
//Sets Port and Listens
return models.sequelize.sync().then((result) => {
app.listen(process.env.PORT || 5000, () => {
console.log(`App running on port ${process.env.PORT || 5000}.`);
});
});
config.js
if (process.env.NODE_ENV !== 'production') require('dotenv').config();
//PG_HOST = 127.0.0.1
console.log(process.env.DATABASE_URL);
module.exports = {
development: {
username: process.env.PG_USER,
password: process.env.PG_PASSWORD,
database: process.env.PG_DATABASE,
host: process.env.PG_HOST,
dialect: 'postgres',
port: process.env.PG_PORT,
operatorsAliases: 0,
},
test: {
username: process.env.PG_USER,
password: process.env.PG_PASSWORD,
database: process.env.PG_DATABASE,
host: process.env.PG_HOST,
dialect: 'postgres',
port: process.env.PG_PORT,
operatorsAliases: 0,
},
production: {
use_env_variable: process.env.DATABASE_URL,
dialect: 'postgres',
dialectOptions: {
ssl: true,
},
},
};
Procfile
web: node server.js
EDIT I made a bug report and I actually think I may have fixed the issue in the node_modules package where the error was being thrown. Now I have to figure out how to make that change inside Heroku since we don't commit node_modules to see if it works. I can run it just find locally now in production mode without reproducing the error.
Bug Report if anyone is interested
https://github.com/sequelize/sequelize/issues/12528
Upvotes: 1
Views: 3491
Reputation: 9
So as it turns out there is an error in my node_modules/sequelize package. I traced into into /node_modules/sequelize/lib/sequelize.js:187:28
. Whats happening is that my config file is submitting the URI string under production from process.env.DATABASE_URL
and it is attached to the username
parameter, or index 1. The issue is how url.parse
is written and what argument is passed. If the user submits the DATABASE_URL URI it passed as if it is an array, however, the first argument is always undefined since its username that it is attached to, which is index 1. I changed the parsing arguments to const urlParts = url.parse(options.use_env_variable, true);
and this seemed to clear up all my issues by directly taking the URI to be parsed instead of worrying about index values.
constructor(database, username, password, options) {
let config;
if (arguments.length === 1 && typeof database === 'object') {
// new Sequelize({ ... options })
options = database;
config = _.pick(options, 'host', 'port', 'database', 'username', 'password');
} else if (
(arguments.length === 1 && typeof database === 'string') ||
(arguments.length === 2 && typeof username === 'object')
) {
// new Sequelize(URI, { ... options }) <--this was already commented out
config = {};
options = username || {};
// url.parse wants to access the object like an array, however, first item is always undefined and it
// needs to access the use_env_variable, which is attached to username argument
// const urlParts = url.parse(options.use_env_variable, true);
const urlParts = url.parse(arguments[0], true);
options.dialect = urlParts.protocol.replace(/:$/, '');
options.host = urlParts.hostname;
...
After that I downloaded npm patch-package and I pushed my patched package to git and heroku and it works.
Upvotes: 0
Reputation: 71
Try this in your config file
production: {
use_env_variable: "DATABASE_URL",
dialect: 'postgres',
dialectOptions: {
ssl: true,
},
},
Since you are using use_env_variable
you should be able to just have the name of the variable as the value.
Upvotes: 1