I have recently Implemented Proxy (in Express.js) for my React App to hide API URL's when making a request. It has been working perfectly fine when I run it the proxy and app on localhost. Now that I'm ready to deploy My application to AWS Amplify, I am a little confused as to how I get my proxy to run there since I'm not manually starting the app and proxy from the CLI. Do I need to use an EC2 instance instead or can I achieve this using Amplify?
Any Help would be greatly appreciated!
This is what my Project Directory Looks like :
This is what my Server.js looks like :
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const axios = require('axios');
var cors = require('cors')
app.use(cors())
app.use(bodyParser.urlencoded({
extended: false
}));
const BASE_URL = 'https://my-aws-lambda-base-url/dev'
const port = process.env.PORT || 5000;
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();
});
app.listen(port, () => console.log(`Listening on port ${port}`));
app.use('/contact', require('body-parser').json(), async (req, res) => {
try {
await axios.post(`${BASE_URL}/contact`, {
Email : req.body.Email,
type : req.body.type,
Message : req.body.Message,
Phone : req.body.Phone
},
{
headers: {
'Content-Type': 'application/json',
}
},
).then(function(response) {
const result = response.data
console.log(result)
if(result && result.Message) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(result))
}
}).catch((e)=> {
console.log(e)
res.send(false)
})
} catch (error) {
console.log(error)
res.send(false)
}
});
And this is how I make the request from In my React App
export async function sendContact(request){
try {
if(!request.Phone)
request.Phone = false
if(request.textMe){
request.type = "BOTH"
}
else{
request.type = "EMAIL"
}
let result ;
await fetch('/contact', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
Email : request.Email,
type : request.type,
Message : request.Message,
Phone : request.Phone
})
}
).then(async response =>
await response.json()
).then(data =>
result = data
).catch((e)=> {
notifyError(e.response.data.Message)
return false
})
console.log(result)
return result
} catch (error) {
console.log(error)
}
}
And Finally, Here's My Build Script from Amplify for my application
version: 1
frontend:
phases:
preBuild:
commands:
- npm i
build:
commands:
- npm run build
artifacts:
baseDirectory: build
files:
- '**/*'
cache:
paths:
- node_modules/**/*
P.S : I do also have "proxy": "http://localhost:5000" added to my Package.json
EDIT :
I tried Using a Background task manager like PM2 to run post build in the build script but that still did not work (although it did locally)
I ended up spinning up a proxy lambda as my API gateway (middle man) between my client and server. I also have the proxy denying any requests not coming from my website.
Related
This is my server.js file
import { json } from 'body-parser';
import sirv from 'sirv';
// import polka from 'polka';
import compression from 'compression';
import * as sapper from '#sapper/server';
import express from 'express';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
express() // You can also use Express
.use(
json(),
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});
This is the endpoint I'm trying to reach
located in src/routes/contact.js
export async function post(req, res, next) {
res.setHeader('Content-Type', 'application/json')
const data = req.body
console.log(data);
return res.end(JSON.stringify({ success: true }))
}
And this is the fetch
fetch('/contact', {
method: 'POST',
body: 'Hola mundo',
headers: {
'Content-Type': 'application/json'
}
}).then(() => {
console.log('Form submitted');
})
But network reports NOT FOUND, What I'm missing here? I thought it was a matter of polka then I switched to Express but the issue remains
PD: This is my reference Post for the structure
fetch post is giving me undefined for the posted data?
I'm trying to setup CSRF tokens so that I can do a number of checks before issueing a token to the client to use in future requests.
Taking the guidance from the csurf documentation, I've setup my express route with the following:
const express = require('express');
const router = express.Router({mergeParams: true});
const csurf = require('csurf');
const bodyParser = require('body-parser');
const parseForm = bodyParser.urlencoded({ extended: false });
const ErrorClass = require('../classes/ErrorClass');
const csrfMiddleware = csurf({
cookie: true
});
router.get('/getCsrfToken', csrfMiddleware, async (req, res) => {
try {
// code for origin checks removed for example
return res.json({'csrfToken': req.csrfToken()});
} catch (error) {
console.log(error);
return await ErrorClass.handleAsyncError(req, res, error);
}
});
router.post('/', [csrfMiddleware, parseForm], async (req, res) => {
try {
// this returns err.code === 'EBADCSRFTOKEN' when sending in React.js but not Postman
} catch (error) {
console.log(error);
return await ErrorClass.handleAsyncError(req, res, error);
}
});
For context, the React.js code is as follows, makePostRequest 100% sends the _csrf token back to express in req.body._csrf
try {
const { data } = await makePostRequest(
CONTACT,
{
email: values.email_address,
name: values.full_name,
message: values.message,
_csrf: csrfToken,
},
{ websiteId }
);
} catch (error) {
handleError(error);
actions.setSubmitting(false);
}
Postman endpoint seems to be sending the same data, after loading the /getCsrfToken endpoint and I manually update the _csrf token.
Is there something I'm not doing correctly? I think it may be to do with Node.js's cookie system.
I think your problem is likely to be related to CORS (your dev tools will probably have sent a warning?).
Here's the simplest working back-end and front-end I could make, based on the documentation:
In Back-End (NodeJS with Express) Server:
In app.js:
var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
const cors = require('cors');
var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })
var app = express()
const corsOptions = {
origin: "http://localhost:3000",
credentials: true,
}
app.use(cors(corsOptions));
app.use(cookieParser())
app.get('/form', csrfProtection, function (req, res) {
res.json({ csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function (req, res) {
res.send('data is being processed')
})
module.exports = app;
(make sure you update the corsOptions origin property to whatever your localhost is in React.
In Index.js:
const app = require('./app')
app.set('port', 5000);
app.listen(app.get('port'), () => {
console.log('App running on port', app.get('port'));
});
In React:
Create file "TestCsurf.js" and populate with this code:
import React from 'react'
export default function TestCsurf() {
let domainUrl = `http://localhost:5000`
const [csrfTokenState, setCsrfTokenState] = React.useState('')
const [haveWeReceivedPostResponseState, setHaveWeReceivedPostResponseState] = React.useState("Not yet. No data has been processed.")
async function getCallToForm() {
const url = `/form`;
let fetchGetResponse = await fetch(`${domainUrl}${url}`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"xsrf-token": localStorage.getItem('xsrf-token'),
},
credentials: "include",
mode: 'cors'
})
let parsedResponse = await fetchGetResponse.json();
setCsrfTokenState(parsedResponse.csrfToken)
}
React.useEffect(() => {
getCallToForm()
}, [])
async function testCsurfClicked() {
const url = `/process`
let fetchPostResponse = await fetch(`${domainUrl}${url}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"xsrf-token": csrfTokenState,
},
credentials: "include",
mode: 'cors',
})
let parsedResponse = await fetchPostResponse.text()
setHaveWeReceivedPostResponseState(parsedResponse)
}
return (
<div>
<button onClick={testCsurfClicked}>Test Csurf Post Call</button>
<p>csrfTokenState is: {csrfTokenState}</p>
<p>Have we succesfully navigates csurf with token?: {JSON.stringify(haveWeReceivedPostResponseState)}</p>
</div>
)
}
Import this into your app.js
import CsurfTutorial from './CsurfTutorial';
function App() {
return (
<CsurfTutorial></CsurfTutorial>
);
}
export default App;
That's the simplest solution I can make based on the CSURF documentations example. It's taken me several days to figure this out. I wish they'd give us a bit more direction!
I made a tutorial video in case it's of any help to anyone: https://youtu.be/N5U7KtxvVto
A little background -
I have my NodeJS server running on port 3001 and my React application on port 3000. I've set up a proxy in my React application package.json to proxy all requests to port 3001 -
"proxy": "http://localhost:3001"
Now when I'm using Axios in my React application to make POST request on 'users/authentication' route on my NodeJS server, the request body is being parsed as blank on the Node server
const request = {
method: 'POST',
url: `/users/authenticate`,
headers: {
'Content-Type': 'application/json'
},
body: {
email: email,
password: password
}
};
console.log(request);
axios(request).then((res) => {
//handle success
});
}).catch((err) => //handleError ))
But unfortunately, the NodeJS application crashes because it's parsing req.body as blank. The relevant code on Server side -
//in index.js file
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json())
app.use('/users', users);
//in users.js file
const express = require('express');
const router = express.Router();
const userController = require('../controllers/users');
router.post('/authenticate', userController.authenticate);
module.exports = router;
//in UserController
const userModel = require('../models/user');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
module.exports = {
authenticate: function (req, res, next) {
console.log(req);
userModel.findOne({ email: req.body.email }, function (err, userInfo) {
if (err) {
next(err);
} else {
if (bcrypt.compareSync(req.body.password, userInfo.password)) {
const token = jwt.sign({ id: userInfo._id }, req.app.get('secretKey'), { expiresIn: '1h' });
res.json({ status: "success", message: "user found!!!", data: { user: userInfo, token: token } });
} else {
res.json({ status: "error", message: "Invalid email/password!!!", data: null });
}
}
});
},
}
But when I'm logging the request in the 'authenticate' function, the req.body is being parsed as blank which is making the application crash.
Now when I do this exact thing using 'Request' package on React, it's working completely fine. Below code using 'Request' library -
var options = {
method: 'POST',
url: 'http://localhost:3000/users/authenticate',
headers:
{
'Content-Type': 'application/json'
},
form:
{
email: email,
password: password
}
};
console.log(options);
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Any idea why it's working fine using Request and not on Axios?
The only problem with 'Request' is that I have to mention the complete URL - 'localhost:3000...' instead of just the route like in Axios.
Any other suggestions on how I could implement this better would be great also.
The property for adding data to the request body in Axios is called data not body.
Following this quick guide (React and PostgreSQL), the following app should print the JSON fetch to the bash terminal (at ~37min of video).
However this does not happen. There is no feedback on the npm or nodemon servers.
When adding a value via the front-end, firefox instantly sends back a 404 status (observed in console:network). In chrome, the thread hangs as pending until the nodemon server is shut down (and then fails with a connection reset error)(again in console:network).
npm is running app and nodemon is running the server.
app.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super();
this.state = {
title: 'Simple postgres app',
treatments: []
}
}
componentDidMount(){
console.log('COMPONENT HAS MOUNTED')
}
addStuff(event){
event.preventDefault()
// console.log('in method');
let data = {
test_field: this.refs.test_field.value,
};
var request = new Request('http://localhost:3000/api/new-thing', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(data),
message: console.log('JSON output: ', JSON.stringify(data))
});
fetch(request)
.then((response) => {
response.json()
.then((data) => {
console.log(data)
})
})
}
render() {
let title = this.state.title;
return (
<div className="App">
<h1> { title } </h1>
<form ref = "testForm">
<input type="text" ref="test_field" placeholder="test_field"/>
<button onClick={this.addStuff.bind(this)}>Add This</button>
</form>
</div>
);
}
}
export default App;
server.js
let express = require('express');
let bodyParser = require('body-parser');
let morgan = require('morgan');
let pg = require('pg');
const PORT = 3000;
// let pool = new pg.Pool({
// port: 5432,
// user: 'postgres',
// password: 'postgres',
// database: 'po1dev_v0.0.1',
// max: 10, //max connections
// host: 'localhost'
// })
let app = express();
app.use(bodyParser.json);
app.use(bodyParser.urlencoded({ extended:true }));
app.use(morgan('dev'));
app.use((request, response, next) => {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// app.post('/api/new-thing', (request,response) => {
// console.log(request.body)
// })
app.post('/api/new-thing', function(request,response){
console.log(request.body);
})
app.listen(PORT, () => console.log('Listening on port ' + PORT));
Any ideas on what may be causing the 404/hang problems in firefox/chrome and how to go about fixing it?
Cheers
That's because the route you're creating doen't return any response so it waits indefinitely for a response then gets timed out.
The route should return some response,
app.post('/api/new-thing', function(request,response){
console.log(request.body);
return response.json({"data": "Hello World"})
})
Which will return the {"data": "Hello World"} from the /api/new-thing route.
Also, bodyParser.json is a function not property. Change it to
app.use(bodyParser.json())
If you are using create-react-app try another port for the backend server. Because by default it uses 3000 for the react app.
I have this code in Ionic app but I don't know how to make an API with Node.js to send this values to sever only by using Node.js.
submitForm() {
let headers = new Headers(
{
'Content-Type': 'application/json',
'Accept': 'application/json'
});
let options = new RequestOptions({ headers: headers });
let data = JSON.stringify({
Value1: this.form.value1,
Value2: this.form.value2,
Value3: this.form.value3
});
console.log(data);
let url = 'http://localhost:3000/calculate';
console.log(url);
return new Promise((resolve, reject) => {
this.http.post(url, data, options)
.toPromise()
.then((response) => {
console.log('API Response : ', response.status);
resolve(response.json());
})
.catch((error) => {
console.error('API Error : ', error.status);
console.error('API Error : ', JSON.stringify(error));
reject(error.json());
});
});
}
You may like to use ExpressJS. Following example may help you
Create a directory lets called api with following 2 files
create app.js in api directory
var http = require('http');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.post('/calculate', function(req, res) {
var data = req.body;
console.log('Here are your data: ', data);
res.json({message: 'you posted successfully'});
});
var port = process.env.PORT || 3000;
var server = http.createServer(app);
server.listen(port);
server.on('error', function(){
console.error('Error')
});
server.on('listening', function(){
console.log('server started on port ' + port)
});
create package.json file in api directory
{
"name": "api",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"body-parser": "~1.17.1",
"express": "~4.15.2"
}
}
now open command line/terminal and install dependencies by running following command(you must go to inside api directory)
npm install
now you can run by just running either npm start or node app.js
You should google for learning and studying and post questions for bug/issue
Update: without any dependencies or library but not recommended
It will be better to use http framework like express, sailsjs, etc. but if you like to play with nodejs then following example may help you
var http = require('http');
var port = process.env.PORT || 3000;
var server = http.createServer(function(req, res) {
var contentType = req.headers['content-type'];
var rawData = '';
req.on('data', function (chunk) {
rawData += chunk;
});
req.on('end', function () {
if(req.method === 'POST' && req.url === '/calculate' && contentType.indexOf('application/json')>-1){
try {
const data = JSON.parse(rawData);
console.log('Your data is here: ', data);
res.writeHead(200, { 'Content-Type': 'application/json'});
var result = {message: 'you have posted successfully'}
res.end(JSON.stringify(result));
} catch (e) {
console.error(e.message);
res.writeHead(400, { 'Content-Type': 'application/json'});
var result = {message: e.message}
res.end(JSON.stringify(result));
}
} else {
res.writeHead(404, { 'Content-Type': 'application/json'});
var result = {message: 'Url not found'}
res.end(JSON.stringify(result));
}
});
});
server.listen(port);
server.on('error', function(){
console.error('Error')
});
server.on('listening', function(){
console.log('server started on port ' + port)
});
I've created an example Node.js project that illustrates client/server request/response using AJAX and JSON. It "requires" only 'http', 'path', and 'fs'.
It implements a 'calculate' function server-side, and has a web page that presents 3 input boxes and a 'Calculate' button.
It's on Github: "https://github.com/bobwirka/NodeClientServer.git"
Hope this will be of help.