Search code examples
reactjsnode.jsexpressreduxvite

Vite React Redux Toolkit, api endpoint responding with my own index.html file


I am learning full-stack development and recently switched my project (a basic mern stack) to Vite from create-react-app so I could configure and use vitests' compatability with react-testing-library. After following this post text and a few others, running npm run dev fires up the server, connects to the database, and pulls up my project in the browser.

The issue is that any request to the backend is somewhere along the intercepted by (my middleware?, RTK error handleing?) and the response sent back to my frontend is my own index.html file. The index.html file is in the root of the frontend, at the same level as the vite.config.js. Putting it in the public folder crashes my browser, as described here text

I can query the backend manuelly from the front end using a useEffect and fetch just fine. The server is running just fine on localhost:5000 and I am getting the expected response when using postman. I can console.log the response in the controller and I get back the expected data. I have added responseHandler as mentioned here text and here text which removed this error :, and the parsing error in my console, but now the error from my redux query is just undefined. I have tried configuring my vite.config.js multiple ways, I have removed the proxy from my package.json as well, with no luck. I also added the cors package and am using in my server.js file.

I know that vite handles enviroment variable differently, but I don't think this is the issue since my frontend folder is completely seperate from my backend, and only the backend is using the variables in the .env file.

Everything works as expected in the create-react-app version of this project.

This post text describes the same errors I am getting, I have tried implementing both suggested solution with no success.

As mentioned before I think it is most likely coming from RTK throwing an error based upon it not liking the response from my server. Here are what I think is all the relevant code.

vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

import dns from "dns";

// localhost part
// dns.setDefaultResultOrder("verbatim");

export default defineConfig(() => {
    return {
        plugins: [react()],
        server: {
            open: true,
            // host: "127.0.0.1",
            port: 3000,
            proxy: {
                "/api": {
                    target: "http://localhost:5000",
                    changeOrigin: true,
                    rewrite: (path) => path.replace(/^\/api/, ""),
                },
            },
        },

        build: {
            manifest: true,
            rollupOptions: {
                input: "./src/App.jsx",
            },
        },

        resolve: {
            alias: [
                {
                    // this is required for the SCSS modules
                    find: /^~(.*)$/,
                    replacement: "$1",
                },
            ],
        },
    };
});

code in controller


    const getSectionByUrl = asyncHandler(async (req, res) => {
    // const section = recursiveSearch(menuItems, 
    req.params.sectionId);

    const section = await Section.find({ url: req.params.sectionId });
    // console.log(section[0], "section in secitonRoutes");
    if (section) {
        // console.log(res.json(section[0]), "controller");
        return res.json(section[0]);
    } else {
        res.status(404);
        throw new Error("Section not found");
    }
});

sectionApiSlice.js with endpoints

    import { SECTIONS_URL } from "../constants";
    import { apiSlice } from "./apiSlice";

    export const sectionsApiSlice = apiSlice.injectEndpoints({
        endpoints: (builder) => ({
            getSections: builder.query({
                query: () => ({
                    url: SECTIONS_URL,
                }),

                keepUnusedDataFor: 5,
            }),
            getSectionByUrl: builder.query({
                query: (sectionUrl) => ({
                    url: `${SECTIONS_URL}/${sectionUrl}`,
                    responseHandler: "text",
                }),
                keepUnusedDataFor: 5,
            }),
            getQuizBySectionKey: builder.query({
                query: (sectionKey) => ({
                    url: `${SECTIONS_URL}/quiz/${sectionKey}`,
                }),
            }),
        }),
    });

    export const {
        useGetSectionsQuery,
        useGetSectionByUrlQuery,
        useGetQuizBySectionKeyQuery,
    } = sectionsApiSlice;

server.js


    import express from "express";
    import cors from "cors";
    import dotenv from "dotenv";
    import cookieParser from "cookie-parser";
    import bodyParser from "body-parser";
    dotenv.config();
    import connectDB from "./config/db.js";
    import { notFound, errorHandler } from "./middleware/errorMiddleware.js";
    import sectionRoutes from "./routes/sectionRoutes.js";
    import userRoutes from "./routes/userRoutes.js";
    const port = process.env.PORT || 5000;
    
    connectDB();
    
    const app = express();
    app.use(cors()); // This will enable CORS for all routes
    //Body parser middleware
    
    app.use(express.json());
    app.use(bodyParser.json());
    app.use(express.urlencoded({ extended: true }));
    
    //Cookie parser middleware. Allows to access cookies
    app.use(cookieParser());
    
    app.get("/", (req, res) => {
        res.send("API is running...");
    });
    
    app.use("/sections", sectionRoutes);
    app.use("/users", userRoutes);
    
    //use errorMiddleware
    app.use(notFound);
    app.use(errorHandler);
    
    app.listen(port, () => console.log(`Server running on port ${port}`));

scripts in frontend package.json

    "scripts": {
        "sass:build": "sass scss:css",
        "start": "vite",
        "build": "vite build",
        "serve": "vite preview",
        "dev": "vite --host localhost --port 3000"
    },

custom error handling, I don't think this is relevant but just incase


    const notFound = (req, res, next) => {
    const error = new Error(`Not Found - ${req.originalUrl}`);
    res.status(404);
    next(error);
    };

    const errorHandler = (err, req, res, next) => {
        //if an error but statusCode is still 200
        let statusCode = res.statusCode === 200 ? 500 : 
    res.statusCode;
        let message = err.message;
    
        //check for mongoose bad ObjectId
        if (err.name === "CastError" && err.kind === "ObjectId") {
            message = `Resource not found`;
            statusCode = 404;
        }
    
        res.status(statusCode).json({
            message,
            stack: process.env.NODE_ENV === "production" ? "🥞" : 
    err.stack,
        });
    };
    
    export { notFound, errorHandler };


Solution

  • I finally got it working with the help of this post here and a lot of trial and error. As it turns out my issue was my frontend and backend paths were not distinct from one another, so once I prefixed the routes my backend was using with '/api/' as in '/api/sections' and updated a few settings on my proxy, everything seems to be working as expected, at least in development. Here is the modified vite.config.js:

    
        import { defineConfig } from "vite";
        import react from "@vitejs/plugin-react";
        
        import dns from "dns";
        
        dns.setDefaultResultOrder("verbatim");
        export default defineConfig(() => {
            return {
                plugins: [react()],
                server: {
                    open: true,
                    port: 3000,
                    proxy: {
                        "/api": {
                            target: "http://localhost:5000",
                            changeOrigin: true,
                            ws: true,
                            secure: false,
                            rewrite: (path) => path.replace(/^\/api/, ""),
                        },
                    },
                },
        
                build: {
                    manifest: true,
                    rollupOptions: {
                        input: "./src/App.jsx",
                    },
                },
        
                resolve: {
                    alias: [
                        {
                            // this is required for the SCSS modules
                            find: /^~(.*)$/,
                            replacement: "$1",
                        },
                    ],
                },
            };
        });