0

I have a working sample however I am sure it can be refactored for improvement. The project is a Weather app built with React, Node/Express and OpenWeather API. Basically the lat and lon values from first API response are needed as params in second API request. Reason for having Node server is to hide API key in browser. :)

Here is a breakdown of folder structure(only includes relevant files):

weather-app/
  ...
  src/
    hooks/
    useForecast.js
  ...
  index.js
  routes/
    weather.js
    onecall.js

Here are the breakdown of each file(only includes relevant codes):

index.js(express entry file):

app.use('/weather', require('./routes/weather'))
app.use('/onecall', require('./routes/onecall'))

weather.js(1st API call):

router.get('/', async (req, res, next) => { 
  try {
    const params = new URLSearchParams({
      ...url.parse(req.url, true).query, //location value from useForecast.js 
      appid: API_KEY
    })

    const apiRes = await needle('get', `${BASE_URL}/weather?${params}`) //api.openweathermap.org/data/2.5/weather?q=somecity&appid={API_KEY}
    const data = apiRes.body //data will be used in useForecast.js

    res.status(200).json(data)
  } catch(error) {
    next(error)
  }
}) 

onecall.js(2nd API call):

router.get('/', async (req, res, next) => {
  try {
    const params = new URLSearchParams({
      ...url.parse(req.url, true).query, //lat and lat value from useForecast.js
      appid: API_KEY 
    })

    const apiRes = await needle('get', `${BASE_URL}/onecall?${params}`) //api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&appid={API_KEY}
    const data = apiRes.body //data will be used in useForecast.js

    res.status(200).json(data)
  } catch (error) {
    next(error)
  }
})

useForecast.js(hook call from React):

const useForecast = () => {
  ...    
  const getCoordinates = async location => {   
    const { data } = await axios(`/weather?q=${location.value}`) //url from weather.js

    if(data.cod === '404' || !data) {
      setError(data.message)
      setLoading(false)
      return
    }

    return data
  }

  const getForecastData = async (lat, lon) => {
    const { data } = await axios(`/onecall?lat=${lat}&lon=${lon}`) //url from onecall.js

    if(data.cod === '400' || !data) {
      setError('something went wrong')
      setLoading(false)
      return
    }

    return data
  }     
 ... 
}  

Can both calls in weather.js and onecall.js be refactored and combined into one? If so would changes in useForecast.js have to be made also?

Thanks in advance!!!

simonxcode
  • 143
  • 1
  • 3
  • 13

1 Answers1

1

Yes, you can do it using a architecture pattern similar to MVC or MVCS

MVCS

Used in server side rendering applications or in a simple terms: used in previous generation webs (java, python, php, etc). Modern webs(react, angular, vue, etc) don't use this. Basically in this pattern you have this structure:

enter image description here

  • V means View : html, templates, etc
  • C means Controller : server side in charge of return just data like json,xml,etc or view merged it with data (https://expressjs.com/en/resources/template-engines.html)
  • S means Service: implements the logic. Controller uses one of several services to orchestrate the operation
  • M means Model: abstraction of the database or datasource to be read or write.

MSR

I don't know if I'm creating this , but is the same as MVCS but for apis or microservices in which views or pages don't exist:

  • R means Rest controller. Route in yous nodejs case
  • S means Service. Javascript class or module with logic. Rest controller uses one of several services to orchestrate the operation
  • M means Model: abstraction of the database, external apis or any datasource (file, etc) to be read or write.

Model-Service-Rest in nodejs

Service.js with logic

const Service = () => {

  this.getCoordinates = async function(location){
    try {
      const params = new URLSearchParams({
        q:location, 
        appid: API_KEY
      })

      const apiRes = await needle('get', `${BASE_URL}/weather?${params}`) //api.openweathermap.org/data/2.5/weather?q=somecity&appid={API_KEY}
      const data = apiRes.body 
      return data;
    } catch(error) {
      throw error;
    }
  }

  this.getForecastData = async function(lat, lon){
    try {
      const params = new URLSearchParams({
        lat:lat,
        lon:lon,
        appid: API_KEY
      })

      const apiRes = await needle('get', `${BASE_URL}/weather?${params}`) //api.openweathermap.org/data/2.5/weather?q=somecity&appid={API_KEY}
      const data = apiRes.body
      return data;
    } catch(error) {
      throw error;
    }
  }
  
}

Routes.js

router.get('/forecast-data', async (req, res, next) => { 
  var coordinates = await this.service.getCoordinates(req.query.q);
  var forecastData = await this.service.getForecastData(req.query.lat, req.query.lon);
  res.status(200).json(forecastData)
}) 

If another web required only the coordinates, you could re-use the service

router.get('/coordinates', async (req, res, next) => { 
  var coordinates = await this.service.getCoordinates(req.query.q);
  res.status(200).json(coordinates)
}) 
JRichardsz
  • 14,356
  • 6
  • 59
  • 94