[DOCKER] Inexperienced create a weather app using OpenWeatherMap and deploy it to Netlify

This time, I made a weather application using OpenWeatherMap and deployed it to Netlify, so I will record it as a study when using the API with Vue.

Get the API key

I was originally a Rakuten member, so I got it from Rakuten Rapid API.

Introduce docker

I'm studying docker, so I introduced it.

Dockerfile


FROM node:14.15.1

WORKDIR /app

ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

RUN apt-get update -y && \
    apt-get upgrade -y && \
    yarn global add @vue/[email protected]

ADD . /app

docker-compose.yml


version: "3"

services:
  web:
    container_name: web
    build: ./web/
    image: web
    volumes:
      - ./web:/app
    ports:
      - 8080:8080
    privileged: true

    command: "yarn run serve"

Install vue with vue-cli.

I created a web folder and installed it in it.

Get weather information

WeatherHome.vue


<template>
  <section id="weatherHome">
    <input
      v-model="getWeatherQuery"
      placeholder="Please enter the location you want to look up"
    />
    <button @click="getWeather">Check the weather</button>
    <div v-if="hasGetWeatherData" id="weatherCard">
      <h1 class="weatherCardHeader">{{ getWeatherResult.name }}</h1>
      <div class="weatherCardDescription">
        {{ getWeatherInfo.description }}
      </div>
      <div class="weatherCardWeatherData">
        <img :src="getWeatherImageUrl" class="weatherCardImage" />
        <p class="weatherCardTemp">
          <v-icon name="thermometer-half" scale="2" />
          {{ getWeatherResult.main.temp }} ℃
        </p>
      </div>
      <div class="weatherCardWeatherOtherData">
        <div class="weatherCardWindSpeed">
          <v-icon name="wind" scale="2" />
          {{ getWeatherResult.wind.speed + "m/s" }}
        </div>
        <div class="weatherCardHumidity">
          <v-icon name="tint" scale="2" />
          {{ getWeatherResult.main.humidity + "%" }}
        </div>
      </div>
wind speed:{{ getWeatherResult.wind.speed }}Humidity:{{
        getWeatherResult.main.humidity
      }}
    </div>
  </section>
</template>

<script>
import axios from "axios";
export default {
  name: "WeatherHome",
  data() {
    return {
      getWeatherQuery: "",
      getWeatherResult: {},
      getWeatherInfo: {},
      getWeatherImageUrl: "",
      hasGetWeatherData: false,
    };
  },
  methods: {
    getWeather() {
      let apiURL = "https://community-open-weather-map.p.rapidapi.com/weather";
      axios
        .get(apiURL, {
          headers: {
            "content-type": "application/octet-stream",
            "x-rapidapi-host": process.env.VUE_APP_OPEN_WEATHER_MAP_API_HOST,
            "x-rapidapi-key": process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
            useQueryString: true,
          },
          params: {
            id: "2172797",
            lang: "ja",
            units: "metric",
            q: this.getWeatherQuery,
          },
        })
        .then((response) => {
          this.getWeatherResult = response.data;
          this.getWeatherInfo = Object.assign(...this.getWeatherResult.weather);
          this.getWeatherImageUrl =
            "http://openweathermap.org/img/wn/" +
            this.getWeatherInfo.icon +
            "@4x.png ";
          this.hasGetWeatherData = true;
          console.log(response);
        })
        .catch((error) => {
          console.log(error);
        });
    },
  },
};
</script>

<style>
#weatherCard {
  background-color: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.16);
  color: #212121;
  text-decoration: none;
  height: 400px;
  width: 400px;
  margin: auto;
}
.weatherCardWeatherData {
  height: 50%;
  display: flex;
  align-items: center;
}
.weatherCardHeader {
  height: 10%;
  text-align: center;
  padding-top: 2%;
}
.weatherCardDescription {
  height: 5%;
  font-size: 150%;
  text-align: center;
  padding: 0.5% 1%;
}
.weatherCardImage {
  margin-left: auto;
}
.weatherCardTemp {
  font-size: 120%;
  margin-right: 15px;
  padding: 1% 1%;
}
.weatherCardWeatherOtherData {
  height: 15%;
  position: relative;
}
.weatherCardWindSpeed {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  left: 70px;
}
.weatherCardHumidity {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  right: 70px;
}
</style>

I use the API key of env.local to get information with axios, display each information, and use vue-awesome to make it look a little better.

Introduce Vuex

