Repository: bradtraversy/node-api-proxy-server Branch: main Commit: deef7d7e53ab Files: 10 Total size: 7.0 KB Directory structure: gitextract_mxqabiz3/ ├── .gitignore ├── MIT-LICENSE.txt ├── index.js ├── middleware/ │ └── error.js ├── package.json ├── public/ │ ├── index.html │ ├── main.js │ └── style.css ├── readme.md └── routes/ └── index.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules .env ================================================ FILE: MIT-LICENSE.txt ================================================ Copyright (c) 2021 Brad Traversy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: index.js ================================================ const express = require('express') const cors = require('cors') const rateLimit = require('express-rate-limit') require('dotenv').config() const errorHandler = require('./middleware/error') const PORT = process.env.PORT || 5000 const app = express() // Rate limiting const limiter = rateLimit({ windowMs: 10 * 60 * 1000, // 10 Mins max: 100, }) app.use(limiter) app.set('trust proxy', 1) // Enable cors app.use(cors()) // Set static folder app.use(express.static('public')) // Routes app.use('/api', require('./routes')) // Error handler middleware app.use(errorHandler) app.listen(PORT, () => console.log(`Server running on port ${PORT}`)) ================================================ FILE: middleware/error.js ================================================ const errorHandler = (err, req, res, next) => { console.log(123) if (res.headersSent) { return next(err) } res.status(500).json({ success: false, error: err.message || 'Server Error', stack: process.env.NODE_ENV === 'production' ? undefined : err.stack, }) } module.exports = errorHandler ================================================ FILE: package.json ================================================ { "name": "api-proxy-server", "version": "1.0.0", "description": "Proxy server to hide public API keys with rate limiting, caching", "main": "index.js", "scripts": { "start": "node index", "dev": "nodemon index" }, "author": "Brad Traversy", "license": "MIT", "dependencies": { "apicache": "^1.6.2", "cors": "^2.8.5", "dotenv": "^10.0.0", "express": "^4.17.1", "express-rate-limit": "^5.5.0", "needle": "^3.0.0" }, "devDependencies": { "nodemon": "^2.0.14" } } ================================================ FILE: public/index.html ================================================ Weather App
================================================ FILE: public/main.js ================================================ const weatherDisplay = document.querySelector('.weather') const weatherForm = document.querySelector('#weather-form') const cityInput = document.querySelector('#city-input') // Fetch weather data from API const fetchWeather = async (city) => { const url = `/api?q=${city}` const res = await fetch(url) const data = await res.json() if (data.cod === '404') { alert('City not found') return } if (data.cod === 401) { alert('Invalid API Key') return } const displayData = { city: data.name, temp: kelvinToFahrenheit(data.main.temp), } addWeatherToDOM(displayData) } // Add display data to DOM const addWeatherToDOM = (data) => { weatherDisplay.innerHTML = `

Weather in ${data.city}

${data.temp} °F

` cityInput.value = '' } // Convert Kelvin to Fahrenheit const kelvinToFahrenheit = (temp) => { return Math.ceil(((temp - 273.15) * 9) / 5 + 32) } // Event listener for form submission weatherForm.addEventListener('submit', (e) => { e.preventDefault() if (cityInput.value === '') { alert('Please enter a city') } else { fetchWeather(cityInput.value) } }) // Initial fetch fetchWeather('Miami') ================================================ FILE: public/style.css ================================================ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap'); * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Poppins', sans-serif; line-height: 1.6; background-color: steelblue; color: #fff; } h2 { font-size: 3rem; font-weight: 600; margin-bottom: 1.5rem; font-weight: bold; } .container { max-width: 500px; margin: 100px auto; text-align: center; } input { width: 100%; padding: 10px; border: none; border-bottom: 2px solid #fff; background-color: transparent; color: #fff; font-size: 1.5rem; font-weight: bold; } input:focus { outline: none; } input::placeholder { color: #fff; } button { padding: 13px; width: 300px; border: none; background-color: #000; color: #fff; margin-top: 30px; } button:hover { background-color: #fff; color: #000; cursor: pointer; } ================================================ FILE: readme.md ================================================ # Node API Proxy Server Server 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 ## Usage ### Install dependencies ```bash npm install ``` ### Run on http://localhost:5000 ```bash npm run dev ``` ### Add public API info Rename **.env.example** to **.env** and edit the values If the public API URL is **https://api.openweathermap.org/data/2.5/weather?q={city}&appid={APIkey}** - API_BASE_URL = "https://api.openweathermap.org/data/2.5/weather" - API_KEY_NAME = "appid" - API_KEY_VALUE = "YOUR API KEY" You 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 - Add new routes as you see fit - Change rate limiting and caching to desired values This project is from this [YouTube tutorial](https://youtu.be/ZGymN8aFsv4) ================================================ FILE: routes/index.js ================================================ const url = require('url') const express = require('express') const router = express.Router() const needle = require('needle') const apicache = require('apicache') // Env vars const API_BASE_URL = process.env.API_BASE_URL const API_KEY_NAME = process.env.API_KEY_NAME const API_KEY_VALUE = process.env.API_KEY_VALUE // Init cache let cache = apicache.middleware router.get('/', cache('2 minutes'), async (req, res, next) => { try { const params = new URLSearchParams({ [API_KEY_NAME]: API_KEY_VALUE, ...url.parse(req.url, true).query, }) const apiRes = await needle('get', `${API_BASE_URL}?${params}`) const data = apiRes.body // Log the request to the public API if (process.env.NODE_ENV !== 'production') { console.log(`REQUEST: ${API_BASE_URL}?${params}`) } res.status(200).json(data) } catch (error) { next(error) } }) module.exports = router