Open In App

Weather Forecast App using MERN Stack

Last Updated : 22 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

This project is a simple weather application built using React.js for the frontend and Node.js with Express.js for the backend. It allows users to check the current weather and forecast for a specific city. The backend retrieves weather data from the OpenWeatherMap API and stores it in a MongoDB database. The frontend displays the weather information and forecast in a user-friendly interface.

Output Preview: Let us have a look at how the final output will look like.

Screenshot-2024-03-13-122016

Prerequisites

Approach to Create Weather Forecast App

Backend:

  • Uses Express.js to set up RESTful API endpoints.
  • Utilizes mongoose to connect to a MongoDB database and store weather data.
  • Retrieves weather data from the OpenWeatherMap API based on user requests.

Frontend:

  • Uses React.js to create a user-friendly interface.
  • Provides an input field for users to enter the city name.
  • Retrieves weather data from the backend API and displays it to the user.
  • Shows both current weather and forecast information.

Steps to Create the Project

Steps to Create the Backend

Project Structure:

Screenshot-2024-03-13-121511

Step 1: Create a directory for project

mkdir server
cd server

Step 2: Initialize npm:

npm init -y

Step 3: Install Dependencies:

npm install express mongoose cors axios

Step 4: Create a file named index.js & write the following code.

Javascript
//server/index.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const axios = require('axios');

const app = express();
const PORT = process.env.PORT || 5000;

// Replace 'YOUR_MONGODB_URI' with your actual MongoDB connection URI
const MONGODB_URI = 'YOUR_MONGODB_URI/weatherforecast';
mongoose.connect(MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true });

app.use(cors());
app.use(express.json());

// Create a Mongoose model for weather data
const WeatherData = mongoose.model('WeatherData', {
  city: String,
  country: String,
  temperature: Number,
  description: String,
  icon: String,
});

// Route to handle storing weather data
app.post('/api/weather', async (req, res) => {
  try {
    // Extract weather data from request body
    const { city, country, temperature, description, icon } = req.body;

    // Create a new document using the WeatherData model
    const weatherData = new WeatherData({
      city,
      country,
      temperature,
      description,
      icon,
    });

    // Save the weather data to the database
    await weatherData.save();

    // Respond with success message
    res.json({ message: 'Weather data saved successfully' });
  } catch (error) {
    console.error('Error saving weather data:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Start the server
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

Run Backend Server:

node index.js

DATABASE OUTPUT:

Screenshot-2024-03-15-011931

Steps to Create the Frontend:

Project Structure:

Screenshot-2024-03-13-121456


Step 1: Set up React frontend using the command.

npx create-react-app client
cd client

Step 2: Install the required dependencies.

npm install axios

Step 3: Create Frontend Components:

  • Modify or replace the src/App.js and src/App.css and other files with code given as below
  • In app.js if that API key is not working generate from openweathermap.org and replace it.
CSS
/*App.css*/
@import url('https://fonts.googleapis.com/css2?family=Concert+One:wght@300&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@700&display=swap');

html {
    height: 100%; 
}

body {
    background: whitesmoke;
    padding: 1rem 5rem;
    font-family: 'Roboto', sans-serif;
    text-transform: capitalize;
    background: linear-gradient(45deg, rgba(183, 204, 248, 0.717), rgba(7, 43, 127, 0.679)), url('https://img.freepik.com/free-photo/blue-sky-with-cloud-closeup_150588-124.jpg?t=st=1710145731~exp=1710149331~hmac=312231e34af28df68a64a9b006a4a627d0f623f0ba6e19eb23e5aceae935e41e&w=360');
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
}

.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.header h1 {
    color: rgb(214, 147, 45);
    text-decoration: underline;
    font-family: 'Orbitron', sans-serif;
}

#input {
    font-size: 18px;
    padding: 5px 10px;
    outline: none;
    border: none;
    border-radius: 15px;
    background: aliceblue;
}

#search {
    background: none;
    padding: 5px 20px;
    color: aliceblue;
    outline: none;
    background: cadetblue;
    font-size: 17px;
    border-radius: 15px;
    cursor: pointer;
    border: none;
}

.weather {
    text-align: center;
    color: aliceblue;
}

#city {
    font-size: 30px;
}

.weather img {
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background: rgba(240, 248, 255, 0.408);
}

#temperature {
    font-size: 50px;
    margin: 0;
    margin-left: 30px;
    margin-bottom: 10px;
}

.temp-box {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 30px 0;
}

#clouds {
    font-size: 20px;
    background: rgba(153, 205, 50, 0.778);
    padding: 2px 20px;
    border-radius: 15px;
}

main {
    display: grid;
    grid-column-gap: 25px;
    grid-template-columns: 1fr 5px 1fr;
    align-items: center;
    margin: 0 50px;
    color: white;
}

.next {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: 10px 0;
}

.next p,
.next h3 {
    margin: 3px 0;
}

.forecstD {
    margin: 20px 50px;
    color: aliceblue;
}

.weekF {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
}

.cast-header {
    color: aliceblue;
    background: rgba(254, 189, 132, 0.539);
    width: max-content;
    padding: 5px 15px;
    border-radius: 20px;
    font-size: 18px;
    margin-bottom: 5px;
}

.divider1,
.divider2 {
    background: rgba(254, 189, 132, 0.539);
    height: 200px;
    border-radius: 5px;
}

.divider2 {
    height: 5px;
    width: 30%;
    margin: 0 auto;
}

.time,
.date {
    color: rgb(254, 189, 132);
}

.desc {
    color: rgb(196, 255, 77);
}
Javascript
//src/App.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css'; 

const apikey = "feff206daa60b539abe8fae8f2ab7f29";

