How to Proxy API Requests in Development Using Parcel Bundler

Posted: October 21st, 2019

Topics: Webpack, React, Parcel, DevTools

I'm a big fan of using Parcel to bundle my website/web app assets. It goes by the "it just works" mantra, far removed from it's alternative, Webpack. That said, the "it just works" system doesn't give you a whole lot of options when it comes to customizing your builds & dev environment.

So, lets take a React app, for example. Create-React-App is probably the most popular solution for bootstraping a React build. You don't even have to see the Webpack configuration if you don't want to - it'll just work. One of the little pieces of magic in CRA is a package.json value called proxy. You can pass a localhost value to which you'd like to route all of your clientside API requests - typically to wherever you have your backend server running. This is done so that you don't have to write any environment-dependant logic into your API handler -- you can just write API requests with relative paths.

Now, I recently began a side project for which I've opted to go serverless. My app will be hosted on Netlify & utilize it's native lambda functions service so I can go monorepo for frontend/backend. Netlify has a great package called netlify-lambda, which will build your functions locally and run them like you'd run a server normally. If I were using CRA/webpack, I'd just add my local lambda server address to the magic proxy value & be done with it. But we're using Parcel, which doesn't allow for much customization of it's dev server, so a but more configuration is necessary.

Github

Upon searching for answers, I found this Github issue where smarter people than me were asking this question. The creator of Parcel weighed in and posed the question: "why is this Parcel's problem?". I tend to agree, maybe CRA has spoiled us. In any case, he offered the solution of setting up your own mini Express server & using Parcel's middleware option along with http-proxy-middleware. Pretty genius, here's what it looks like:

/// run-devServer.js

const proxy = require('http-proxy-middleware');
const Bundler = require('parcel-bundler');
const express = require('express');

const bundler = new Bundler('src/index.html', {
  // Don't cache anything in development 
  cache: false,
});

const app = express();
const PORT = process.env.PORT || 3000;

// This route structure is specifc to Netlify functions, so 
// if you're setting this up for a non-Netlify project, just use
// whatever values make sense to you.  Probably something like /api/**

app.use(
  '/.netlify/functions/',
  proxy({
    // Your local server
    target: 'http://localhost:9000',
    // Your production routes
    pathRewrite: {
      '/.netlify/functions/': '',
    },
  })
);

// Pass the Parcel bundler into Express as middleware
app.use(bundler.middleware());

// Run your Express server
app.listen(PORT);

So now, your package.json may look something more like this:

  "scripts": {
    "rimraf": "rimraf dist/ && rimraf functions/ && rimraf .cache/ && rimraf node_modules/",
    "clean": "yarn rimraf && yarn cache clean && yarn",
    "dev": "concurrently \"yarn dev:client\" \"yarn dev:functions\"",
    "dev:client": "rimraf dist/ && node scripts/run-devServer.js",
    "dev:functions": "netlify-lambda serve api/lambda",
    "build": "yarn build:client && yarn build:functions",
    "build:client": "rimraf dist/ && parcel build src/index.html",
    "build:functions": "netlify-lambda build api/lambda",
    "serve:client": "parcel serve src/index.html",
    "serve:functions": "netlify-lambda serve api/lambda",
    "test": "jest",
    "test:watch": "jest --watch"
  }

Anyways, thought this was a great workaround for injecting some CRA magic into your own Parcel configuration. You can check this in action in my serverless React starter files.