Proxy with express.js

210,771

Solution 1

You want to use http.request to create a similar request to the remote API and return its response.

Something like this:

const http = require('http');
// or use import http from 'http';


/* your app config here */

app.post('/api/BLABLA', (oreq, ores) => {
  const options = {
    // host to forward to
    host: 'www.google.com',
    // port to forward to
    port: 80,
    // path to forward to
    path: '/api/BLABLA',
    // request method
    method: 'POST',
    // headers to send
    headers: oreq.headers,
  };

  const creq = http
    .request(options, pres => {
      // set encoding
      pres.setEncoding('utf8');

      // set http status code based on proxied response
      ores.writeHead(pres.statusCode);

      // wait for data
      pres.on('data', chunk => {
        ores.write(chunk);
      });

      pres.on('close', () => {
        // closed, let's end client request as well
        ores.end();
      });

      pres.on('end', () => {
        // finished, let's finish client request as well
        ores.end();
      });
    })
    .on('error', e => {
      // we got an error
      console.log(e.message);
      try {
        // attempt to set error message and http status
        ores.writeHead(500);
        ores.write(e.message);
      } catch (e) {
        // ignore
      }
      ores.end();
    });

  creq.end();
});

Notice: I haven't really tried the above, so it might contain parse errors hopefully this will give you a hint as to how to get it to work.

Solution 2

request has been deprecated as of February 2020, I'll leave the answer below for historical reasons, but please consider moving to an alternative listed in this issue.

Archive

I did something similar but I used request instead:

var request = require('request');
app.get('/', function(req,res) {
  //modify the url in any way you want
  var newurl = 'http://google.com/';
  request(newurl).pipe(res);
});

I hope this helps, took me a while to realize that I could do this :)

Solution 3

I found a shorter and very straightforward solution which works seamlessly, and with authentication as well, using express-http-proxy:

const url = require('url');
const proxy = require('express-http-proxy');

// New hostname+path as specified by question:
const apiProxy = proxy('other_domain.com:3000/BLABLA', {
    proxyReqPathResolver: req => url.parse(req.baseUrl).path
});

And then simply:

app.use('/api/*', apiProxy);

Note: as mentioned by @MaxPRafferty, use req.originalUrl in place of baseUrl to preserve the querystring:

    forwardPath: req => url.parse(req.baseUrl).path

Update: As mentioned by Andrew (thank you!), there's a ready-made solution using the same principle:

npm i --save http-proxy-middleware

And then:

const proxy = require('http-proxy-middleware')
var apiProxy = proxy('/api', {target: 'http://www.example.org/api'});
app.use(apiProxy)

Documentation: http-proxy-middleware on Github

I know I'm late to join this party, but I hope this helps someone.

Solution 4

To extend trigoman's answer (full credits to him) to work with POST (could also make work with PUT etc):

app.use('/api', function(req, res) {
  var url = 'YOUR_API_BASE_URL'+ req.url;
  var r = null;
  if(req.method === 'POST') {
     r = request.post({uri: url, json: req.body});
  } else {
     r = request(url);
  }

  req.pipe(r).pipe(res);
});

Solution 5

I used the following setup to direct everything on /rest to my backend server (on port 8080), and all other requests to the frontend server (a webpack server on port 3001). It supports all HTTP-methods, doesn't lose any request meta-info and supports websockets (which I need for hot reloading)

var express  = require('express');
var app      = express();
var httpProxy = require('http-proxy');
var apiProxy = httpProxy.createProxyServer();
var backend = 'http://localhost:8080',
    frontend = 'http://localhost:3001';

app.all("/rest/*", function(req, res) {
  apiProxy.web(req, res, {target: backend});
});

app.all("/*", function(req, res) {
    apiProxy.web(req, res, {target: frontend});
});

var server = require('http').createServer(app);
server.on('upgrade', function (req, socket, head) {
  apiProxy.ws(req, socket, head, {target: frontend});
});
server.listen(3000);
Share:
210,771
user124114
Author by

user124114

Updated on April 06, 2021

