[
  {
    "path": ".gitignore",
    "content": "node_modules\n.env"
  },
  {
    "path": "MIT-LICENSE.txt",
    "content": "Copyright (c) 2021 Brad Traversy\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "index.js",
    "content": "const express = require('express')\nconst cors = require('cors')\nconst rateLimit = require('express-rate-limit')\nrequire('dotenv').config()\nconst errorHandler = require('./middleware/error')\n\nconst PORT = process.env.PORT || 5000\n\nconst app = express()\n\n// Rate limiting\nconst limiter = rateLimit({\n  windowMs: 10 * 60 * 1000, // 10 Mins\n  max: 100,\n})\napp.use(limiter)\napp.set('trust proxy', 1)\n\n// Enable cors\napp.use(cors())\n\n// Set static folder\napp.use(express.static('public'))\n\n// Routes\napp.use('/api', require('./routes'))\n\n// Error handler middleware\napp.use(errorHandler)\n\napp.listen(PORT, () => console.log(`Server running on port ${PORT}`))\n"
  },
  {
    "path": "middleware/error.js",
    "content": "const errorHandler = (err, req, res, next) => {\n  console.log(123)\n  if (res.headersSent) {\n    return next(err)\n  }\n\n  res.status(500).json({\n    success: false,\n    error: err.message || 'Server Error',\n    stack: process.env.NODE_ENV === 'production' ? undefined : err.stack,\n  })\n}\n\nmodule.exports = errorHandler\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"api-proxy-server\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Proxy server to hide public API keys with rate limiting, caching\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"node index\",\n    \"dev\": \"nodemon index\"\n  },\n  \"author\": \"Brad Traversy\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"apicache\": \"^1.6.2\",\n    \"cors\": \"^2.8.5\",\n    \"dotenv\": \"^10.0.0\",\n    \"express\": \"^4.17.1\",\n    \"express-rate-limit\": \"^5.5.0\",\n    \"needle\": \"^3.0.0\"\n  },\n  \"devDependencies\": {\n    \"nodemon\": \"^2.0.14\"\n  }\n}\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"stylesheet\" href=\"style.css\" />\n\n    <title>Weather App</title>\n  </head>\n  <body>\n    <div class=\"container\">\n      <div class=\"weather\"></div>\n      <form id=\"weather-form\">\n        <input type=\"text\" id=\"city-input\" placeholder=\"Enter a city\" />\n        <button type=\"submit\">Submit</button>\n      </form>\n    </div>\n\n    <script src=\"main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "public/main.js",
    "content": "const weatherDisplay = document.querySelector('.weather')\nconst weatherForm = document.querySelector('#weather-form')\nconst cityInput = document.querySelector('#city-input')\n\n// Fetch weather data from API\nconst fetchWeather = async (city) => {\n  const url = `/api?q=${city}`\n\n  const res = await fetch(url)\n  const data = await res.json()\n\n  if (data.cod === '404') {\n    alert('City not found')\n    return\n  }\n\n  if (data.cod === 401) {\n    alert('Invalid API Key')\n    return\n  }\n\n  const displayData = {\n    city: data.name,\n    temp: kelvinToFahrenheit(data.main.temp),\n  }\n\n  addWeatherToDOM(displayData)\n}\n\n// Add display data to DOM\nconst addWeatherToDOM = (data) => {\n  weatherDisplay.innerHTML = `\n    <h1>Weather in ${data.city}</h1>\n    <h2>${data.temp} &deg;F</h2>\n  `\n  cityInput.value = ''\n}\n\n// Convert Kelvin to Fahrenheit\nconst kelvinToFahrenheit = (temp) => {\n  return Math.ceil(((temp - 273.15) * 9) / 5 + 32)\n}\n\n// Event listener for form submission\nweatherForm.addEventListener('submit', (e) => {\n  e.preventDefault()\n\n  if (cityInput.value === '') {\n    alert('Please enter a city')\n  } else {\n    fetchWeather(cityInput.value)\n  }\n})\n\n// Initial fetch\nfetchWeather('Miami')\n"
  },
  {
    "path": "public/style.css",
    "content": "@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');\n\n* {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\nbody {\n  font-family: 'Poppins', sans-serif;\n  line-height: 1.6;\n  background-color: steelblue;\n  color: #fff;\n}\n\nh2 {\n  font-size: 3rem;\n  font-weight: 600;\n  margin-bottom: 1.5rem;\n  font-weight: bold;\n}\n\n.container {\n  max-width: 500px;\n  margin: 100px auto;\n  text-align: center;\n}\n\ninput {\n  width: 100%;\n  padding: 10px;\n  border: none;\n  border-bottom: 2px solid #fff;\n  background-color: transparent;\n  color: #fff;\n  font-size: 1.5rem;\n  font-weight: bold;\n}\n\ninput:focus {\n  outline: none;\n}\n\ninput::placeholder {\n  color: #fff;\n}\n\nbutton {\n  padding: 13px;\n  width: 300px;\n  border: none;\n  background-color: #000;\n  color: #fff;\n  margin-top: 30px;\n}\n\nbutton:hover {\n  background-color: #fff;\n  color: #000;\n  cursor: pointer;\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# Node API Proxy Server\n\nServer used for hiding API keys, rate limiting and caching. This uses the [OpenWeather API](https://openweathermap.org/api) but you can easily change it to whatever public API you are using\n\n## Usage\n\n### Install dependencies\n\n```bash\nnpm install\n```\n\n### Run on http://localhost:5000\n\n```bash\nnpm run dev\n```\n\n### Add public API info\n\nRename **.env.example** to **.env** and edit the values\n\nIf the public API URL is **https://api.openweathermap.org/data/2.5/weather?q={city}&appid={APIkey}**\n\n- API_BASE_URL = \"https://api.openweathermap.org/data/2.5/weather\"\n- API_KEY_NAME = \"appid\"\n- API_KEY_VALUE = \"YOUR API KEY\"\n\nYou can add on any other query params as needed when hitting the /api endpoint such as https://yourdomain/api?q=detroit without having to add your key in the client\n\n- Add new routes as you see fit\n- Change rate limiting and caching to desired values\n\nThis project is from this [YouTube tutorial](https://youtu.be/ZGymN8aFsv4)\n"
  },
  {
    "path": "routes/index.js",
    "content": "const url = require('url')\nconst express = require('express')\nconst router = express.Router()\nconst needle = require('needle')\nconst apicache = require('apicache')\n\n// Env vars\nconst API_BASE_URL = process.env.API_BASE_URL\nconst API_KEY_NAME = process.env.API_KEY_NAME\nconst API_KEY_VALUE = process.env.API_KEY_VALUE\n\n// Init cache\nlet cache = apicache.middleware\n\nrouter.get('/', cache('2 minutes'), async (req, res, next) => {\n  try {\n    const params = new URLSearchParams({\n      [API_KEY_NAME]: API_KEY_VALUE,\n      ...url.parse(req.url, true).query,\n    })\n\n    const apiRes = await needle('get', `${API_BASE_URL}?${params}`)\n    const data = apiRes.body\n\n    // Log the request to the public API\n    if (process.env.NODE_ENV !== 'production') {\n      console.log(`REQUEST: ${API_BASE_URL}?${params}`)\n    }\n\n    res.status(200).json(data)\n  } catch (error) {\n    next(error)\n  }\n})\n\nmodule.exports = router\n"
  }
]