Search code examples
javascriptnode.jsreactjsherokuaxios

Axios requests returning HTML instead of JSON in production build. (React, Node.js, Heroku)


When I was sending the requests from the React frontend, I tested to see what type of response Axios gave on the Networks Tab for Chrome, Firefox, Postman and it showed that on localhost it's showing that it's application/json type while in production on Heroku it's text/html type. The routes that I set up are returning res.json() so I'm not sure what could be causing the problem.

index.js

const express = require("express");
const dotenv = require("dotenv");
const path = require('path');

// Load env
dotenv.config({ path: './config.env' });

const app = express();
app.use(express.json());

// Routes
app.use('/', require('./routes/routes'));

const port = process.env.PORT || 8080;

if(process.env.NODE_ENV === 'production')
{
    app.use(express.static(path.join(__dirname, 'build'))); 

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

app.listen(port, () => {
    console.log(`Listening at port ${port}`);
});

routes.js

const express = require("express");
const router = express.Router();
const mysql = require('mysql2');

const db = mysql.createPool
({
    user: `${process.env.USER}`,
    host: `${process.env.HOST}`,
    password: `${process.env.PASSWORD}`,
    database: `${process.env.DATABASE}`,
    connectionLimit: 10,
    waitForConnections: true,
});

router.get('/user', (req, res) => 
{
    try 
    {
        let id = req.query.id;
        let admin = 0;

        // Check database to see if there's a user with the same ID as the one given to us.
        // If not, create a new user

        db.query("SELECT * FROM users WHERE id = ?", [id], (err, result) => 
        {                    
            if(result.length == 0)
            {
                db.query("INSERT INTO users (id, admin) VALUES (?, ?)", [id, admin]);
            }

            return res.status(200).json({"result": result});
        });
    } 
    catch (err) 
    {
        console.log(err);
        return res.status(400).json({error: "An error occurred with user"});
    }
});

router.post('/addMatch', (req, res) =>
{
    try 
    {
        let uid = req.body.uid;
        let mid = req.body.mid;
           
        db.query("INSERT INTO matches (uid, mid) VALUES (?, ?)", [uid, mid], (err, result) => 
        {
            return res.status(200).json({"result": result});              
        });
    } 
    catch (err) 
    {        
        console.log(err);
        return res.status(400).json({error: "An error occurred with addMatch"});
    }
});

router.post('/deleteMatch', (req, res) =>
{
    try 
    {
        let uid = req.body.uid;
        let mid = req.body.mid;
           
        db.query("DELETE FROM matches WHERE uid = ? AND mid = ?", [uid, mid], (err, result) => 
        {
            return res.status(200).json(result);              
        });
    } 
    catch (err) 
    {        
        console.log(err);
        return res.status(400).json({error: "An error occurred with deleteMatch"});
    }
});

router.get('/getMatches', (req, res) =>
{
    try 
    {
        let id = req.query.id;

        db.query("SELECT * FROM media WHERE id IN (SELECT mid FROM matches WHERE uid = ?);", [id], (err, matches) => 
        {
            return res.status(200).json(matches);
        });
    } 
    catch (err) 
    {
        console.log(err);
        return res.status(400).json({error: "An error occurred with getMatches"});       
    }
});

router.get('/getMovie', (req, res) =>
{
    try 
    {
        let id = Math.floor(Math.random() * 50) + 1;

        db.query(`SELECT * FROM media WHERE id = ${id}`, (err, result) => 
        {
            return res.status(200).json(result[0]);
        });
    } 
    catch (err) 
    {
        console.log(err);
        return res.status(400).json({error: "An error occurred with getMovie"});
    }
});

module.exports = router;

Swipe.js

import React from "react";
import MatchResults from "../components/MatchResults";
import Data from '../components/Data';
import { Container, Button } from 'reactstrap';
import { withAuth0 } from "@auth0/auth0-react";
import axios from 'axios';

import '../styles/Swipe.css';

class Swipe extends React.Component 
{
    constructor(props) 
    {
        super(props);

        const { user } = this.props.auth0;

        this.state = { 
            data: {},
            uid: user.sub.split('|')[1],
            matches: []
        };

        this.yesButtonApi = this.yesButtonApi.bind(this);
        this.noButtonApi = this.noButtonApi.bind(this);
        this.getMovie = this.getMovie.bind(this);
        this.getMatches = this.getMatches.bind(this);
    }

    componentDidMount() 
    {
        this.getMovie();
        this.getMatches();
    }

    yesButtonApi() 
    {    
        try 
        {
            var body = {
                uid: this.state.uid,
                mid: this.state.data.id
            }  
    
            axios.post('/addMatch', body).then(() =>
            {
                this.getMovie();
                this.getMatches();
            });
        } 
        catch (error) 
        {
            console.log(error)
        }
    }

    noButtonApi() 
    {
        try 
        {
            this.getMovie();
        } 
        catch (error) 
        {
            console.log(error);
        }
    }

    getMovie()
    {
        try 
        {
            axios.get('/getMovie').then((response) =>
            {
                this.setState
                ({
                    data: response.data
                });
            });
        } 
        catch (error) 
        {
            console.log(error);
        }
    }

    getMatches()
    {
        try 
        {
            let uid = this.state.uid;

            axios.get(`/getMatches?id=${uid}`).then((response) => 
            {
                this.setState
                ({
                    matches: response.data
                });
            });
        } 
        catch (error) 
        {
            console.log(error);
        }
    }

    render() 
    {
        return (
            <div className="text-center">
                <Container className="swipe">
                    <Data title={this.state.data.title} description={this.state.data.description} rating={this.state.data.rating} preview={this.state.data.video}/>
                    <Button color="success" onClick={this.yesButtonApi} className="ml-sm">Yes</Button>
                    <Button color="danger" onClick={this.noButtonApi} className="ml-sm">No</Button>
                    <MatchResults matches={this.state.matches} uid={this.state.uid}/>
                </Container>
            </div>
        )
    }
}

export default withAuth0(Swipe);

MatchResults.js

import React, { Component } from 'react';
import { Card, CardBody, Container, CardHeader, Row, Col, Collapse, Button } from 'reactstrap';
import { AiFillStar } from 'react-icons/ai';
import '../styles/MatchResults.css';
import axios from 'axios';

class MatchResults extends Component 
{
    constructor(props) 
    {
        super(props);

        this.state = { isOpen: false, matches: this.props.matches, uid: this.props.uid };

        this.toggle = this.toggle.bind(this);
        this.getMatches = this.getMatches.bind(this);
        this.deleteButtonApi = this.deleteButtonApi.bind(this);
    }

    componentDidUpdate(prevProps)
    {
        if(this.props !== prevProps)
        {
            this.setState
            ({
                matches: this.props.matches
            });
        }
    }

    toggle()
    {
        try 
        {
            this.setState
            ({
                isOpen: !this.state.isOpen
            });

            if(this.state.isOpen)
            {
                this.getMatches();
            }
        } 
        catch (error) 
        {
            console.log(error);
        }
    }

    getMatches()
    {
        try 
        {
            let uid = this.state.uid;

            axios.get(`/getMatches?id=${uid}`).then((response) => 
            {
                this.setState
                ({
                    matches: response.data
                });
            });
        } 
        catch (error) 
        {
            console.log(error);
        }
    }

    deleteButtonApi(e)
    {
        try 
        {
            var body = {
                uid: this.state.uid,
                mid: e.target.value
            }
    
            axios.post('/deleteMatch', body).then(() =>
            {
                this.getMatches();
            });
        } 
        catch (error) 
        {
            console.log(error);
        }
    }

    render() 
    {
        return (
            <Container className="match-results">
                <div className="text-center">
                    <Button color="primary" onClick={this.toggle} style={{ marginBottom: '1rem' }}>View Matches</Button>
                    <Collapse isOpen={this.state.isOpen}>
                        <Row>
                            {
                                this.state.matches.map((item, val) => 
                                {
                                    val += 1;
                                    return (
                                        <Col sm="12" md="6" lg="4">
                                            <Card key={val} className="match-results-card" data-testid="card">
                                                <CardHeader>
                                                    <div data-testid="title">
                                                        {item.title}
                                                    </div>
                                                    <span data-testid="rating">
                                                        <AiFillStar />
                                                        {item.rating}
                                                    </span>
                                                </CardHeader>
                                                <CardBody>
                                                    <div data-testid="description">
                                                        {item.description}
                                                    </div>
                                                </CardBody>
                                                <Button className="deleteBtn" color="danger" onClick={this.deleteButtonApi} value={item.id}>Delete</Button>
                                            </Card>
                                        </Col>
                                    )
                                })
                            }
                        </Row>
                    </Collapse>
                </div>
            </Container>
        )
    }
}

export default MatchResults;

Localhost

Localhost

Localhost Response

Localhost Response

Production

Production

Production Response

Production Response


Solution

  • The response you're getting in your production deployment is the default index.html served when there's a problem with your node server configuration. It is not about the response being text/html, its about the node server being setup incorrectly (possibly none of the routes are working). The underlying problem could be anything, a few examples in this question.

    I suggest start by looking at your node.js logs in your production environment (debugging Node.js application on Heroku)