function App() {
  const [city, setCity] = useState('');
  const [weatherData, setWeatherData] = useState(null);

  useEffect(() => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition((position) => {
        const { latitude, longitude } = position.coords;
        const url = `http://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${apikey}`;

        fetchWeatherData(url);
      });
    }
  }, []);

  const fetchWeatherData = async (url) => {
    try {
      const response = await axios.get(url);
      const data = response.data;
      console.log(data);
      weatherReport(data);
      setWeatherData(data);
      // Send data to backend for storage
      saveWeatherData(data);
    } catch (error) {
      console.error('Error fetching weather data:', error);
    }
  };

  const searchByCity = async () => {
    try {
      const urlsearch = `http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apikey}`;
      const response = await axios.get(urlsearch);
      const data = response.data;
      console.log(data);
      weatherReport(data);
      setWeatherData(data);
      // Send data to backend for storage
      saveWeatherData(data);
    } catch (error) {
      console.error('Error fetching weather data:', error);
    }
    setCity('');
  };

  const saveWeatherData = async (data) => {
    try {
      const response = await axios.post('http://localhost:5000/api/weather', {
        city: data.name,
        country: data.sys.country,
        temperature: Math.floor(data.main.temp - 273),
        description: data.weather[0].description,
        icon: data.weather[0].icon,
      });
      console.log('Weather data saved to database:', response.data);
    } catch (error) {
      console.error('Error saving weather data to database:', error);
    }
  };

  const weatherReport = async (data) => {
    const urlcast = `http://api.openweathermap.org/data/2.5/forecast?q=${data.name}&appid=${apikey}`;
    try {
      const response = await axios.get(urlcast);
      const forecast = response.data;
      console.log(forecast.city);
      hourForecast(forecast);
      dayForecast(forecast);

      console.log(data);
      document.getElementById('city').innerText = data.name + ', ' + data.sys.country;
      console.log(data.name, data.sys.country);

      console.log(Math.floor(data.main.temp - 273));
      document.getElementById('temperature').innerText = Math.floor(data.main.temp - 273) + ' °C';

      document.getElementById('clouds').innerText = data.weather[0].description;
      console.log(data.weather[0].description);

      let icon1 = data.weather[0].icon;
      let iconurl = "http://api.openweathermap.org/img/w/" + icon1 + ".png";
      document.getElementById('img').src = iconurl;
    } catch (error) {
      console.error('Error fetching forecast data:', error);
    }
  };

  const hourForecast = (forecast) => {
    document.querySelector('.templist').innerHTML = '';
    for (let i = 0; i < 5; i++) {
      var date = new Date(forecast.list[i].dt * 1000);
      console.log((date.toLocaleTimeString(undefined, 'Asia/Kolkata')).replace(':00', ''));

      let hourR = document.createElement('div');
      hourR.setAttribute('class', 'next');

      let div = document.createElement('div');
      let time = document.createElement('p');
      time.setAttribute('class', 'time');
      time.innerText = (date.toLocaleTimeString(undefined, 'Asia/Kolkata')).replace(':00', '');

      let temp = document.createElement('p');
      temp.innerText = Math.floor((forecast.list[i].main.temp_max - 273)) + ' °C' + ' / ' + Math.floor((forecast.list[i].main.temp_min - 273)) + ' °C';

      div.appendChild(time);
      div.appendChild(temp);

      let desc = document.createElement('p');
      desc.setAttribute('class', 'desc');
      desc.innerText = forecast.list[i].weather[0].description;

      hourR.appendChild(div);
      hourR.appendChild(desc);
      document.querySelector('.templist').appendChild(hourR);
    }
  };

  const dayForecast = (forecast) => {
    document.querySelector('.weekF').innerHTML = '';
    for (let i = 8; i < forecast.list.length; i += 8) {
      console.log(forecast.list[i]);
      let div = document.createElement('div');
      div.setAttribute('class', 'dayF');

      let day = document.createElement('p');
      day.setAttribute('class', 'date');
      day.innerText = new Date(forecast.list[i].dt * 1000).toDateString(undefined, 'Asia/Kolkata');
      div.appendChild(day);

      let temp = document.createElement('p');
      temp.innerText = Math.floor((forecast.list[i].main.temp_max - 273)) + ' °C' + ' / ' + Math.floor((forecast.list[i].main.temp_min - 273)) + ' °C';
      div.appendChild(temp);

      let description = document.createElement('p');
      description.setAttribute('class', 'desc');
      description.innerText = forecast.list[i].weather[0].description;
      div.appendChild(description);

      document.querySelector('.weekF').appendChild(div);
    }
  };

  return (
    <div>
      <div className="header">
        <h1>WEATHER APP</h1>
        <div>
          <input
            type="text"
            name=""
            id="input"
            placeholder="Enter city name"
            value={city}
            onChange={(e) => setCity(e.target.value)}
          />
          <button id="search" onClick={searchByCity}>
            Search
          </button>
        </div>
      </div>

      <main>
        <div className="weather">
          <h2 id="city">Delhi,IN</h2>
          <div className="temp-box">
            <img src="/weathericon.png" alt="" id="img" />
            <p id="temperature">26 °C</p>
          </div>
          <span id="clouds">Broken Clouds</span>
        </div>
        <div className="divider1"></div>

        <div className="forecstH">
          <p className="cast-header">Upcoming forecast</p>
          <div className="templist">
            {/* Hourly forecast will be rendered here */}
          </div>
        </div>
      </main>

      <div className="forecstD">
        <div className="divider2"></div>
        <p className="cast-header"> Next 4 days forecast</p>
        <div className="weekF">
          {/* Daily forecast will be rendered here */}
        </div>
      </div>
    </div>
  );
}

export default App;

To start frontend code:

npm start

Output:

forecast

Weather Forecast final output



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads