I’ve built an app with Express and React which has GET and POST routes which work perfectly locally. I’ve deployed through Heroku and nothing is working anymore! I’m just getting a 404 error. I’ve tried to create a static.json file which hasn’t worked, although I didn't use Create-React-App to set it up anyway.
This is my index.js:
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const app = express()
const router = require('./config/router')
const { port, dbURI } = require('./config/environment')
const errorHandler = require('./lib/errorHandler')
const logger = require('./lib/logger')
app.use(express.static(`${__dirname}/public`))
app.use(express.static(`${__dirname}/dist`))
mongoose.connect(dbURI, { useNewURLParser: true, useCreateIndex: true})
app.use(bodyParser.json())
app.use(logger)
app.use('/api', router)
app.use(errorHandler)
app.listen(port, () => console.log(`Up and running on port ${port}`))
module.exports = app
router.js
const vacancies = require('../controllers/vacancies')
router.route('/vacancies')
.get(vacancies.index)
.post(vacancies.create)
router.route('/vacancies/:id')
.get(vacancies.show)
module.exports = router
controller:
//tested in insomnia - works
function indexRoute(req, res, next) {
Vacancy
.find(req.query)
.then(vacancies => res.status(200).json(vacancies))
.catch(next)
}
//tested in insomnia - works
function showRoute(req, res, next) {
Vacancy
.findById(req.params.id)
.then(vacancy => res.status(200).json(vacancy))
.catch(next)
}
//tested in insomnia - works
function createRoute(req, res) {
Vacancy
.create(req.body)
.then(vacancy => res.status(201).json(vacancy))
.catch(err => res.status(422).json(err))
}
module.exports = {
index: indexRoute,
show: showRoute,
create: createRoute
}
and lastly, environment file:
const port = process.env.PORT || 4000
const dbURI = process.env.MONGODB_URI || `mongodb://localhost/dxw-job-board-${process.env.NODE_ENV || 'dev'}`
module.exports = { port, dbURI }
This is for a code test for a job I really want to super anxious about it not working properly - any help would be greatly appreciated!
I ran into this and pulled my hair out when I first tried to deploy an app on Heroku (was my first experience with any sort of deployment actually). On the production server (Heroku) you should be serving the static files from the build directory, not the /public directory.
Something like this:
if (process.env.NODE_ENV === "production") {
// react build directory /public
} else {
// Local production env react /public dir
}
This should tell Node that it's on Heroku (or whatever platform you use) and needs to use the production build and not the development version.
Hope that solves the issue for you.
Related
I have a basic MERN stack application. On the app when I do npm start, it runs fine and functions as expected. However, after doing a npm run build, and running serve -s build, the app fails with the following message -
TypeError: a.map is not a function
at He (Contacts.js:27:16)
at wl (react-dom.production.min.js:166:137)
at Pi (react-dom.production.min.js:215:270)
at wu (react-dom.production.min.js:291:202)
at ys (react-dom.production.min.js:279:389)
at vs (react-dom.production.min.js:279:320)
at ms (react-dom.production.min.js:279:180)
at as (react-dom.production.min.js:270:88)
at rs (react-dom.production.min.js:267:429)
at x (scheduler.production.min.js:13:203)
Here is my express main server.js file -
const express = require('express')
const cors = require('cors')
const app = express()
const connectDB = require('./config/db')
app.use(cors())
//Connect database
connectDB()
//Init Middleware
app.use(express.json({ extended: false }))
app.get('/', (req, res) =>
res.json({ msg: 'Welcome to the Digital RoloDex API'}))
//Define Routes
app.use('/api/users', require('./routes/users'))
app.use('/api/auth', require('./routes/auth'))
app.use('/api/contacts', require('./routes/contacts'))
const PORT = process.env.PORT || 4444
app.listen(PORT, () => console.log(`app running on ${PORT}`))
Here is the file where the error is coming from -
import React, { useEffect } from 'react'
import ContactItem from './ContactItem'
import Spinner from '../layout/Spinner'
import { getContacts, useContacts } from '../context/contact/ContactState'
const Contacts = () => {
const [ contactState, contactDispatch ] = useContacts()
const { contacts, filtered, loading } = contactState
useEffect(() => {
getContacts(contactDispatch)
}, [contactDispatch])
if (contacts === 0 && !loading) {
return <h4>Please add a contact</h4>
}
return (
<div>
{ contacts !== null && !loading ?
filtered.length !== 0 ?
filtered.map((contact) => (
<ContactItem key={contact._id} contact={contact} />
))
: contacts.map((contact) => (
<ContactItem key={contact._id} contact={contact} />
))
:
<Spinner />
}
</div>
)
}
export default Contacts
I found two stackoverflow questions with the same problem. I applied their tips, but it is not helping either. Any help would be greatly appreciated.
https://stackoverflow.com/questions/65069848/typeerror-a-map-is-not-a-function
https://stackoverflow.com/questions/64362803/map-is-not-a-function-when-getting-react-files-from-build-folder
I want to answer my own question just in case someone runs into the same error. Foremost, in a static build, any variables, components, functions, etc. are going to be renamed with abbreviated letters. Which is why it is stating "a".map is not a function.
In addition, my backend and client were in two separate folders. So my front end wasn't calling to my back end properly. I uninstalled cors since this would make my users' information vulnerable across the web. Instead, I put my backend folder and files in the root and kept the client in its own folder. From there, I cd. into my client folder and did an npm run build. The error went away and was able to call back to the express api.
I have never deployed a MERN app to production before. This is going to be my first attempt and I am planning to deploy the app to digital ocean.
So, I have prepared my MERN app for deployment by following the instructions in a Udemy course. My app structure is as follows:
The following are the main changes that I have made to my application:
Because there will be no server created by create-react-app in production, I have written the production routing logic inside server/index.js that essentially says that for any routes not managed by app.use("/api/users", userRoutes) & app.use("/api/download", downloadRoutes), the backend server will server the index.html file inside the client/build folder, which I have created by running the command: npm run build.
server/index.js
const express = require("express");
const path = require("path");
const dotenv = require("dotenv");
const colors = require("colors");
const connectDB = require("./config/db");
const {
notFound,
globalErrorHandler,
} = require("./middleware/errorMiddleware");
const userRoutes = require("./routes/userRoutes");
const downloadRoutes = require("./routes/downloadRoutes");
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use("/api/users", userRoutes);
app.use("/api/download", downloadRoutes);
// Routing logic in production
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "/client/build")));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
});
}
app.use(notFound);
app.use(globalErrorHandler);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`.yellow.bold);
});
I have changed the process.env.NODE_ENV to production inside the .env file.
After the above-mentioned changes, when I run "npm start" (starts only the backend server) (not "npm run dev" that concurrently starts both the frontend and backend server) and visit http://localhost:5000, I should see my app. Instead, I see the following error.
What am I doing wrong?
As you can see in the error message, Express is looking for an index.html inside server/client/build which does not exist. Fix the path.
app.use(express.static(path.join(__dirname, '../client/build')))
You need to move your entire client folder inside the server and then add the following in the index.js file of the server:
if (process.env.NODE_ENV === "production") {
app.use(express.static("front/build"));
const path = require('path')
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, 'front', 'build',
'index.html'))
})
}
Make sure to run npm run build first.
const port = process.env.PORT || ("http://localhost:3002")
const postUrl=`${port}/post`;
addData=()=>{
console.log(postUrl)
console.log(process.env,"AS")
Axios.post(postUrl,this.state.form)
.then((response)=>{
this.setState({errorMessage:"",successMessage:response.data})})
.catch((err)=>{
this.setState({successMessage:"",errorMessage:err.response.data.message})
})
}
When I am calling the backend in the production process.env.PORT is blank.
(process.env.NODE_EV= production which is absolutely correct exactly like in the backend)
My backend is completely fine as it getting the process.env.PORT correctly.
But my frontend is not getting the process.env.PORT that's why it keeps calling the other address("http://localhost:3002").
App will completely work fine if I keep open my local machine backend because the "http://localhost:3002"
is available to serve. But in production, Heroku keeps changing the process.env.PORT which is showing its value in the backend, not in the frontend
How can I make my frontend to call my backend server properly in production??
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const port = process.env.PORT || 3002;
const cors = require("cors");
const path=require("path");
const routing = require("./routing/route");
require('dotenv').config();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use("/",routing);
app.use(function (err, req, res, next) {
console.log("hii")
res.status(500).send({ message: err.message });
});
if(process.env.NODE_ENV ==="production"){
app.use(express.static("client/build"));
app.get("*",(req,res)=>{
res.sendFile(path.resolve((__dirname,"client","build","index.html")));
});
}
app.listen(port, () => {
console.log(` Server is started at http://localhost:${port}`);
});
server file
If your React application is served from your Node.JS application as you said, you could just use window.location. window.location is an object that stores statistics about the current page that the user is on, and you could use that to construct a URL and send the server a request, like so:
// This URL uses a template literal, which is a new feature of ES6.
// All but Internet Explorer supports it.
// This is using window.location.protocol, which is either `http:` or `https:`,
// depending on the protocol that the page was loaded with. window.location.host
// is the host that the page was loaded from, with the port number.
const postUrl =
`${window.location.protocol}//${window.location.host}/post`;
// And then requesting with the URL.
addData = () => {
console.log(postUrl);
console.log(process.env, "AS");
Axios.post(postUrl, this.state.form)
.then((response) => {
this.setState({errorMessage: "",successMessage: response.data});
})
.catch((err) => {
this.setState({successMessage: "",errorMessage: err.response.data.message});
});
}
I'm running a two container docker setup; NextJS for the public facing web, and Django backend for admins to add content. The routes are nicely working with getInitialProps which fetches the added content. As the content references static images, they are connected via docker volumes (./static/media on django container and ./public/media on nextjs container).
However when a new image appears in ./public/media, the started NextJs server returns a 404 response for those images.
// EDITED SOLUTION: As suggested by #Pierfrancesco
The workaround solution is to create a custom server which dynamically serves those files
// server.js
const express = require('express')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
server.get('/media/images/*', (req, res) => {
// Disallow travelling up in the file tree
let target = req.originalUrl.replace("..", "")
return res.sendFile(__dirname + '/public' + target);
})
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
Could this be a feature request or is this a bug in NextJS?
General question.
I am looking to create a console-like application with Node.js that also has api capabilities. It will have timed task that will run on a schedule but also be available to send http request to. My problem is that while I've created the api. I don't know how to go about creating basic task to be performed on a schedule while the server stays running in case http request is made for different types of task.
Main idea is to have the task schedule to run while also keeping the server running and waiting for http request
Folder paths
folders
-controllers
-models
-modules
-node-modules
-routes
general files
app.js
package-lock.json
package.json
server.js
task.js
Proposed area for Scheduled task to be performed:
index.js
const { checkTablesForData, requestRoutine, runNecessaryUpdate } = require('./task')
function Main() {
...
\\task to be executed
\\Check the table for records to be processed
let obj = checkTablesForData();
\\Send third party request with object data. ie - id's
obj.map( id => {
\\call request routine
requestRoutine(id)
})
\\Run final process
runNecessaryUpdate(id)
}
task.js
checkTablesForData(){
...
}
requestRoutine(id){
...
}
runNecessaryUpdate(id){
...
}
module.exports = { runNecessaryUpdate, checkTablesForData, requestRoutine }
Code for api setup
app.js
const express = require('express')
const path = require('path')
const app = express() //, api = express();
const cors = require('cors');
const bodyParser = require('body-parser');
app.get('/', function(req, res){
res.redirect('./api')
})
const api = require('./routes/api');
app.use('/api', api)
module.exports = app
server.js
const app = require('./app');
const port = process.env.PORT || 3000;
app.listen(port, 'localhost', () => {
console.log(`Server is running on port ${port}`);
});
api.js
const express = require('express');
const router = express.Router();
//Controller Modules
const controller = require('../controllers/homeController');
//Routes
router.get('/request/:id', controller.post)
module.exports = router;
controller.js
//Send request to 3rd party api
export.post = function(req, res){
const options = {
....
}
return request(options)
.then( response => {
...
}
.catch( error => {
\\error routine
}
}
Advice is very much needed.
Thanks!