{"id":19143154,"url":"https://github.com/devenes/weather-api","last_synced_at":"2025-05-07T00:25:09.984Z","repository":{"id":41826017,"uuid":"453805705","full_name":"devenes/weather-api","owner":"devenes","description":"Weather API deployed on AWS EC2 with CI/CD Automation. Node.js, Express.js, Axios, GitHub Actions, Web Hooks, Docker, Jenkins, CloudFormation, Nginx.","archived":false,"fork":false,"pushed_at":"2023-04-06T14:06:21.000Z","size":1006,"stargazers_count":10,"open_issues_count":5,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-19T16:12:01.861Z","etag":null,"topics":["aws","aws-cloudformation","axios","cloudformation","docker","docker-container","docker-containers","expressjs","github","github-actions","javascript","jenkins","nginx","nginx-proxy","nodejs","webhooks"],"latest_commit_sha":null,"homepage":"https://github.com/devenes/weather-api/actions","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devenes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-30T21:23:39.000Z","updated_at":"2025-04-08T08:09:27.000Z","dependencies_parsed_at":"2024-11-09T07:31:15.082Z","dependency_job_id":"aa04ea63-5265-475c-871a-364fbb8e4bdd","html_url":"https://github.com/devenes/weather-api","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devenes%2Fweather-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devenes%2Fweather-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devenes%2Fweather-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devenes%2Fweather-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devenes","download_url":"https://codeload.github.com/devenes/weather-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252789656,"owners_count":21804477,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-cloudformation","axios","cloudformation","docker","docker-container","docker-containers","expressjs","github","github-actions","javascript","jenkins","nginx","nginx-proxy","nodejs","webhooks"],"created_at":"2024-11-09T07:29:44.543Z","updated_at":"2025-05-07T00:25:09.946Z","avatar_url":"https://github.com/devenes.png","language":"JavaScript","readme":"# Weather API\n\n[![Node.js CI](https://github.com/devenes/weather-api/actions/workflows/node.js.yml/badge.svg)](https://github.com/devenes/weather-api/actions/workflows/node.js.yml)\n\n\u003cp align=\"left\"\u003e \u003ca href=\"https://aws.amazon.com\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://raw.githubusercontent.com/devicons/devicon/master/icons/amazonwebservices/amazonwebservices-original-wordmark.svg\" alt=\"aws\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003ca href=\"https://www.docker.com/\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://raw.githubusercontent.com/devicons/devicon/master/icons/docker/docker-original-wordmark.svg\" alt=\"docker\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003ca href=\"https://expressjs.com\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://raw.githubusercontent.com/devicons/devicon/master/icons/express/express-original-wordmark.svg\" alt=\"express\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003ca href=\"https://git-scm.com/\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://www.vectorlogo.zone/logos/git-scm/git-scm-icon.svg\" alt=\"git\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://raw.githubusercontent.com/devicons/devicon/master/icons/javascript/javascript-original.svg\" alt=\"javascript\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003ca href=\"https://www.jenkins.io\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://www.vectorlogo.zone/logos/jenkins/jenkins-icon.svg\" alt=\"jenkins\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003ca href=\"https://www.nginx.com\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://raw.githubusercontent.com/devicons/devicon/master/icons/nginx/nginx-original.svg\" alt=\"nginx\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003ca href=\"https://nodejs.org\" target=\"_blank\" rel=\"noreferrer\"\u003e \u003cimg src=\"https://raw.githubusercontent.com/devicons/devicon/master/icons/nodejs/nodejs-original-wordmark.svg\" alt=\"nodejs\" width=\"40\" height=\"40\"/\u003e \u003c/a\u003e \u003c/p\u003e\n\n![pipeline](/readme/pipeline.png)\n\nYou can use the following commands if you want to run the application locally:\n\n`npm install`\n\n`npm run start`\n\nYou can see the real-time weather of **Barcelona** in the browser at:\n\n`http://localhost:3456/temperature?city=barcelona`\n\n---\n\n## Application Containerization with Docker\n\nTo containerize the application you need to build a Docker image first. We defined the dependencies, requirements, and instructions in the `Dockerfile`:\n\n```bash\nFROM node:16-alpine\n# Create app directory\nWORKDIR /usr/src/app\n# Install app dependencies\nCOPY package*.json ./\nRUN npm install\n# Copy app files\nCOPY . .\n# Define the port that the app will listen on\nEXPOSE 3456\n# Define the command that will be executed when the container is run\nCMD [ \"node\", \"index.js\" ]\n```\n\nTo build the Docker image you need to use the `docker build` command:\n\n`docker build -t devenes/weather-app:20 .`\n\nAfter you have built the Docker image, run the containerized application using the `docker run` command:\n\n`docker run -p 3456:3456 devenes/weather-app:20`\n\nAfter you have run the Docker image as a container, you can access the app using the following URL on your local machine:\n`http://localhost:3456/`\n\n`http://localhost:3456/temperature?city=barcelona`\n\nTo upload the Docker image to Docker Hub, we used the `docker push` command:\n\n`docker push devenes/weather-app:20`\n\nCheck out the [Docker Hub Profile](https://hub.docker.com/repository/docker/devenes/weather-app) to see the Docker image and the other versions of the containerized application.\n\nYou can download the Docker image from the Docker Hub using the following command:\n\n`docker pull devenes/weather-app`\n\nTo stop the container, use the `docker stop` command:\n\n`docker stop \u003ccontainer_id\u003e`\n\nTo remove the container, use the `docker rm` command:\n\n`docker rm \u003ccontainer_id\u003e`\n\nTo remove the image, use the `docker rmi` command:\n\n`docker rmi \u003cimage_id\u003e`\n\n---\n\n## CI/CD with GitHub Actions\n\nWe used GitHub Actions to automate the build and deployment of our Docker image to Docker Hub. We used this configuration in our GitHub Actions workflow to trigger the build and deploy the image to Docker Hub every time we commit to the \"release\" branch.\n\nThe `on` and `push` sections are defined to run the trigger when a new commit is created in the \"release\" branch.\n\n```bash\nname: Docker Build And Push\non:\n  push:\n    branches:\n      - \"release\"\n```\n\nWrote the stages to build Docker image and login to Docker Hub.\nFor logining to Docker Hub you need to define your Docker Hub credentials in the environment variables on GitHub settings which are called `DOCKER_HUB_USERNAME` and `DOCKER_HUB_PASSWORD`.\n\n```bash\njobs:\n  docker:\n    steps:\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v1\n      - name: Login to DockerHub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n```\n\nOne of the best practices is to name the Docker image tag with using the `run_number` variable in the GitHub workflow to avoid overwriting images and define new versions.\n\n```bash\n- name: Build and push\n  uses: docker/build-push-action@v2\n  with:\n    push: true\n    # use the latest tag from the release branch with run_number\n    tags: devenes/weather-app:${{github.run_number}}\n```\n\n---\n\n## CI/CD with Jenkins using Webhooks\n\nAn alternative way to build and push the Docker image automatically is to use Webhooks on GitHub to trigger the build and push when a new commit is added into the \"release\" branch. You need to add a Webhook configuration on your GitHub repository settings with writing the Jenkins Webhook URL in the `Payload URL` section.\n\nYour `Payload URL` will appear as:\n\n`http://your-jenkins-server:8080/github-webhook/`\n\n- You need to be sure that GitHub plugin is installed and enabled on your Jenkins server: https://plugins.jenkins.io/github/\n\nSimply when you add a new commit on your GitHub repository, you can trigger the Jenkins pipeline by sending a GET request to the `Payload URL`. It means that every time you commit into a specific branch which you selected on Jenkins settings or into any other branch, the Jenkins pipeline will be triggered. By defining the stages on Jenkins pipeline, you can clone your repository automatically or build, push and pull the Docker image.\n\n- Install the Docker Pipelines plugin on Jenkins which allows building, testing, and using Docker images from Jenkins Pipeline projects:\n  https://plugins.jenkins.io/docker-workflow/\n\nFirst, we need to set our credentials on the Jenkins pipeline which we defined in the `Global Configuration` section in `Manage Jenkins`.\n\n```bash\n  environment {\n    registry = \"devenes/weather-app\"\n    registryCredential = 'dockerHub'\n    dockerfile = 'Dockerfile'\n   }\n```\n\nYou can add your Git repository URL in the pipeline stages with Git method or you can set your own Git repository URL in the Jenkins pipeline settings.\n\n```bash\n    stage('Cloning Git') {\n      steps {\n        git 'https://github.com/devenes/best-cloud-academy-api.git'\n      }\n    }\n    stage('Building image') {\n      steps{\n        script {\n          dockerImage = docker.build registry + \":$BUILD_NUMBER\"\n        }\n      }\n    }\n```\n\nIn order to upload the Docker image to Docker Hub in Jenkins pipeline, you need to set your Docker Hub credentials as the environment variables on Jenkins settings, named `dockerHubUser` and `dockerHubPassword` under ID `dockerHub`.\n\n```bash\n    stage('Deploy Image') {\n      steps {\n        withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {\n          sh \"docker login -u ${env.dockerHubUser} -p ${env.dockerHubPassword}\"\n          sh \"docker push devenes/weather-app:20\"\n        }\n      }\n    }\n```\n\nIn other way you can set your credential ID on Jenkins pipeline as the environment variable and use following methods to upload the Docker image to Docker Hub:\n\n```bash\n    stage('Deploy Image') {\n      steps{\n         script {\n            docker.withRegistry( '', registryCredential ) {\n            dockerImage.push()\n          }\n        }\n      }\n    }\n```\n\n- You may face the following error when you try to push the Docker image to Docker Hub.\n  `denied: requested access to the resource is denied`\n  The solution that I tried, you can follow to use the `docker login` command to login to Docker Hub and then use the `docker push` command to push the Docker image to Docker Hub manually. Or you can use the `docker logout` command to logout from Docker Hub. And build your Docker image again. After that, you can use the `docker login` command to login to Docker Hub again and then use the `docker push` command to push the Docker image to Docker Hub. You refresh the Docker Hub credentials on your machine and then you can push the Docker image to Docker Hub.\n\n---\n\n## Getting started with AWS CloudFormation\n\nThe way we chose to implement Jenkins into the CI/CD pipeline is using AWS CloudFormation to create a Stack and deploy it to AWS. The reason we use CloudFormation is to automatically configure and install a server like Nginx and also tools like Git, Docker, and Jenkins.\n\n- Use the `jenkins-server.yml` template file to create a CloudFormation Stack on AWS.\n\n---\n\n## Reverse Proxy with Nginx\n\nAfter running Docker container, we need to configure the app to be available on the Internet. But we will not be configuring the app to be available on the Internet. We will be manupulating the Nginx configuration file to use a reverse proxy to forward requests to the port which is Nginx listening on.\n\nIn the first step, we need to create a new Nginx server and configure it. The earlier we configure the Nginx server, the faster the app will be available on the Internet. So the fastest way to configure the Nginx server is using CloudFormation Stack so that we can edit the Nginx configuration file in the CloudFormation template. The first port we defined is 3456 on the app and Dockerfile when we built and we need to forward to make the app available on the Internet via HTTP protocol by listening on port 80.\n\nWe added the following commands to the CloudFormation template under the `UserData` section:\n\n```bash\n# install nginx\namazon-linux-extras install nginx1.12\n# start nginx\nsystemctl start nginx\n# configure reverse proxy in nginx.conf with adding the following line at the 48th line under server section\nsed -i '48i proxy_pass http://localhost:3456/;' etc/nginx/nginx.conf\n```\n\n- Note: Adding a new line to the Nginx configuration file is not the best way to configure the Nginx server. Replacing the entire Nginx configuration file or directory is the best way to configure the Nginx server.\n\nFinal view of Nginx configuration file:\n\n```bash\nserver {\n    # listen on port 80\n    listen       80 default_server;\n    listen       [::]:80 default_server;\n    server_name  _;\n    root         /usr/share/nginx/html;\n\n    # Load configuration files for the default server block.\n    include /etc/nginx/default.d/*.conf;\n\n    location / {\n    # The port which is our app working on\n    proxy_pass http://localhost:3456/;\n    }\n```\n\n- Also if you want to see the result of the app on the Internet quickly, you can add `Docker pull` and `Docker run` commands into the CloudFormation template under the `UserData` section. So you can quickly see the result of the application on the Internet, without waiting for the manual installation and configuration of the resources.\n\n```bash\ndocker pull devenes/weather-app:20\ndocker run -d -p 3456:3456 --name weather-app devenes/weather-app:20\n```\n\nAt the end of the CloudFormation template, we need to add the following command under the `UserData` section for restarting the Nginx server to be able to see the result of the app on the Internet with reverse proxy on port 80:\n\n```bash\nsystemctl restart nginx.service\n```\n\n---\n\n## Output on the live server\n\n![names](/readme/names.png)\n\n![toronto](/readme/toronto.png)\n\n![madrid](/readme/madrid.png)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevenes%2Fweather-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevenes%2Fweather-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevenes%2Fweather-api/lists"}