Summary:
I have a web app with two main components: a frontend built using Vue.js, and a backend using Express.js.
The backend serves two functions:
Currently, I'm facing a CORS issue where the frontend on my local machine can connect to the API successfully, but when attempting to access the office list API from the backend on the same local machine, I encounter errors.
This problem seems to arise specifically when the backend attempts to communicate with itself to serve the office list API.
For my application, I'm using Node/Express on the backend, Vue.js for the front-end and Heroku for production/database.
Part of my API setup is to fetch a list of Offices from my database.
Local machine set up:
Both servers are running locally successfully, as well as connected to the database. The backend serves the front-end as was recommended to me prior.
However, when i use the frontend port on my local machine, i can access the office list api without issue, but on the backend server, it just won't work (same for production env, but that's another issue)
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://app-name.herokuapp.com/api/offices. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.
I've linked the longer 'Error, cannot fetch office list' message.
As far as i know, CORS is only handled in the backend, for both frontend and backend server instances.
Here is the relevant CORS code in my server.js file
const express = require('express');
const cors = require('cors');
const path = require('path');
require('dotenv').config();
const morgan = require('morgan');
const app = express();
const corsOptions = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
allowedHeaders: 'Content-Type,Authorization',
};
app.use(cors(corsOptions));
app.use(morgan('dev'));
app.use(express.json());
So my question is, how can i resolve the CORS error and correctly fetch the office list from the API?
And secondarily, why is my frontend working correctly, but my backend is not able to interact with the api?
APIs are naturally handled through the backend using port 3000, so curl commands are working correctly, it's the browser that is not interacting with the API correctly.
As in:
curl http://localhost:3000/api/offices
Displays the correct list of offices, but
http://localhost:3000/offices
Doesn't display any of them, even though the routes are set up properly, and the frontend
http://localhost:8080/offices
Does display them correctly.
I have tried:
main.js
import { createApp } from 'vue';
import App from './App.vue';
import 'bootstrap/dist/css/bootstrap.min.css';
import router from './router';
import axios from 'axios';
const app = createApp(App);
const isLocal = process.env.NODE_ENV === 'development'; // Check if it's a local development environment
// Define the base API URL conditionally
const BASE_API_URL = isLocal
? 'http://localhost:3000/api/' // Local development URL
: process.env.VUE_APP_API_BASE_URL; // Heroku-deployed URL
// Set the Axios default base URL
axios.defaults.baseURL = BASE_API_URL;
// Use the app variable you've already created
app.use(router).mount('#app');
Requested code snippets:
The rest of the server.js (for office API routes)
// Import route modules for each resource
const officesRoutes = require('./routes/officesRoutes');
// API routes should be defined before static file serving
app.use('/api/offices', officesRoutes);
// Serve static files for non-API routes
app.use('/', express.static(path.join(__dirname, '../frontend/dist')));
app.get('*', (req, res) => {
if (!req.path.startsWith('/api/')) {
res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
}
});
// Add Debugging Statements
app.use((req, res, next) => {
console.log(`Incoming Request: ${req.method} ${req.url}`);
next();
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
officeRoutes.js (relevant snippet)
// Logging to indicate this router is being accessed
console.log("Accessing Office API router...");
// Endpoints for operations on the collection of offices
router.route('/')
.get(async (req, res) => {
// Fetch and return all offices
try {
const allOffices = await pool.query('SELECT * FROM offices');
res.json(allOffices.rows);
} catch (err) {
res.status(500).json({ error: err.message });
}
})
Front end /office/ snippet (router, index.js)
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/HomePage.vue';
import OfficeList from '../views/OfficeList.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/offices',
name: 'Offices',
component: OfficeList,
},
];
const router = createRouter({
history: createWebHistory('/'),
routes
});
export default router;
If Express is serving your Vue app statically, you should be using host-relative URLs, eg
axios.get("/api/offices") // DO NOT configure axios.defaults.baseURL
Then there's absolutely no reason to allow cross-origin access, making your application simpler and safer overall.
Your entire server configuration should look more like this
const express = require('express');
const path = require('path');
require('dotenv').config();
const morgan = require('morgan');
const PORT = process.env.PORT || 3000;
const app = express();
app.use(morgan('dev'));
app.use(express.json());
// Import route modules for each resource
const officesRoutes = require('./routes/officesRoutes');
// API routes should be defined before static file serving
app.use('/api/offices', officesRoutes);
// Serve static files for non-API routes
const frontendDir = path.join(__dirname, '../frontend/dist');
app.use(express.static(frontendDir));
// Client-side routing handler
app.get('/*', (req, res) => {
// no need to check the path here
res.sendFile(path.join(frontendDir, 'index.html'));
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
You can even run the Vite dev server on its own for local development without changing URLs. Simply configure the dev server proxy to forward /api
URLs to Express
// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': 'http://localhost:3000',
},
},
});