Ajax request from another domain using AngularJs - node.js

I'm using this book to learn AngularJS where I build this webapp with Angular, Node, Deployd. Now, my app stays at localhost:5000/app.html, 5000 is the port where node web server listen. I try to retrieve my data stored with deployd in this way:
$http.get("localhost:5500/products")
.success(function (data) {
$scope.data.products = data;
})
.error(function (error) {
$scope.data.error = error;
});
But this cause an error: no 'Access-Control-Allow-Origin' header is present on the requested resource. How can I solve this? Thanks :)

Kevin B is right. It's the Same Origin Policy blocking your request.
What you should do here is to direct your requests from the client to your node server ("/products"). Here, you can easily proxy them to localhost:5500, e.g. using node-http-proxy (https://github.com/nodejitsu/node-http-proxy).
From the node-http-proxy README.md (adapted the host/port to your use-case):
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer(options);
require('http').createServer(function(req, res) {
proxy.web(req, res, { target: 'http://localhost:5500' });
});
It might be, that this interferes with your current node server (serving you the client-side angular code in the first place). In case you're using Express for this, you can combine "http" and "http-proxy" like this: https://stackoverflow.com/a/22244101/3651406

Related

Best practice to call an API in next.js and avoid CORS errors

I am at early stages setting up a next.js application, I only had experience using react so far.
I setup docker with a frontend app (next.js) on localhost:3000 and a backend app (node.js/express) on localhost:5000. They both work.
Now I am trying to call an express endpoint from the frontend, what I am doing is:
const registerUser = async event => {
event.preventDefault()
const res = await fetch(
process.env.NEXT_PUBLIC_SERVER + '/user/signup',
{
body: JSON.stringify({
username: event.target.name.value,
email: event.target.email.value,
password: event.target.password.value
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
}
)
result = await res.json()
}
and I am getting an error saying
Access to fetch at 'http://localhost:5000/user/signup' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
just a note: the endpoint works as expected using Postman.
I made some research and I find a few resources saying I should call an internal next.js endpoint (pages/api), and from there call my api. Is this the best practice with next.js? In react I just use to call the api directly.
Other than just how to solve this, I would like to know what's the best practice in this case? Thanks.
If you have separate servers for frontend and backend (for example, next.js and express) that cannot listen on the same port, there are two broad alternatives:
Either the browser loads the frontend from one server and makes API requests to the other server
next.js <-- browser --> express
This requires the backend app to set CORS headers, for example, using cors and the statement
app.use(cors({origin: "host of next.js", ...}));
Or the browser makes all requests to the port of next.js, and this forwards all API requests to the other server
browser --> next.js --> express
No CORS is necessary in this case, but API requests take more hops than before. So it is simplicity vs performance (like so often).
First of all, are you sure you need an Express BE? The power of Next.js relies in its serverless approach, most of times, unless you have a very complex BE, you can do everything with serverless functions.
If you really need to have a separate express server for your Next application remember that you will lose some important Next features:
Before deciding to use a custom server, please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.
Usually to address the CORS issues in dev environment, since you need FE to run on a different PORT from BE to have Hot Reload, when you use React the best approach is the proxy approach, you can just add an entry to package.json on the React project,
"proxy": "http://localhost:5000" (if your server runs on PORT 5000)
Source: https://create-react-app.dev/docs/proxying-api-requests-in-development/
This way all the http traffic is going to be redirected on port 5000 and will reach your Express server, while keeping having hot reload features and your client files running on port 3000.
By the way, that's the case if you have a standard React FE and a custom Express BE, if you are using NextJS even with a custom Express Server, you need to create the server and to connect it using Next:
/ server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const hostname = 'localhost'
const port = 3000
// when using middleware `hostname` and `port` must be provided below
const app = next({ dev, hostname, port })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer(async (req, res) => {
try {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
await app.render(req, res, '/a', query)
} else if (pathname === '/b') {
await app.render(req, res, '/b', query)
} else {
await handle(req, res, parsedUrl)
}
} catch (err) {
console.error('Error occurred handling', req.url, err)
res.statusCode = 500
res.end('internal server error')
}
}).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://${hostname}:${port}`)
})
})
source: https://nextjs.org/docs/advanced-features/custom-server
Again, I suggest you to deeply evaluate if you really need a custom express server for your app, because most of times you don't, and development experience is much smoother in a serverless environment!