I created WeatherAnime.vue because I wanted to see it a little more elaborately, such as displaying a rain animation when it was raining. By the way, I thought I had never used Vuex, and now I have two components, so I introduced it.

store.js


import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    apiKey: process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
    apiHost: "https://community-open-weather-map.p.rapidapi.com/weather",
    getWeatherQuery: "",
    getWeatherResult: {},
    hasGetWeatherData: false,
  },
  mutations: {
    setGetWeatherQuery(state, val) {
      state.getWeatherQuery = val;
    },
    setGetWeatherResult(state, data) {
      state.getWeatherResult = data;
    },
    setHasGetWeatherData(state, data) {
      state.hasGetWeatherData = data;
    },
  },
  getters: {
    getWeatherResult: (state) => {
      return String(state.getWeatherResult);
    },
    hasGetWeatherData: (state) => {
      return Boolean(state.hasGetWeatherData);
    },
  },
  actions: {
    async getWeather({ commit, state }, getWeatherQuery) {
      try {
        commit("setGetWeatherQuery", getWeatherQuery);

        const response = await axios.get(state.apiHost, {
          headers: {
            "content-type": "application/octet-stream",
            "x-rapidapi-host": process.env.VUE_APP_OPEN_WEATHER_MAP_API_HOST,
            "x-rapidapi-key": process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
            useQueryString: true,
          },
          params: {
            lang: "ja",
            units: "metric",
            q: state.getWeatherQuery,
          },
        });

        const newWeatherData = {
          name: response.data.name,
          temp: response.data.main.temp,
          description: response.data.weather[0].description,
          icon: response.data.weather[0].icon,
          info: response.data.weather[0].main,
          wind: response.data.wind.speed,
          humidity: response.data.main.humidity,
        };

        commit("setGetWeatherResult", newWeatherData);
        commit("setHasGetWeatherData", true);
      } catch (error) {
        console.log(error);
      }
    },
  },
});

export default store;

WeatherHome.vue


<template>
  <section id="weatherHome">
    <input v-model.trim="search" placeholder="Please enter the location you want to look up" />
    <button @click="getData">Check the weather</button>
    <div v-if="this.hasGetWeatherData" id="weatherCard">
      <h1 class="weatherCardHeader">
        {{ this.getWeatherResult.name }}
      </h1>
      <div class="weatherCardDescription">
        {{ this.getWeatherResult.description }}
      </div>
      <div class="weatherCardWeatherData">
        <img
          :src="
            `http://openweathermap.org/img/wn/${this.getWeatherResult.icon}@4x.png`
          "
          class="weatherCardImage"
        />
        <p class="weatherCardTemp">
          <v-icon name="thermometer-half" scale="2" />
          {{ this.getWeatherResult.temp }} ℃
        </p>
      </div>
      <div class="weatherCardWeatherOtherData">
        <div class="weatherCardWindSpeed">
          <v-icon name="wind" scale="2" />
          {{ this.getWeatherResult.wind + "m/s" }}
        </div>
        <div class="weatherCardHumidity">
          <v-icon name="tint" scale="2" />
          {{ this.getWeatherResult.humidity + "%" }}
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import { mapActions, mapState } from "vuex";
export default {
  name: "WeatherHome",
  data() {
    return {
      search: "",
    };
  },
  computed: {
    weatherQuery: {
      get() {
        return this.$store.state.getWeatherQuery;
      },
      set(value) {
        this.$store.commit("setGetWeatherQuery", value);
      },
    },
    ...mapState(["getWeatherResult", "hasGetWeatherData"]),
  },
  methods: {
    ...mapActions(["getWeather"]),
    getData() {
      this.getWeather(this.search);
    },
  },
};
</script>

<style>
#weatherCard {
  background-color: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.16);
  color: #212121;
  text-decoration: none;
  height: 400px;
  width: 400px;
  margin: auto;
}
.weatherCardWeatherData {
  height: 50%;
  display: flex;
  align-items: center;
}
.weatherCardHeader {
  height: 10%;
  text-align: center;
  padding-top: 2%;
}
.weatherCardDescription {
  height: 5%;
  font-size: 150%;
  text-align: center;
  padding: 0.5% 1%;
}
.weatherCardImage {
  margin-left: auto;
}
.weatherCardTemp {
  font-size: 120%;
  margin-right: 15px;
  padding: 1% 1%;
}
.weatherCardWeatherOtherData {
  height: 15%;
  position: relative;
}
.weatherCardWindSpeed {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  left: 70px;
}
.weatherCardHumidity {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  right: 70px;
}
</style>

