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