Heroku nodejs- redirect HTTP to HTTPS

I am working on Progressive Web App and when testing it with Lighthouse I get:
Does not redirect HTTP traffic to HTTPS
My server is in nodejs and it is on Heroku. I was trying to add this code:
app.get('*',function(req,res,next){
if(req.headers['x-forwarded-proto']!='https'){
res.redirect(`https://${req.header('host')}${req.url}`)
}else{
next()
}
})
But it didn't help. Any ideas how to solve that?
You want to enforce this on the server, and the easiest way to do that is to use the express-sslify module. From the documents:
var express = require('express');
var http = require('http');
var enforce = require('express-sslify');
var app = express();
// Use enforce.HTTPS({ trustProtoHeader: true }) since you're behind Heroku's reverse proxy
app.use(enforce.HTTPS({ trustProtoHeader: true }));
// The rest of your express config.
Note also that you want to employ app.use() as I did, rather than app.get() as you did in order to make sure you're covering all requests to your app, not just GETs.
listen on port 80 and redirect to https
var http = require('http');
var server = http.createServer((req, res) => {
res.writeHead(301,{Location: `https://${req.headers.host}${req.url}`});
res.end();
});
server.listen(80);
Why not check req.protocol?
app.get('*',function(req,res,next){
if(req.protocol !== 'https'){
res.redirect(`https://${req.header('host')}${req.url}`)
}else{
next()
}
})
Another solution I have been using with no apparent drawbacks is to use the node package ssl-express-www, and that is assuming you are using Express which it looks like you are.
The code is extremely simple:
First install the package npm i ssl-express-www
Then in your server file:
const secure = require("ssl-express-www")
const app = express();
app.use(secure)
That's literally it! Put that code at the top before your route handlers and it will do all the 'heavy-lifting' for you.
I found the solution. As I was using React, I just created file, on client side, called static.json in the main folder, and added that:
{
"root": "build/",
"https_only": true,
"headers": {
"/**": {
"Strict-Transport-Security": "max-age=7776000"
}
}
}

Nodejs - Proxy multiple applications to a single url locally

I'd like to have multiple nodejs applications all listening on different ports proxied to a URL on my local machine.
An example would be
localhost:3000 -> mysite.dev
localhost:3030 -> mysite.dev/api
It would make developing locally match my production setup and help immensely with my stateless authentication setep. I thought hotel was going to be the solution I wanted, but it doesn't do exactly what I'm looking for.
If possible I'd like to avoid using nginx locally.
You can try http-proxy module and you can specify proxy as
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create your proxy server and set the target in the options.
//
httpProxy.createProxyServer({target:'http://localhost:9000'}).listen(8000); // See (†)
//
// Create your target server
//
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(9000);
they have nice docs available here https://github.com/nodejitsu/node-http-proxy

Node Proxy - Proxy a SSL localhost target from a basic http server

