简体   繁体   English

React + Express 应用程序上的 API 路由在 Heroku 上给出 404

[英]API routes on react + express app giving 404 on Heroku

So I built a simple mern app that works fine on my local environment, but when deploying to Heroku, it serves the react app fine but 404s on the API calls.所以我构建了一个简单的 mern 应用程序,它可以在我的本地环境中正常工作,但是在部署到 Heroku 时,它可以很好地为 React 应用程序提供服务,但在 API 调用上却出现了 404s。 I can't seems to figure out the issue.我似乎无法弄清楚这个问题。 I'm sending requests using Axios.我正在使用 Axios 发送请求。 I checked the network requests and they all look good, but still come back 404. testing in postman also returned the same error.我检查了网络请求,它们看起来都不错,但仍然返回 404。在 postman 中测试也返回相同的错误。

Here's the server code... Any idea why this is failing?这是服务器代码......知道为什么会失败吗?

const express = require('express');
const path = require('path');
const Axios = require('axios');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
require('dotenv').config();

const PORT = process.env.PORT || 8080;
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/book';

const app = express();

// Define middleware here
app.use(express.urlencoded({ extended: true }));
app.use(bodyParser.json());
// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === 'production') {
  app.use(express.static('client/build'));
}

const { Schema } = mongoose;
const bookSchema = new Schema({
  info: Schema.Types.Mixed,
});
const Book = mongoose.model('Book', bookSchema);

app.post('/api/search', (req, res) => {
  Axios.get(
    `https://www.googleapis.com/books/v1/volumes?q=${req.body.term}`
  ).then(books => res.json(books.data.items));
});
app.post('/api/save', (req, res) => {
  const newBook = new Book({ info: req.body.book });
  newBook.save(err => {
    if (err) res.json(err);
    res.json({ status: true });
  });
});
app.post('/api/unsave', (req, res) => {
  Book.findByIdAndRemove(req.body.book._id, err => {
    if (err) res.json(err);
    res.json({ status: true });
  });
});
app.get('/api/saved', (req, res) => {
  Book.find({}, (err, books) => {
    if (err) res.json(err);
    res.json(books);
  });
});
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, './client/build/index.html'));
});

mongoose.connect(mongoUri, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log('connected');
});

app.listen(PORT, () => {
  console.log(`🌎 ==> API server now on port ${PORT}!`);
});

and here's my package.json这是我的 package.json

{
    "name": "google-book",
    "version": "1.0.0",
    "description": "",
    "main": "server.js",
    "scripts": {
        "start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev",
        "start:prod": "node server.js",
        "start:dev": "concurrently \"nodemon --ignore 'client/*'\" \"npm run client\"",
        "client": "cd client && npm run start",
        "install": "cd client && npm install",
        "build": "cd client && npm run build",
        "heroku-postbuild": "npm run build"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "axios": "^0.19.2",
        "body-parser": "^1.19.0",
        "concurrently": "^5.1.0",
        "dotenv": "^8.2.0",
        "express": "^4.17.1",
        "mongodb": "^3.5.3",
        "mongoose": "^5.9.1"
    }
}

My react routes in case that would be an issue我的反应路线以防万一

return (
    <div className="app">
      <Header />
      <Router>
        <Route exact path='/'>
          <Searchbar search={search} setSearch={setSearch} />
          {!search.term ? (
            <div className="message">
              <p>Search for a book or whatever</p>
            </div>
          ) : <SearchList results={search.results} />}
        </Route>
        <Route path='/saved'>
          <h2 className="title">Saved Books</h2>
          <SavedList />
        </Route>
        <Footer />
      </Router>
    </div>
  );

Final results:最终结果:


Explanation:解释:

So, even when running this locally I was getting 404's - the issue turned out to be how you were starting the app.因此,即使在本地运行此程序时,我也会收到 404 错误 - 结果证明您是如何启动应用程序的。

You only need to start the server, and not the client.您只需要启动服务器,而不是客户端。 It looks like you were starting the "built-in" server that comes with create-react-app ... so, your server was never actually accepting requests as your front end was running on port 3000 and your backend was running on whatever port you had set in .env .看起来您正在启动create-react-app附带的“内置”服务器......因此,您的服务器实际上从未接受请求,因为您的前端在端口 3000 上运行而您的后端在任何端口上运行你已经在.env设置了。

Due to how you have axios sending requests (just using the current URL, which was running on the built in create-react-app port, not your server port), it was essentially sending requests to the wrong port.由于axios发送请求的方式(仅使用在内置create-react-app端口上运行的当前 URL,而不是您的服务器端口),它本质上是将请求发送到错误的端口。

This is something I should have thought of last night, since I remembered seeing your Heroku app was using the development build of React (via the Firefox React extension) - that should have been a red flag.这是我昨晚应该想到的事情,因为我记得看到你的 Heroku 应用程序正在使用 React 的开发版本(通过 Firefox React 扩展)——这应该是一个危险信号。

