React + Express应用上的API路由在Heroku上提供404

2020-02-22 javascript node.js reactjs express heroku

因此,我构建了一个简单的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>
  );

Answers

如果您在api保存中添加console.log,则在击中该URL时是否运行?即使返回404,它也可能仍在运行。

最终结果:


说明:

因此,即使在本地运行此程序,我也会得到404错误-问题的根源是您如何启动应用程序。

您只需要启动服务器,而无需启动客户端。看来您正在启动create-react-app附带的“内置”服务器...因此,由于前端在端口3000上运行而后端在任何端口上运行,因此服务器实际上从未接受请求您已经在.env设置了。

由于axios如何发送请求(仅使用当前URL,该URL在内置的create-react-app端口上运行,而不是在服务器端口上运行),因此实际上是在将请求发送到错误的端口。

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

我添加了2个新的npm脚本: npm run begin npm startnpm start (将原始npm start重命名为npm 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}!`);
});

Related