diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy-vercel.yml
similarity index 60%
rename from .github/workflows/deploy.yml
rename to .github/workflows/deploy-vercel.yml
index cb90dec..8867d9c 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy-vercel.yml
@@ -1,5 +1,9 @@
name: Deploy to Production Environment
+# This workflow deploys the backend, a full-server express app to Vercel
+# as opposed to their recommended approach of deploying serverless functions
+# using NextJS "pages/api". Vercel CLI v28.2.0 is used for deployment
+
# This workflow will trigger on any tag/release created on *any* branch
# Make sure to create tags/releases only from the "master" branch for consistency
on:
@@ -60,7 +64,7 @@ jobs:
npm install
npm run lint
- deploy-docs:
+ deploy-client:
name: Deploy client to Github Pages
needs: lint-client
runs-on: ubuntu-latest
@@ -77,3 +81,37 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./
publish_branch: gh-pages
+
+ deploy-server:
+ name: Deploy Server to Vercel
+ needs: lint-server
+ runs-on: ubuntu-latest
+ env:
+ VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
+ VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
+ VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
+ steps:
+ - name: Checkout the repository
+ uses: actions/checkout@v3
+ - name: Use NodeJS v16.14.2
+ uses: actions/setup-node@v3
+ with:
+ node-version: 16.14.2
+ - name: Install Vercel CLI
+ run: npm install --global vercel@28.2.0
+ - name: Pull Vercel Environment Information
+ run: |
+ cd server
+ vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
+ - name: Build Project Artifacts
+ run: |
+ cd server
+ vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
+ - name: Deploy Project Artifacts to Vercel
+ run: |
+ cd server
+ vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
+ - name: Post Deployment Clean-up
+ run: |
+ cd server
+ rm -r -f .vercel
diff --git a/.gitignore b/.gitignore
index 5a37509..8b12bc1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,7 @@ node_modules/
.vscode
.env
*.zip
+
+.vercel
+
+.vercel
diff --git a/server/.env.example b/server/.env.example
index 1ddb490..cd01f64 100644
--- a/server/.env.example
+++ b/server/.env.example
@@ -1,3 +1,6 @@
ALLOWED_ORIGINS=http://localhost:3000
ALLOW_CORS=0
+API_RATE_LIMIT=100
+API_WINDOW_MS_MINUTES=15
MONGO_URI=mongodb://localhost/todo-next
+DEPLOYMENT_PLATFORM=regular
diff --git a/server/.gitignore b/server/.gitignore
index d767605..fdf1d5d 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -1,3 +1,5 @@
node_modules/
.env
*.zip
+
+.vercel
diff --git a/server/.vercelignore b/server/.vercelignore
new file mode 100644
index 0000000..92eb3ee
--- /dev/null
+++ b/server/.vercelignore
@@ -0,0 +1,15 @@
+node_modules/
+*.env
+*.zip
+*.md
+*.toml
+
+.vercel
+.vscode
+.git
+.github
+.eslintrc.js
+.eslintignore
+.vercelignore
+package.json
+package-lock.json
diff --git a/server/README.md b/server/README.md
index a0c2219..607df92 100644
--- a/server/README.md
+++ b/server/README.md
@@ -38,11 +38,14 @@ The following dependencies are used for this project's localhost development env
3. Set up the environment variables. Create a `.env `file inside the **/server** directory with reference to the `.env.example` file.
- | Variable Name | Description |
- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
- | MONGO_URI | MongoDB connection string.
Default value uses the localhost MongoDB connection string. |
- | ALLOWED_ORIGINS | IP/domain origins in comma-separated values that are allowed to access the API if `ALLOW_CORS=1`.
Include `http://localhost:3000` by default to allow CORS access to the **/client** app. |
- | ALLOW_CORS | Allow Cross-Origin Resource Sharing (CORS) on the API endpoints.
Default value is `1`, allowing access to domains listed in `ALLOWED_ORIGINS`.
Setting to `0` will make all endpoints accept requests from all domains, including Postman. |
+ | Variable Name | Description |
+ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+ | API_WINDOW_MS_MINUTES | Time in `minutes` where `API_RATE_LIMIT` times of successive calls from an IP are allowed on the server. |
+ | API_RATE_LIMIT | It's the maximum number of allowed API requests on the server per `API_WINDOW_MS_MINUTES`.
Users will receive a `429 - Too many requests` server error after hitting the limit.
The limit will reset after API_WINDOW_MS_MINUTES minutes, after which users can resume making API requests. |
+ | ALLOW_CORS | Allow Cross-Origin Resource Sharing (CORS) on the API endpoints.
Default value is `1`, allowing access to domains listed in `ALLOWED_ORIGINS`.
Setting to `0` will make all endpoints accept requests from all domains, including Postman. |
+ | ALLOWED_ORIGINS | IP/domain origins in comma-separated values that are allowed to access the API if `ALLOW_CORS=1`.
Include `http://localhost:3000` by default to allow CORS access to the **/client** app. |
+ | DEPLOYMENT_PLATFORM | This variable refers to the backend `server`'s hosting platform, defaulting to `DEPLOYMENT_PLATFORM=regular`
for full-server NodeJS express apps.
Valid values are:
`regular` - for traditional full-server NodeJS express apps
`vercel` - for Vercel (serverless) |
+ | MONGO_URI | MongoDB connection string.
Default value uses the localhost MongoDB connection string. |
## Usage
diff --git a/server/api/README.md b/server/api/README.md
new file mode 100644
index 0000000..68ddd02
--- /dev/null
+++ b/server/api/README.md
@@ -0,0 +1,13 @@
+## api
+
+The `/api` directory is required by vercel when using the @latest `vercel.json` configuration file settings (for Vercel CLI v28.2.0 as of this writing).
+
+Ideally, this directory should contain **serverless functions** [written per file](https://vercel.com/guides/using-express-with-vercel#next.js) following the NextJS api pages writing guide, but we'd like to deploy a standalone full-server express app on vercel.
+
+### References
+
+[[1]](https://vercel.com/guides/using-express-with-vercel) - using express with vercel
+[[2]](https://vercel.com/docs/project-configuration#project-configuration/functions) - project config with vercel.json
+
+@weaponsforge
+20220828
diff --git a/server/api/index.js b/server/api/index.js
new file mode 100644
index 0000000..9dcb0e9
--- /dev/null
+++ b/server/api/index.js
@@ -0,0 +1,4 @@
+require('dotenv').config()
+const app = require('../src/index')
+
+module.exports = app
diff --git a/server/package-lock.json b/server/package-lock.json
index 6497756..7a597be 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -13,6 +13,7 @@
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.1",
+ "express-rate-limit": "^6.5.2",
"mongoose": "^6.5.2"
},
"devDependencies": {
@@ -1385,6 +1386,17 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/express-rate-limit": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.5.2.tgz",
+ "integrity": "sha512-N0cG/5ccbXfNC+FxRu7ujm2HjKkygF2PL7KLAf/hct9uqKB5QkZVizb/hEst6tUBXnfhblYWgOorN2eY+Saerw==",
+ "engines": {
+ "node": ">= 12.9.0"
+ },
+ "peerDependencies": {
+ "express": "^4 || ^5"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4389,6 +4401,12 @@
"vary": "~1.1.2"
}
},
+ "express-rate-limit": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.5.2.tgz",
+ "integrity": "sha512-N0cG/5ccbXfNC+FxRu7ujm2HjKkygF2PL7KLAf/hct9uqKB5QkZVizb/hEst6tUBXnfhblYWgOorN2eY+Saerw==",
+ "requires": {}
+ },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
diff --git a/server/package.json b/server/package.json
index 9cc1ab6..ff1bc67 100644
--- a/server/package.json
+++ b/server/package.json
@@ -2,9 +2,10 @@
"name": "server",
"version": "1.0.0",
"description": "This directory will contain the backend express server that will serve the Todo CRUD API endpoints.",
- "main": "src/index.js",
+ "main": "api/index.js",
"scripts": {
- "start": "node src/index.js",
+ "start": "node api/index.js",
+ "start:vercel": "node api/index.js",
"dev": "nodemon watch src/index.js",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
@@ -16,6 +17,7 @@
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.1",
+ "express-rate-limit": "^6.5.2",
"mongoose": "^6.5.2"
},
"devDependencies": {
diff --git a/server/public/401.html b/server/public/401.html
new file mode 100644
index 0000000..07d5cd3
--- /dev/null
+++ b/server/public/401.html
@@ -0,0 +1,10 @@
+
+
+
Unauthorized
+ + diff --git a/server/public/404.html b/server/public/404.html new file mode 100644 index 0000000..703c88c --- /dev/null +++ b/server/public/404.html @@ -0,0 +1,10 @@ + + + + +404 Not Found
+ + diff --git a/server/public/index.html b/server/public/index.html new file mode 100644 index 0000000..5353a82 --- /dev/null +++ b/server/public/index.html @@ -0,0 +1,10 @@ + + + + +Welcome to the Todo Notes API v1
+ + diff --git a/server/src/index.js b/server/src/index.js index cc735d5..2b3eae3 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -4,12 +4,19 @@ require('./utils/db') const express = require('express') const cors = require('cors') const cookieParser = require('cookie-parser') +const rateLimit = require('express-rate-limit') const app = express() const PORT = process.env.PORT || 3001 const { corsOptions } = require('./utils/cors_options') const controllers = require('./controllers') +const limiter = rateLimit({ + windowMs: process.env.API_WINDOW_MS_MINUTES * 60 * 1000, // in minutes + max: process.env.API_RATE_LIMIT, // limit each IP to API_RATE_LIMIT requests per windowMs + message: 'Too many requests from this IP. Please try again after 15 minutes.' +}) + // Initialize the express app app.use(express.json()) app.use(express.urlencoded({ extended: false })) @@ -19,7 +26,7 @@ if (process.env.ALLOW_CORS === '1') { app.use(cors(corsOptions)) } -app.use('/api', controllers) +app.use('/api', limiter, controllers) app.get('*', (req, res) => { return res.status(200).send('Welcome to the Todo API') @@ -29,6 +36,10 @@ app.use((err, req, res, next) => { return res.status(500).send(err.message) }) -app.listen(PORT, () => { - console.log(`listening on http://localhost:${PORT}`) -}) +if (process.env.DEPLOYMENT_PLATFORM !== 'vercel') { + app.listen(PORT, () => { + console.log(`listening on http://localhost:${PORT}`) + }) +} + +module.exports = app diff --git a/server/vercel.json b/server/vercel.json new file mode 100644 index 0000000..ff03b2c --- /dev/null +++ b/server/vercel.json @@ -0,0 +1,14 @@ +{ + "rewrites": [ + { + "source": "/api/(.*)", + "destination": "/api" + } + ], + "redirects": [ + { + "source": "/src(.*)", + "destination": "/public/401.html" + } + ] +}