I have added 2 new npm scripts: npm run begin and npm start (renamed the original npm start to npm run start:original . npm run begin properly builds your front end, and then starts your backend afterwards. This is ultimately what resolved the issue . I also had NODE_ENV=production while testing locally.我添加了 2 个新的npm脚本: npm run beginnpm start (将原始npm start重命名为npm run start:original npm run begin正确构建您的前端,然后启动您的后端。这最终解决了问题. 我在本地测试时也有NODE_ENV=production

I also removed npm heroku-postbuild as it is not needed.我还删除了npm heroku-postbuild因为它不需要。


Code Changes:代码更改:

After getting this to work, I noticed there was something wrong with your front end code - a loop was sending the request over and over - I kept seeing the below info logged to the console.让它工作后,我注意到你的前端代码有问题 - 一个循环一遍又一遍地发送请求 - 我一直看到以下信息记录到控制台。 So I also resolved that using the code further down (I did not delete any code, I just commented the code out, so that you can see the changes I made).所以我还解决了进一步使用代码的问题(我没有删除任何代码,我只是将代码注释掉,以便您可以看到我所做的更改)。

I don't know where you're using Mongo at but I tested this using Atlas - I had issues talking to the database after deploying to Heroku, so I also had to change how you were connecting to the database in server.js .我不知道您在何处使用 Mongo,但我使用 Atlas 对此进行了测试 - 在部署到 Heroku 后我在与数据库交谈时遇到了问题,因此我还必须更改您在server.js中连接到数据库的方式。 You can also view these changes below, or in the GitHub repo..您还可以在下方或 GitHub 存储库中查看这些更改。

Let me know if you want me to send a pull request to your repo so you'll have the updated code and won't have to manually change anything.如果您希望我向您的存储库发送拉取请求,请告诉我,以便您拥有更新的代码,而无需手动更改任何内容。

Lastly, double check your environmental variables inside Heroku - make sure that they are set.最后,仔细检查 Heroku 中的环境变量 - 确保它们已设置。

// This kept being logged to the console
...
actually hit the route
actually hit the route
actually hit the route
actually hit the route
actually hit the route
actually hit the route
...
...
// This kept going on and on and on after I searched

These are the changes I made to fix the request loop:这些是我为修复请求循环所做的更改:

// App.js

function App() {
  /**
   * Separated your state into 2 different variables.
   * Your request loop was happening due to how your 
   * useEffect was configured (specifically the dependency 
   * array)
   */
  const [searchTerm, setSearchTerm] = useState();
  const [searchResults, setSearchResults] = useState();

  /*
  const [search, setSearch] = useState({
    term: '',
    results: []
  });
  */

  useEffect(() => {
    Axios.post(`/api/search`, { term: searchTerm /* search.term */ })
    .then(books => {
      setSearchResults(books.data);
      // setSearch({...search, results: books.data})
    });
  }, [searchTerm]);

  return (
    <div className="app">
      <Header />
      <Router>
        <Route exact path='/'>
          <Searchbar /* search={search} <-- No need for this param */ setSearch={setSearchTerm} /> 
          {!searchTerm /* search.term */ ? (
            <div className="message">
              <p>Search for a book or whatever</p>
            </div>
          ) : <SearchList results={searchResults/* search.results */} />}
        </Route>
        <Route path='/saved'>
          <h2 className="title">Saved Books</h2>
          <SavedList />
        </Route>
        <Footer />
      </Router>
    </div>
  );
}
// Searchbar.js

const Searchbar = ({/* search, */ setSearch}) => { // <-- No need for search param here
    return (
        <form action="#" method="get" className="searchbar" onSubmit={e => e.preventDefault()}>
            <DebounceInput
                minLength={2}
                debounceTimeout={300}
                type="search" 
                placeholder="🔎 search..."
                onChange={(e) => setSearch(e.target.value)}
            />
        </form>
    )
}
// server.js

require('dotenv').config();

const express = require('express');
const path = require('path');
const Axios = require('axios');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');

const PORT = process.env.PORT || 8080;
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/book';

const app = express();

// Define middleware here
app.use(express.urlencoded({ extended: true }));
app.use(bodyParser.json());

// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === 'production') {
  app.use(express.static('client/build'));
}

const { Schema } = mongoose;

const bookSchema = new Schema({
  info: Schema.Types.Mixed,
});

// *** REMOVED THIS ***
// const Book = mongoose.model('Book', bookSchema);

// ==========================================================
// **********************************************************
//          CHANGED THE WAY YOU CONNECT TO MONGO
// **********************************************************
// ==========================================================
/** */ mongoose.set('useCreateIndex', true);
/** */ 
/** */ const mongoConnection = mongoose.createConnection(mongoUri, {
/** */   useUnifiedTopology: true,
/** */   useNewUrlParser: true,
/** */   useFindAndModify: false,
/** */ });
/** */ 
/** */ const Book = mongoConnection.model('Book', bookSchema /*, 'COLLECTION_NAME'*/);
// ==========================================================
// **********************************************************
//                      END OF CHANGES
// **********************************************************
// ==========================================================

app.post('/api/search', (req, res) => {
  console.log('actually hit the route');
  Axios.get(
    `https://www.googleapis.com/books/v1/volumes?q=${req.body.term}`
  ).then(books => res.json(books.data.items));
});

app.post('/api/save', (req, res) => {
  const newBook = new Book({ info: req.body.book });
  newBook.save(err => {
    if (err) res.json(err);
    res.json({ status: true });
  });
});

app.post('/api/unsave', (req, res) => {
  Book.findByIdAndRemove(req.body.book._id, err => {
    if (err) res.json(err);
    res.json({ status: true });
  });
});

app.get('/api/saved', (req, res) => {
  Book.find({}, (err, books) => {
    if (err) res.json(err);
    else res.json(books);
  });
});

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, './client/build/index.html'));
});

/*
const db = mongoose.connection;

db.on('error', // console.error.bind(console, 'connection error:') 
  error => {
    console.log("[MONGOOSE][ERROR]", error);
  }
);

db.once('open', function() {
  console.log('[MONGOOSE][SUCCESS] Connected to database!');
});
*/

app.listen(PORT, () => {
  console.log(`🌎 ==> API server now on port ${PORT}!`);
});

if you add a console.log in the api save does it run when hitting that url?如果您在 api save 中添加一个 console.log,它会在点击该 url 时运行吗? Even though it returns a 404 it might still be running possibly.即使它返回 404,它也可能仍在运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM