首页 > 解决方案 > react + express 应用程序上的 API 路由在 Heroku 上给出 404

问题描述

所以我构建了一个简单的 mern 应用程序,它在我的本地环境中运行良好,但是当部署到 Heroku 时,它可以很好地服务于 react 应用程序,但在 API 调用上会出现 404。我似乎无法弄清楚这个问题。我正在使用 Axios 发送请求。我检查了网络请求,它们看起来都不错,但仍然返回 404。在邮递员中进行测试也返回了相同的错误。

这是服务器代码...知道为什么会失败吗?

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}!`);
});

这是我的 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"
    }
}

我的反应路线以防万一

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>
  );

标签: javascriptnode.jsreactjsexpressheroku

解决方案


最终结果:


解释:

所以,即使在本地运行它时,我也得到了 404 - 问题原来是你如何启动应用程序。

您只需要启动服务器,而不需要启动客户端。看起来您正在启动随附的“内置”服务器create-react-app......所以,您的服务器实际上从未接受请求,因为您的前端在端口 3000 上运行,而您的后端在您设置的任何端口上运行.env

由于您axios发送请求的方式(仅使用在内置create-react-app端口上运行的当前 URL,而不是您的服务器端口),它本质上是将请求发送到错误的端口。

这是我昨晚应该想到的,因为我记得看到你的 Heroku 应用程序正在使用 React 的开发版本(通过 Firefox React 扩展)——这应该是一个危险信号。

我添加了 2 个新npm脚本:npm run beginnpm start(将原始脚本重命名npm startnpm run start:original.npm run begin正确构建您的前端,然后启动您的后端。 这最终解决了问题NODE_ENV=production。我在本地测试时也遇到了。

我也删除npm heroku-postbuild了,因为它不需要。


代码更改:

让它工作后,我注意到您的前端代码有问题 - 一个循环一遍又一遍地发送请求 - 我一直看到以下信息记录到控制台。所以我也解决了再往下使用代码(我没有删除任何代码,我只是将代码注释掉,以便您可以看到我所做的更改)。

我不知道您在哪里使用 Mongo,但我使用 Atlas 进行了测试 - 在部署到 Heroku 后与数据库通信时遇到问题,因此我还必须更改您在server.js. 您还可以在下面或在 GitHub 存储库中查看这些更改。

如果您希望我向您的仓库发送拉取请求,请告诉我,这样您就可以获得更新的代码,而无需手动更改任何内容。

最后,仔细检查 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

这些是我为修复请求循环所做的更改:

// 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}!`);
});

推荐阅读