Next.js/server.js project
My server.ts file:
import * as express from 'express';
import * as next from 'next';
...
import * as rateLimit from 'express-rate-limit';
import * as config from '../config';
// const apiLimiter = rateLimit({
// windowMs: 1 * 60 * 1000, // 1 minutes
// max: 200,
// message: "Too many requests from this IP"
// });
const routes = require('./nextRoutes');
const app = next({ dev: config.dev });
app.prepare().then(() => {
const server = express();
// server.use(apiLimiter);
...
server.get('*', (req, res) => {
const handle = routes.getRequestHandler(app);
handle(req, res, req.url);
});
server.listen(config.port, (err) => {
if (err) { throw err; }
});
})
question 1: I tried limiting requests to 200 per minute. But this limitation worked for all requests. How can I limit a specific IP address, since it will not work to limit the sessionID, they are all different come? I want to restrict by IP
question 2: Is there any way to get the IP address exceeding the limit, some callback inside the given middleware? I looked through the documentation and understand that there is such a way, but I did not understand how to implement it.
Thanks for any answers or ideas!!! Plus in karma. And have a nice day!
Related
For my senior capstone, my group and I have developed a web-based application to simulate Bitcoin - using react.js for the front-end and node.js/express for the back-end. Up until recently, we've had all of simulation-creating-code (javascript files) inside the src directory, meaning it was being built client-side. Due to high waiting times to create a simulation from all the hashing necessary in transactions, we decided that our simulation-creating-code would be better suited for the back-end rather than the front end. Taking the load off the client and putting it on the server drastically improved the speed of creating a simulation, so 'Great success!'.
When we made this change, we ended up having some issues with require and import statements. Reactjs only supports import statements and Express uses require statements. We had to use some js functions that we developed in our API's so we imported them with require statements, and we thought we thought it was resolved because on our development environment, everything runs as smooth as butter, but once it's deployed, our login page is unable to make an API call. The error is: Failed to load resource: the server responded with a status of 500 (Internal Server Error).
It's interesting because this route in the API worked prior to making this big switch from require to import, and those changes were in other files/routes. The login API remains completely unchanged.
Either way, I'll drop some code in case it's helpful in troubleshooting.
server.js
const express = require("express");
const app = express();
const router = express.Router();
const path = require("path");
var cors = require("cors");
require("dotenv").config();
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb" }));
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
// List of routes
router.use("/api/users", require("./api/users"));
router.use("/api/data", require("./api/data"));
router.use("/api/share", require("./api/share"));
router.use("/api/addresses", require("./api/addresses"));
const root = path.join(__dirname, "client/build");
app.use(express.static(root));
app.use(router);
app.use(cors({ origin: true, credentials: true }));
app.listen(
process.env.PORT,
() => `Server running on port ${process.env.PORT}`
);
api/users.js login route
const express = require("express");
const app = express();
const db = require("../dbConn");
const bcrypt = require("bcrypt-nodejs");
const cors = require("cors");
const router = express.Router();
const jwt = require("jwt-simple");
const config = require("../configuration/config.json");
// to parse JSON
app.use(express.json());
router.post("/login", (req, res) => {
//check if email and password are sent
if (!req.body.email || !req.body.password) {
return res.status(401).json({ error: "Missing username and/or password" });
}
// go into mysql and get info
let qry = `select * from user where email = "${req.body.email}"`;
db.query(qry, (err, rows) => {
if (err) {
return res.status(500).json({ error: err });
}
// assert: no error - process the result set
if (rows.length == 0) {
// no users found
res.status(400).json({ msg: "No users found" });
} else {
// process the user records
let users = [];
rows.forEach((row) => {
let user = {
uid: row.uid,
email: row.email,
role: row.role,
dateCreated: row.created_date,
password: row.password,
};
users.push(user);
});
if (users[0]) {
// Does given password hash match the database password hash?
bcrypt.compare(req.body.password, users[0].password, (err, result) => {
// Send back a token that contains the user's username
const token = jwt.encode({ email: req.body.email }, config.secret);
if (result == true) {
res.status(200).json({
msg: "user authenticated",
fname: users[0].fname,
lname: users[0].lname,
role: users[0].role,
token: token,
});
} else {
res.sendStatus(401);
}
});
}
}
});
});
router.post("/auth", cors(), (req, res) => {
try {
let user = jwt.decode(req.body.token, config.secret);
res.status(200).send(user);
} catch (err) {
res.sendStatus(401);
}
});
SignIn.js client/src/components. This is wrapped in a react.useEffect() arrow function, but again I don't believe the issue is here because this page remains unchanged from a working version.
const handleSubmit = (e) => {
e.preventDefault();
const credentials = { email, password };
// API call to login to account
// if successful, redirect to landing page
// if not, display error message
fetch(`http://${process.env.REACT_APP_API_URL}/api/users/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
})
.then(async (res) => {
if (res.status == 200) {
return res.json();
} else {
throw new Error("Failed to Login!");
}
})
.then(async (res) => {
// Store token in cookie
setCookie("token", res.token, { path: "/${path}", maxAge: 3600 * 24 });
// Toggle state of sign in
toggleSignIn();
// Feedback
setFeedback(true);
setFeedbackObj({ message: "Signed in!", severity: "success" });
//redirect
history.push(`${process.env.PUBLIC_URL}/simulation`);
})
.catch(async (err) => {
// Feedback
setFeedback(true);
setFeedbackObj({ message: "Sign In Error", severity: "error" });
console.error(err);
});
};
If there are any other files that are of interest please let me know.
I've tried to mess with the proxy in package.json, but I don't think thats the answer because it was working previously. I've had a really difficult time finding others with similar issues or resources other than how to build a simple app with Express backend and React.js front end. This is not our issue because our application was working perfectly before this big switch. I believe the issue is stemming from require statements in our API and the running of JS functions in the API. I have no way to confirm this because in production (deployment), the errors are super uninformative, and in development, it runs perfectly fine.
I have been trying to solve this issue for a couple of weeks now, and I've made very little progress. If anyone has suggestions or tips on troubleshooting deployment, I would greatly appreciate it.
Thanks!
I've my site built on node + express, and trying to enable gzip compression using this tutorial but it didn't worked as shown in the tutorial. I can't see Content-Encoding in response header.
Here is my code.
const compression = require('compression');
const express = require("express");
const path = require("path");
var redirects = require('express-seo-redirects');
var proxy = require('express-http-proxy');
require('dotenv').config()
/**
* App Variables
*/
const app = express();
app.use(compression({ filter: shouldCompress, threshold: 0 }));
//app.use(compression()) - //I've also tried this.
function shouldCompress (req, res) {
if (req.headers['x-no-compression']) {
// don't compress responses with this request header
return false
}
// fallback to standard filter function
return compression.filter(req, res)
}
let setCache = function (req, res, next) {
// here you can define period in second, this one is 1 day
const period = 1 * 24 * 60 * 60 * 1000
// you only want to cache for GET requests
if (req.method == 'GET') {
res.set('Cache-control', `public, max-age=${period}`)
} else {
// for the other requests set strict no caching parameters
res.set('Cache-control', `no-store`)
}
// res.set('Content-Encoding', 'gzip')
// remember to call next() to pass on the request
next()
}
app.use(setCache)
And when I use res.set('Content-Encoding', 'gzip') inside app.get("/", (req, res) => { then it shows in response header but website stop working (not showing any error other than blank screen).
Bellow images are of my rest code.
Gzip compression was showing only for resource files (css, js etc..). So I resolved it by adding res.contentType('text/html'); in setCache function.
const exceptions = ['.js', '.css', '.ico', '.jpg', '.jpeg', '.png', '.gif', '.tiff', '.tif', '.bmp', '.svg', '.ttf', '.eot', '.woff', '.php', '.xml', '.xsl'];
let setCache = function (req, res, next) {
// here you can define period in second, this one is 5 minutes
const period = 1 * 24 * 60 * 60 * 1000;
if(!exceptions.some(v => req.url.includes(v))){
res.contentType('text/html');
}
// you only want to cache for GET requests
if (req.method == 'GET') {
res.set('Cache-control', `public, max-age=${period}`)
} else {
// for the other requests set strict no caching parameters
res.set('Cache-control', `no-store`)
}
// res.set('Content-Encoding', 'gzip')
// remember to call next() to pass on the request
next()
}
app.use(setCache)
I wrote a Node/Typescript app that used data from Firebase Cloud Firestore. The app worked perfectly and I was able to test my endpoints fine using simple mocha commands of the generated .js file. Here is an example of one of the test scripts:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', () => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200)
)
it('does not allow put at top level', () =>
supertest(app)
.put('/risks')
.expect(403)
)
})
Here is the App.js that is referring to (imports and declarations excluded):
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
Here is the Risk router with only the GET endpoint:
const express = require('express');
const bodyParser = require('body-parser');
const riskRouter = express.Router();
import { firestore, firebasestore } from '../firebase/firebase';
riskRouter.use(bodyParser.json());
riskRouter.route('/')
.get((req,res,next) => {
return firestore.collection('risks').get()
.then(snapshot => {
let risks = [];
snapshot.forEach(doc => {
const data = doc.data()
const _id = doc.id
risks.push({_id, ...data });
});
res.send(risks)
})
.catch( err => res.json({error: err}))
})
// POST, PUT and DELETE are implemented here but not needed for this discussion
module.exports = riskRouter
When I tried to migrate this to Firebase, I basically copied the entire node application to the /functions directory and made the following change to the App.ts file
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
In both cases, the test command in package.json is
"test": "tsc && mocha lib/**/*.spec.js"
Also, the Risk router is identical in both cases.
In the case that works, all the test simply run cleanly. Also, they are making calls to the external Firebase backend
In the case that fails, I get the following output:
Risk API
1) works to get /
0 passing (2s)
1 failing
1) Risk API works to get /:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
at listOnTimeout (internal/timers.js:549:17)
at processTimers (internal/timers.js:492:7)
All of my endpoints use some form of res.send(), res.json(), etc which I read is sufficient and the explicit use of done() is not needed. If I'm wrong about that, I'd like to know as well as the proper syntax to fix it.
I also tried running mocha directly on the generated test script using the --timeout 15000 option, but got the same result.
Any suggestions would be greatly appreciated!
Can you try the following:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', (done) => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200, done)
)
it('does not allow put at top level', (done) =>
supertest(app)
.put('/risks')
.expect(403, done)
)
})
I'm using express 4.16.3 and trying to make sense of why one request to a controller works and a request doesn't.
in my server.js i've got the following. There's no semi-colons because used prettier beforehand.
import express from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
import PriceCheckerController from './controllers/PriceChecker'
import PersonalLibraryController from './controllers/PersonalLibrary'
const app = express()
app.set('port', process.env.PORT || 5000)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))
app.use(cors())
// routes definition
app.use('/api/books',PersonalLibraryController) // this does not
app.use('/api/stock-prices', PriceCheckerController) // this works
//
app.listen(app.get('port'), error => {
if (error) {
logger.error(`error fcc-isqa:${error}`)
} else {
logger.info(`fcc-isqa is running on port ${app.get('port')}`)
}
})
export default app
In PriceCheckerController i've implemented it like so.
import 'babel-polyfill' // mocha required
import express from 'express'
import logger from '../logger'
const PriceCheckerController = express.Router()
PriceCheckerController.use((req, res, next) => {
logger.info(
`date=>${new Date()}\n method=>${req.method}nsender:${req.ip}`
)
})
PriceCheckerController.get('/', async (req, res) => {
return res.status(200).json({message:'soon'})
})
export default PriceCheckerController
In PersonalLibraryController i've implemented it like so
import 'babel-polyfill'
import express from 'express'
import logger from '../logger'
const PersonalLibraryController = express.Router()
PersonalLibraryController.use((req,res,next)=>{
logger.info(
`library date=>${
new Date()}method=>${req.method}url=>${req.baseUrl}${req.path}`
)
})
PersonalLibraryController.route('/test')
.get(async (req, res) => {
return res.status(200).json({message: 'get soon'})
})
.post(async (req,res)=>{
return res.status(200).json({message: 'post soon'})
})
export default PersonalLibraryController
A request to /api/stock-prices returns ok with message soon.
A request to /api/books/test is logged by the middleware but a response is not sent back. It eventually gives a timeout
Can anyone give me any insights/ help in understanding what is the problem and how to fix it?
Thanks in advance.
Your middleware functions need to call next() to carry on the route execution.
e.g
PriceCheckerController.use((req, res, next) => {
logger.info(
`date=>${new Date()}\n method=>${req.method}nsender:${req.ip}`
)
next();
})
https://expressjs.com/en/guide/using-middleware.html
"If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging."
I am having trouble figuring out the best way for the serve to render my react components only after the http requests have been resolved.
For example:
component A <- component B <- component C (C triggers an action which makes a call to the API and returns data to render, in the mean time just renders 'loading').
When inspecting my source code, I only see 'loading' and would like, for SEO purposes, the server to wait till component C's call has resolved and rendered.
In theory, that sounds easy because the server should call that action itself and wait till the action is finished then call react.renderToString(), like this:
server.get('/', function (req, res, next) {
showMessages({}, function showMessagesCallback() { //add a callback
var html = React.renderToString(ChatComponent());
res.send(html);
});
});
But what if multiple components make action calls and I need to wait for multiple actions to solve and then call renderToString
Instead, the requests are picked up on the client side. My server file:
/**
* This leverages Express to create and run the http server.
* A Fluxible context is created and executes the navigateAction
* based on the URL. Once completed, the store state is dehydrated
* and the application is rendered via React.
*/
import express from 'express';
import path from 'path';
import serialize from 'serialize-javascript';
import {navigateAction} from 'fluxible-router';
import debugLib from 'debug';
import React from 'react';
import app from './app';
import HtmlComponent from 'components/Html';
const htmlComponent = React.createFactory(HtmlComponent);
const debug = debugLib('quran-com');
const server = express();
server.set('state namespace', 'App');
server.use('/public', express.static(path.join(__dirname, '/build')));
server.use('/images', express.static(path.join(__dirname, '/client/images')));
server.use('/fonts', express.static(path.join(__dirname, '/client/styles/fonts')));
server.use((req, res, next) => {
let context = app.createContext();
debug('Executing navigate action');
context.getActionContext().executeAction(navigateAction, {
url: req.url
}, (err) => {
if (err) {
if (err.status && err.status === 404) {
next();
} else {
next(err);
}
return;
}
debug('Exposing context state');
const exposed = 'window.App=' + serialize(app.dehydrate(context)) + ';';
debug('Rendering Application component into html');
const html = React.renderToStaticMarkup(htmlComponent({
context: context.getComponentContext(),
state: exposed,
markup: React.renderToString(context.createElement())
}));
debug('Sending markup');
res.type('html');
res.write('<!DOCTYPE html>' + html);
res.end();
});
});
const port = process.env.PORT || 8000;
server.listen(port);
console.log('Listening on port ' + port);
export default server;
what's the best way to do this?
You need to rethink architecture of your app. Components should not be getting data themselves, you are better of when something gives data to them.
My suggestion would be to do it in the navigate action, so that it becomes entry point into any view. Then here you can resolve all data needed and feed stores with that data, once resolved call the callback. E.g.:
module.exports = {
navigateAction: function (context, state, done) {
var completeNavigation = function () {
context.dispatch('NAVIGATE_DONE');
done()
}
var route = _.last(state.routes);
debug('navigate to: ' + route.name);
switch (route.name) {
case 'products':
context.executeAction(productActions.loadProducts, null, completeNavigation);
break;
default:
completeNavigation();
break;
}
}
};
In this sample I'm using react-router.