Comments

  • user124114
    user124114 about 3 years

    To avoid same-domain AJAX issues, I want my node.js web server to forward all requests from URL /api/BLABLA to another server, for example other_domain.com:3000/BLABLA, and return to user the same thing that this remote server returned, transparently.

    All other URLs (beside /api/*) are to be served directly, no proxying.

    How do I achieve this with node.js + express.js? Can you give a simple code example?

    (both the web server and the remote 3000 server are under my control, both running node.js with express.js)


    So far I found this https://github.com/http-party/node-http-proxy , but reading the documentation there didn't make me any wiser. I ended up with

    var proxy = new httpProxy.RoutingProxy();
    app.all("/api/*", function(req, res) {
        console.log("old request url " + req.url)
        req.url = '/' + req.url.split('/').slice(2).join('/'); // remove the '/api' part
        console.log("new request url " + req.url)
        proxy.proxyRequest(req, res, {
            host: "other_domain.com",
            port: 3000
        });
    });
    

    but nothing is returned to the original web server (or to the end user), so no luck.

    • Saule
      Saule about 8 years
      the way you do it is working for me, without any modifications
    • VyvIT
      VyvIT about 7 years
      Although a bit too late to answer, but was facing similar issue and resolved it by removing body parser so that request body is not being parsed before proxying it further.
  • user124114
    user124114 about 12 years
    Yeah, some modifications were necessary, but I like this better than introducing an extra new "Proxy" module dependency. A bit verbose, but at least I know exactly what's going on. Cheers.
  • Alex Turpin
    Alex Turpin over 10 years
    Thanks, much simpler than using Node.js' HTTP request
  • Stephan Hoyer
    Stephan Hoyer about 10 years
    Even simpler, if you also pipe the request: stackoverflow.com/questions/7559862/…
  • Henrik Peinar
    Henrik Peinar about 10 years
    Nice and clean solution. I posted an answer to make it work with POST request also (otherwise it doesn't forward your post body to the API). If you edit your answer I'd be happy to remove mine.
  • Mariano Desanze
    Mariano Desanze almost 10 years
    Couldn't make it work with PUT. But works great for GET and POST. Thank you!!
  • davnicwil
    davnicwil over 9 years
    @Protron for PUT requests just use something like if(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
  • Tamlyn
    Tamlyn over 9 years
    Also see this answer for improved error handling.
  • keinabel
    keinabel almost 9 years
    whenever I try to do similar routing (or the exact same) I end up with the following: stream.js:94 throw er; // Unhandled stream error in pipe. ^ Error: getaddrinfo ENOTFOUND google.com at errnoException (dns.js:44:10) at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:94:26) any ideas?
  • setec
    setec almost 9 years
    It seems like you need to do res.writeHead before data chink is written, otherwise you'll get error (headers cant be written after body).
  • Paul
    Paul over 8 years
    Rather than a case statement all of which do the same thing except use a different request function) you could sanitize first (e.g. an if statement that calls your default if the method isn't in the list of approved methods), and then just do r = request[method](/* the rest */);
  • Carlos Rymer
    Carlos Rymer over 8 years
    If you need to pass through headers as part of a PUT or POST request, make sure to delete the content-length header so request can calculate it. Otherwise, the receiving server could truncate the data, which will lead to an error.
  • Vinoth Kumar
    Vinoth Kumar over 8 years
    The req.url doesn't have the full url, so updated the answer to use req.baseUrl instead of req.url
  • MaxPRafferty
    MaxPRafferty over 8 years
    I also like to use req.originalUrl in place of baseUrl to preserve querystrings, but this may not always be the desired behavior.
  • Selfish
    Selfish over 8 years
    @MaxPRafferty - vaid comment. Worth noting. Thanks.
  • Michal Tsadok
    Michal Tsadok over 7 years
    @user124114 - please put the full solution that you have used
  • User
    User about 7 years
    Can you pass the IP address through this somehow?
  • Andrew
    Andrew over 6 years
    This is the best solution. I'm using http-proxy-middleware, but it's the same concept. Don't spin your own proxy solution when there are great ones out there already.
  • Juicy
    Juicy almost 6 years
    Doesn't seem to work anymore with request. Throws a write after end error.
  • nish1013
    nish1013 almost 6 years
    What does pipe() do here? does it return the complete response get from the remote service?
  • sec0ndHand
    sec0ndHand over 5 years
    This is the only one that also deals with web-sockets.
  • Shnd
    Shnd almost 5 years
    seems that you'll have problem setting headers this way. Cannot render headers after they are sent to the client
  • Christopher Krah
    Christopher Krah over 4 years
    replace it like this: var preparedWrite = ''; // wait for data cres.on('data', function(chunk){ //res.write(chunk); preparedWrite = preparedWrite+chunk; }); cres.on('close', function(){ // closed, let's end client request as well res.writeHead(cres.statusCode); res.end(); }); cres.on('end', function(){ // finished, let's finish client request as well res.writeHead(cres.statusCode); res.write(preparedWrite); res.end(); });
  • mekwall
    mekwall over 4 years
    I've updated the answer to es6 syntax and fixed the writeHead issue
  • Mustafa Hussain
    Mustafa Hussain over 4 years
    there's another package that is more simple to use npm install express-proxy-server --save var proxy = require('express-proxy-server'); app.use('/proxy', proxy('example.org/api'));
  • drmrbrewer
    drmrbrewer about 4 years
    @Jonathan @trigoman now that request has been deprecated (see notice at github.com/request/request), what is the alternative?
  • Dimitri Kopriwa
    Dimitri Kopriwa about 4 years
    http-proxy-middleware/dist/index.js' does not provide an export named 'default
  • valik
    valik about 4 years
    @Henrik Peinar, will this help when i do a login post request and expect to redirect from web.com/api/login to web.com/
  • Wildhammer
    Wildhammer almost 4 years
    This resolved my issue. The only difference was the version of http-proxy-middleware that I installed did things slightly different which was easy to figure out following documentations.
  • tanner burton
    tanner burton almost 4 years
    This helped me but I had to change forwardPath to proxyReqPathResolver. It looks like they deprecated forwardPath.
  • FiNeX
    FiNeX almost 3 years
    Hi, what if I need to alter a string on the HTML body before piping out to the client?
  • ArpitM
    ArpitM over 2 years
    This answer helped me with a non-deprecated solution using node-fetch.