Search code examples
javascriptreactjsnode.jsexpressvite

Cannot get response from an API call in the express.js backend


I want to make an API call from my backend server running express.js. I expect when I visit http://localhost:3004/api/weather?city=[cityName], I should see the json response from openweathermap.org. Instead, I get the message "unknown endpoint" from my middleware. Thats the terminal output from the server:

Time: 2024-09-08T08:02:14.510Z
Method: GET
Path:   /api/weather
Query:  { city: 'zagreb' }
Headers: {
  host: 'localhost:3004',
  connection: 'keep-alive',
  pragma: 'no-cache',
  'cache-control': 'no-cache',
  'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"',
  'sec-ch-ua-mobile': '?0',
  'sec-ch-ua-platform': '"macOS"',
  'upgrade-insecure-requests': '1',
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
  accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
  'sec-fetch-site': 'none',
  'sec-fetch-mode': 'navigate',
  'sec-fetch-user': '?1',
  'sec-fetch-dest': 'document',
  'accept-encoding': 'gzip, deflate, br, zstd',
  'accept-language': 'en,de-DE;q=0.9,de;q=0.8,fr-FR;q=0.7,fr;q=0.6,en-US;q=0.5,hr;q=0.4,nl;q=0.3',
  cookie: 'Webstorm-8aead92d=3b86ad4a-0728-412a-9b16-792fba3736a2'
}
Body:   {}
IP:     ::1

Here is my code for the Route:

require('dotenv').config()
const weatherRouter = require('express').Router()
const axios = require('axios')

const apiKey = process.env.API_KEY

weatherRouter.get('/api/weather', async (req, res, next) => {
    const { city } = req.query
    if (!city) {
        return res.status(400).json({ error: 'City must be provided' });
    }
    try {
        console.log(`Fetching weather data for ${city}`);
        const response = await axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${city}&limit=5&appid=${apiKey}`);
        res.json(response.data);
    } catch (error) {
        res.status(500).json({ error: 'Failed to fetch weather data' });
    }
    next()
})

module.exports = weatherRouter

The final goal is, that I make the request from a form from the frontend running react.js. This currently does not work either. That is the code I wrote on the frontend to link it to with be backend:

import axios from 'axios'
const baseUrl = '/api/weather'

const getWeatherData = async city => {
    const response = await axios.get(`${baseUrl}?city=${city}`)
    return response.data
}

export default { getWeatherData }

Can anyone tell me what I am doing wrong? Here is the code from app.js where I mount the routes:

const config = require('./utils/config')
const express = require('express')
require('express-async-errors')
const app = express()
const cors = require('cors')
const citiesRouter = require('./controllers/cities')
const weathersRouter = require('./controllers/weathers')
/* const usersRouter = require('./controllers/users')
const loginRouter = require('./controllers/login') */
const middleware = require('./utils/middleware')
const logger = require('./utils/logger')
const mongoose = require('mongoose')

mongoose.set('strictQuery', false)

logger.info('connecting to', config.MONGODB_URI)

mongoose.connect(config.MONGODB_URI)
  .then(() => {
    logger.info('connected to MongoDB')
  })
  .catch((error) => {
    logger.error('error connection to MongoDB:', error.message)
  })

app.use(cors())
app.use(express.static('dist'))
app.use(express.json())
app.use(middleware.requestLogger)
app.use(middleware.tokenExtractor)

app.use('/api/cities', citiesRouter)
app.use('/api/weather', weathersRouter)

app.use(middleware.unknownEndpoint)
app.use(middleware.errorHandler)

module.exports = app

Here is the code from vite.config.js:

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  proxy: {
    "/api": {
      target: "http://localhost:3004",
      changeOrigin: true,
    },
  },
})

Browser console and network tab

Here is a screenshot when making a get request from the frontend: enter image description here

Here is a screenshot after removing the proxy in vite.config.js and setting baseURL to http://localhost:3004/api/weather: enter image description here


Solution

  • Full example BE and FE:

    // index.js
    const express = require('express');
    const weatherRouter = require('./routes/weatherRouter'); // Import the weatherRouter
    
    const app = express();
    const port = 3004;
    
    // Middleware to parse JSON
    app.use(express.json());
    
    // Use the weatherRouter for '/api/weather' routes
    app.use('/api/weather', weatherRouter);
    
    // Root route
    app.get('/', (req, res) => {
      res.send('Welcome to the Express App!');
    });
    
    app.listen(port, () => {
      console.log(`Server is running on http://localhost:${port}`);
    });
    

    weatherRouter

    // routes/weatherRoutter.js
    const express = require('express');
    const router = express.Router();
    
    // A simple weather route
    router.get('/', (req, res) => {
      res.json({
        location: 'New York',
        temperature: '22°C',
        condition: 'Sunny',
      });
    });
    
    module.exports = router;
    

    Frontend code:

    vite.config.js

    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [react()],
      server: {
        proxy: {
          '/api': {
            target: 'http://localhost:3004',
            changeOrigin: true,
          },
        },
      },
    });
    

    api.js

    import axios from 'axios';
    
    const axiosClient = axios.create({
      baseURL: import.meta.env.VITE_API_BASE_URL || '',
    });
    
    const getWeatherData = async (city) => {
      const response = await axiosClient.get('/api/weather', {
        params: {
          city,
        },
      });
      return response.data;
    };
    
    export default { getWeatherData };
    

    App.jsx

    import { useEffect } from 'react';
    import './App.css';
    import api from './api';
    
    function App() {
      useEffect(() => {
        api.getWeatherData('New York').then((data) => console.log(data));
      }, []);
    
      return <div>Test</div>;
    }
    
    export default App;
    

    enter image description here

    enter image description here