I have a React & Express application but I can't deploy this app to Heroku.
My file structure is like that:
- client
- public
- src
- package.json
- index.js
- package.json
client/package.json:
{
"name": "client",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:5000",
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"styled-components": "^5.3.3",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"babel-plugin-macros": "^3.1.0",
"babel-plugin-styled-components": "^2.0.2"
}
}
package.json
{
"name": "moviefy",
"version": "1.0.0",
"description": "",
"main": "index.js",
"engines": {
"node": "14.17.3",
"npm": "6.14.11"
},
"cacheDirectories": [
"node_modules",
"client/node_modules"
],
"scripts": {
"start": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"",
"server": "nodemon index.js",
"client": "cd client && npm run start",
"postinstall": "cd client && npm install",
"build": "NODE_ENV=production cd client && npm install && npm run build",
"start-server": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.24.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"react-component-export-image": "^1.0.6",
"react-router-dom": "^5.2.0"
},
"devDependencies": {
"concurrently": "^6.4.0",
"nodemon": "^2.0.15"
}
}
index.js:
require("dotenv").config();
const express = require("express");
const axios = require("axios");
const app = express();
const path = require("path");
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;
const REDIRECT_URI = process.env.REDIRECT_URI;
const FRONTEND_URI = process.env.FRONTEND_URI;
const PORT = process.env.PORT || 5000;
app.use(express.static(path.resolve(__dirname, "./client/build")));
app.get("/", (req, res) => {
res.send("Hello");
});
/**
* Generates a random string containing numbers and letters
* @param {number} length The length of the string
* @return {string} The generated string
*/
const generateRandomString = (length) => {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
const stateKey = "spotify_auth_state";
app.get("/login", (req, res) => {
const state = generateRandomString(16);
res.cookie(stateKey, state);
const scope = [
"user-read-private",
"user-read-email",
"user-top-read",
"playlist-read-private",
"playlist-read-collaborative",
"user-top-read",
"user-follow-read",
].join(" ");
const query = {
client_id: CLIENT_ID,
response_type: "code",
redirect_uri: REDIRECT_URI,
state: state,
scope: scope,
};
let queryParam = new URLSearchParams(query);
res.redirect(`https://accounts.spotify.com/authorize?${queryParam}`);
});
app.get("/callback", (req, res) => {
const code = req.query.code || null;
const data = {
grant_type: "authorization_code",
code: code,
redirect_uri: REDIRECT_URI,
};
axios({
method: "post",
url: "https://accounts.spotify.com/api/token",
data: new URLSearchParams(data),
headers: {
"content-type": "application/x-www-form-urlencoded",
Authorization: `Basic ${new Buffer.from(
`${CLIENT_ID}:${CLIENT_SECRET}`
).toString("base64")}`,
},
})
.then((response) => {
if (response.status === 200) {
const { refresh_token, access_token, expires_in } = response.data;
const queryParams = new URLSearchParams({
access_token,
refresh_token,
expires_in,
});
res.redirect(`${FRONTEND_URI}/?${queryParams}`);
} else {
res.redirect(`/?${JSON.stringify({ error: "invalid_token" })}`);
}
})
.catch((error) => {
res.send(error);
});
});
app.get("/refresh_token", (req, res) => {
const { refresh_token } = req.query;
axios({
method: "post",
url: "https://accounts.spotify.com/api/token",
data: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refresh_token,
}),
headers: {
"content-type": "application/x-www-form-urlencoded",
Authorization: `Basic ${new Buffer.from(
`${CLIENT_ID}:${CLIENT_SECRET}`
).toString("base64")}`,
},
})
.then((response) => {
res.send(response.data);
})
.catch((error) => {
res.send(error);
});
});
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "./client/build", "index.html"));
});
app.listen(PORT, () => {
console.log(`listening on ${PORT}`);
});
And I got this error when I run "heroku logs" command in my console:
2021-12-13T20:43:20.583181+00:00 heroku[web.1]: Starting process with command `npm start`
2021-12-13T20:43:22.310970+00:00 app[web.1]:
2021-12-13T20:43:22.311002+00:00 app[web.1]: > moviefy@1.0.0 start /app
2021-12-13T20:43:22.311005+00:00 app[web.1]: > concurrently --kill-others-on-fail "npm run server" "npm run client"
2021-12-13T20:43:22.311006+00:00 app[web.1]:
2021-12-13T20:43:22.314863+00:00 app[web.1]: sh: 1: concurrently: not found
2021-12-13T20:43:22.319859+00:00 app[web.1]: npm ERR! code ELIFECYCLE
2021-12-13T20:43:22.320034+00:00 app[web.1]: npm ERR! syscall spawn
2021-12-13T20:43:22.320108+00:00 app[web.1]: npm ERR! file sh
2021-12-13T20:43:22.320194+00:00 app[web.1]: npm ERR! errno ENOENT
2021-12-13T20:43:22.322717+00:00 app[web.1]: npm ERR! moviefy@1.0.0 start: `concurrently --kill-others-on-fail "npm run server" "npm run client"`
I tried to use "client/build" structure in index.js file but it didn't work too. Also, I tried "heroku-postbuild" command in package.json file but I still got same error in heroku.