What I am trying to do:
Proxy a java api that runs on https://127.0.0.1:443/api/ along side my UI that runs on non-SSL http://127.0.0.1:1337/ in order to circumnavigate some CORS issues.
My attempt:
Proxy the api at the SSL port 443 to my non-SSL development port of 1338.
proxy my UI to 1337
Proxy 1137 to :8080/index.html and proxy 1338 to :8080/api/
Access my app from localhost:8080
My problem:
The UI comes in just fine... but I can not hit the API at :8080/api/httpSession/init
Yes, I can still hit the API at https://localhost/api/httpSession/init
api.js - Renders index.html at :1337
var app = express();
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
var options = {
changeOrigin: true,
target: {
https: true
}
};
httpProxy.createServer(443, '127.0.0.1', options).listen(1338);
start.js - Proxies 1337 and 1338 into 8080
// First I start my two servers
uiServer.start(); // renders index.html at 1337
apiServer.start(); //
// I attempt to patch them back into one single non-SSL port.
app
.use('/', proxy({target: 'http://localhost:1337/'}))
.all('/api/*', proxy({target: 'http://localhost:1338/'}))
.listen(8080, function () {
console.log('PROXY SERVER listening at http://localhost:%s', 8080);
});
What you're looking for is request piping. Try this example:
// Make sure request is in your package.json
// if not, npm install --save request
var request = require('request');
// Intercept all routes to /api/...
app.all('/api/*', function (req, res) {
// Get the original url, it's a fully qualified path
var apiPath = req.originalUrl;
// Form the proxied URL to your java API
var url = 'https://127.0.0.1' + apiPath;
// Fire off the request, and pipe the response
// to the res handler
request.get(url).pipe(res);
});
Make sure to add some error handling if the api can't be reached, such as this SO solution.
For the proxy issue, my guess is that it is keeping the /api/* in the url and that's not present on the router in your API service. You could try adding /api to the router in the API service since it's going to keep the url string the same when it sends it. Otherwise, you likely need to proxy and rewrite the url so that the API will match the request to a route.
On another note, what about just installing the cors module and using in the app? I do something similar and it's working well without all the proxy items. https://www.npmjs.com/package/cors

Cross-domain POST request in Node.JS with preflight?

I have just started with Node.
I am trying to get cross-domain form data from an HTML form to parse in a Node.js server. I have been able to do this with simple POST data, not with POST requests that require preflight.
I am running the Node code on cloud9 app servers. I am also using the Cors module to handle the requests. This module works well with simple requests (test here to see a simple request work), however with requests that require preflight I get this result from the Chrome inspector console.
XMLHttpRequest cannot load https://nms-motaheri-1.c9.io:8080/mail.
The request was redirected to 'https://c9.io:8080/api/nc/auth?.....SHORTENED',
which is disallowed for cross-origin requests that require preflight.
Here is my server.js code:
// Define dependencies
var express = require('express')
, cors = require('cors')
, app = express()
, parse_post = require("parse-post");
// Core module config
var corsOptions = {
origin: '*',
preflightContinue: true // <- I am assuming this is correct
};
app.use(cors(corsOptions));
// Respond to option request with HTTP 200
// ?? Why is this not answering my OPTION requests sufficiently ??
app.options('*',function(req,res){
res.send(200);
});
// Give a hello world response to all GET requests
app.get('/',function(req,res){
res.sendFile(__dirname + '/index.html');
});
// Handle all POST requests to /mail
app.post('/mail', parse_post(function(req, res) {
console.log(req.body);
res.json({msg: 'This is CORS-enabled for all origins!'});
})
);
// Listen on default Cloud9 port which is 8080 in this case
app.listen(process.env.PORT, function(){
console.log('CORS-enabled web server listening on port ' + process.env.PORT);
});
Why is this happening and how can I satisfactorily answer the OPTION request for my POST with pre-flight?
Here is the post request and response in Chrome dev tools:
Turns out that part of the problem was that the cloud9 server was set to private making these requests all redirect.
After making the server public, the redirections stopped. However, I received an error that the Node.js server did not have any Access-Control-Allow-Origin headers to allow requests from my cross origin domain. I noticed that "simple" with-out preflight requests would go through. So instead of trying to understand why it was not accepting my allow-all-origin-configuration on the Node.js side I decided to serialized the POST data to get rid of the preflight requirement and changed the data type in my angular request to plain text.
To get rid of preflight, first get rid of any POST header configuration (cache, etc), make sure your request Content-Type is plain text and make sure your actual content is plain text too. So if it is in JSON serialize it in jQuery before sending it with POST.
This is what my new Angular Post request code looked like:
sendEmail: function(email) {
var config = {
headers: {
'Content-Type': 'text/plain'
}
};
var POSTDATA= JSON.stringify(POSTDATAJSON);
return $http.post(POSTURL, POSTDATA, config)
}
And in Node.js this, I am using the cors Node.js module:
app.post('/mail', parse_post(function(req, res) {
var postReq = JSON.parse(Object.keys(req.body));
}));

Resources