Heroku除登录端点外所有接口返回503,开发环境运行正常
Heroku部署后除登录端点外其他API请求超时(H12错误)
开发环境下博客、用户信息和登录页面功能完全正常,但部署到Heroku生产环境后,仅/api/login端点可用,/api/blogs、/api/users等所有GET/POST端点均无法正常工作,Heroku日志显示H12 - 请求超时错误,伴随503状态码。
相关代码
app.js
require('dotenv').config() const config = require('./utils/config') const express = require('express') require('express-async-errors') const app = express() const mongoose = require('mongoose') const cors = require('cors') const morgan = require('morgan'); const middleware = require('./utils/middleware') const blogsRouter = require('./controllers/blogs') const usersRouter = require('./controllers/users') const loginRouter = require('./controllers/login') mongoose.connect(config.MONGODB_URI) .then(() => { console.log('connected to MongoDB') }) .catch((error) => { console.log('error connection to MongoDB:', error.message) }) app.use(middleware.tokenExtractor) app.use(cors()) app.use(express.static("build")) app.use(express.json()) app.use('/api/blogs', blogsRouter) app.use('/api/users', usersRouter) app.use('/api/login', loginRouter) app.use(middleware.errorHandler) app.use(middleware.unknownEndpoint) app.use(morgan(':method :url :body')); module.exports = app
index.js
require('dotenv').config() const app = require('./app') const http = require('http') const logger = require('./utils/logger') const config = require('./utils/config') const server = http.createServer(app) const PORT = process.env.PORT server.listen(PORT, () => { logger.info(`Server running on port ${config.PORT}`) })
Controllers
blogs.js
const blogsRouter = require('express').Router() const { getNextKeyDef } = require('@testing-library/user-event/dist/keyboard/getNextKeyDef') const Blog = require('../models/blog') const User = require('../models/user') const jwt = require('jsonwebtoken') const getTokenFrom = request => { const authorization = request.get('authorization') if (authorization && authorization.toLowerCase().startsWith('bearer ')) { return authorization.substring(7) } return null } blogsRouter.get('/', (request, response) => { Blog .find({}).populate('user', {username: 1, name: 1}) .then(blogs => { response.json(blogs) }) }) blogsRouter.get('/:id', async (request, response) => { const blog = await Blog.findById(request.params.id).populate('user', {username: 1, name: 1}, ('comments', {commenter: 1, comment: 1})) if (blog) { response.json(blog.toJSON()) } else { response.status(404).end() } }) blogsRouter.post('/', async (request, response, next) => { const body = request.body const token = getTokenFrom(request) const decodedToken = jwt.verify(token, process.env.SECRET) if (!decodedToken.id) { return response.status(401).json({ error: 'token missing or invalid' }) } const user = await User.findById(decodedToken.id) if (body.title === undefined) { return response.status(400).json({ error: 'content missing' }) } if (body.author === undefined) { return response.status(400).json({ error: 'content missing' }) } const newblog = new Blog({ title: body.title, author: body.author, url: body.url, user: user._id }) const savedBlog = await newblog.save() user.blogs = user.blogs.concat(savedBlog._id) await user.save() response.json(savedBlog) }) blogsRouter.post('/:id/comments', async (request, response, next) => { try { const body = request.body const blog = await Blog.findById(request.params.id) if (blog) { blog.comments.push(body.comment) const savedBlog = await blog.save() response.status(200).json(savedBlog.toJSON()); } } catch (exception) { next(exception) }}) blogsRouter.delete('/:id', async (request, response, next) => { const decodedToken = jwt.verify(request.token, process.env.SECRET) if (!request.token || !decodedToken.id) { return response.status(401).json({ error: 'token missing or invalid' }) } try { const blog = await Blog.findById(request.params.id) if (blog.user.toString() === decodedToken.id.toString()) { await blog.remove() response.status(204).end() } else { response.status(401).end() } } catch (exception) { next(exception) } }) blogsRouter.put('/:id', async (request, response, next) => { const body = request.body /* const decodedToken = jwt.verify(request.token, process.env.SECRET) if (!request.token || !decodedToken.id) { return response.status(401).json({ error: 'token missing or invalid' }) } const user = await User.findById(decodedToken.id) */ const blog = { title: body.title, author: body.author, url: body.url, user: body.user._id, likes: body.likes } const savedBlog = await Blog.findByIdAndUpdate(request.params.id, blog, { new: true }) response.json(savedBlog) }) module.exports = blogsRouter
login.js
const jwt = require('jsonwebtoken') const bcrypt = require('bcrypt') const loginRouter = require('express').Router() const User = require('../models/user') loginRouter.post('/', async (request, response) => { const { username, password } = request.body const user = await User.findOne({ username }) const passwordCorrect = user === null ? false : await bcrypt.compare(password, user.passwordHash) if (!(user && passwordCorrect)) { return response.status(401).json({ error: 'invalid username or password' }) } const userForToken = { username: user.username, id: user._id, } const token = jwt.sign(userForToken, process.env.SECRET) response .status(200) .send({ token, username: user.username, name: user.name }) }) module.exports = loginRouter
users.js
const bcrypt = require('bcrypt') const usersRouter = require('express').Router() const User = require('../models/user') usersRouter.get('/', async (request, response) => { const users = await User.find({}).populate('blogs', {title: 1, author: 1, url: 1}) response.json(users) }) usersRouter.post('/', async (request, response) => { const { username, name, password } = request.body const existingUser = await User.findOne({ username }) if (existingUser) { return response.status(400).json({ error: 'username must be unique' }) } if (!username) { return response.status(400).json({ error: 'please add a username' }) } if (!password) { return response.status(400).json({ error: 'please add a valid password' }) } const saltRounds = 10 const passwordHash = await bcrypt.hash(password, saltRounds) const user = new User({ username, name, passwordHash, }) const savedUser = await user.save() response.status(201).json(savedUser) }) module.exports = usersRouter
排查与解决方向
- MongoDB连接校验:检查Heroku环境变量中
MONGODB_URI是否正确,确认数据库(如MongoDB Atlas)的IP白名单允许Heroku服务器访问,登录端点能工作可能是因为未触发复杂数据库查询,而其他端点因数据库连接超时导致请求超过Heroku 30秒限制。 - 调整中间件顺序:将
morgan日志中间件移到express.json()之后、路由挂载之前,确保能记录所有请求日志,排查请求是否到达服务器。 - 统一异步处理逻辑:将
blogsRouter.get('/')这类使用.then()的路由改为async/await写法,确保数据库查询错误能被express-async-errors捕获,避免请求无响应超时。 - 检查Token提取一致性:
tokenExtractor中间件和部分路由自定义的getTokenFrom逻辑需保持一致,避免因Token提取失败导致后续逻辑卡住。 - Heroku资源检查:免费额度dyno可能因资源不足超时,尝试重启dyno或查看Heroku metrics监控资源占用情况。
内容的提问来源于stack exchange,提问作者Julius Goddard
相关产品推荐
相关产品推荐