Change the background image

I wanted to make an animation, but I couldn't understand it even after reading various sources on github, so I hurriedly changed the background image.

WeatherAnime.vue


<template>
  <div
    class="weather-anime"
    :class="[
      { 'weather-rain': getWeatherResult.info === 'Rain' },
      { 'weather-snow': getWeatherResult.info == 'Snow' },
      { 'weather-clear': getWeatherResult.info == 'Clear' },
      { 'weather-clouds': getWeatherResult.info == 'Clouds' },
    ]"
  ></div>
</template>

<script>
import { mapState } from "vuex";
export default {
  computed: {
    ...mapState(["getWeatherResult"]),
  },
};
</script>

<style scoped>
.weather-anime {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}
.weather-rain {
  background-image: url("../assets/images/rain.jpg ");
}
.weather-clouds {
  background-image: url("../assets/images/clouds.jpg ");
}
.weather-clear {
  background-image: url("../assets/images/clear.jpg ");
}
</style>

Fix a little

Tokyo was specified for the button design change and initial data.

WeatherHome.vue


<div class="weatherSearch">
<input
  v-model.trim="search"
  placeholder="Please enter the location you want to look up"
/>
<button @click="getData" class="weatherCardButton">Check the weather</button>
</div>

<script>
created: function() {
  this.getWeather("Tokyo");
},
</script>

<style>
.weatherCardButton {
  border: 2px solid transparent;
}
.weatherCardButton:hover {
  background: transparent;
  border-color: white;
}
</style>

Deploy to Netlify

netlify.toml


[build]
  base = "/web"
[build.environment]
  YARN_VERSION = "1.22.5"
  YARN_FLAGS = "--no-ignore-optional"
  NODE_VERSION = "14.15.3"

At the end

It was completed, but when I read the code using OpenWeatherMap on Github, I am keenly aware of how low my code level is. The code is saved on Github, so if you want to see it, please. Recently, I'm thinking that it is better to learn the pattern of how to make by code reading and copying sutras in order to improve.

Recommended Posts

Inexperienced create a weather app using OpenWeatherMap and deploy it to Netlify
Create a Spring Boot web app that uses IBM Cloudant and deploy it to Cloud Foundry
Create a portfolio app using Java and Spring Boot
[Rails] Create sitemap using sitemap_generator and deploy to GAE
Try to create a server-client app
Create a program to post to Slack with GO and make it a container
Steps to create a simple camel app using Apache Camel Spring Boot starters
[Rails] How to create a graph using lazy_high_charts
A memo to simply create a form using only HTML and CSS in Rails 6
I tried to create a LINE clone app
Until you create a Spring Boot project in Intellij and push it to Github
When making a personal app, I was wondering whether to make it using haml
How to create a header or footer once and use it on another page
I tried using Wercker to create and publish a Docker image that launches GlassFish 5.
Create a Java Servlet and JSP WAR file to deploy to Apache Tomcat 9 in Gradle
A memorandum when trying to create a GUI using JavaFX
How to convert A to a and a to A using AND and OR in Java
Click the [rails] button to create a random alphanumeric password and enter it in the password field
Assign a Java8 lambda expression to a variable and reuse it
[Rails] I tried to create a mini app with FullCalendar
How to deploy a simple Java Servlet app on Heroku
How to deploy jQuery in your Rails app using Webpacker
How to deploy a kotlin (java) app on AWS fargate
How to develop and register a Sota app in Java
How to join a table without using DBFlute and sql
How to create and launch a Dockerfile for Payara Micro
Create a fortune using Ruby
How to create a method
How to create a jar file or war file using the jar command
I tried to create a simple map app in Android Studio
I want to create a chat screen for the Swift chat app!
Create a memo app with Tomcat + JSP + Servlet + MySQL using Eclipse
Deploy Rails apps to Azure App Service using Docker & Continuous Deployment
[Rails 6] How to create a dynamic form input screen using cocoon
Create a JVM for app distribution with JDK9 modules and jlink
How to deploy an app that references a local jar to heroku
How to read a file and treat it as standard input
Easy way to create a mapping class when using the API
Image Spring Boot app using jib-maven-plugin and start it with Docker
Use Jenkins to build inside Docker and then create a Docker image.
Create a Docker Image for redoc-cli and register it on Docker Hub
I want to download a file on the Internet using Ruby and save it locally (with caution)