The Pulsar Cafe    ·

Get Rid of CORS Errors

CORS errors suck. I have spent many hours chasing down errors in my javascript code that turned out to be because running your frontend and backend servers on different local ports doesn’t fly with any modern web server. When I was first learning to write webapps, I would chase a CORS error for a couple hours, figure out finally what it was, and then hack together some solution found on Stack Overflow. Then, like a silly child, I would move onto the next intermediate problem.

Following the Same-Origin Policy isn’t something you do for a specific type of webapp. It’s something you need to do for every webapp. Sure, your frontend will probably be served as a static JS bundle in production, but I don’t know anyone who develops like that. You run a server to watch file changes and bundle your frontend on the fly. Something like npm or yarn. So there should be a once-and-forall fix.

As a result of graduating college and becoming someone who depends on their skill at programming to earn a living, about a year ago I started to care about how people actually do things in the wild. One tool I paid close attention to was Docker.1 And because I got familiar enough with this tool, I saw it as the obvious solution to fixing this CORS BS.

tl;dr: If you run a proxy server that routes all requests to / to your frontend, and all requests to /api to your backend, your backend server can’t tell the frontend is being served on a different port.

This solution doesn’t necessarily have anything to do with Docker. It’s just that by using Docker you don’t have to run the proxy server on your localhost. I consider that a big win. If you tend to frequently hack around with new, shiny things, keeping environments isolated saves a lot of trouble.

Here’s how to run an Nginx proxy inside a Docker container that solves the CORS problem for local development.

Constraints

Before we go too far, I will warn you that my configuration only works for Mac. It shouldn’t be too hard to make it work for some kind of Linux, however. The one place in my config that is Mac-specific is the DNS that Docker uses to identify my host machine. See this documentation for info on host.docker.internal.

Dockerfile

If you’d like, make a new folder to store the configuration for the container.

$ cd /where/i/keep/my/code && mkdir -p docker/nginx

Docker images are built from a definition specified in a file called Dockerfile. Here is the one I use for my Nginx proxy.

# docker/nginx/Dockerfile

FROM nginx:alpine

COPY nginx.conf /etc/nginx/nginx.conf


Nginx Config

The logic used by the proxy is specified in the nginx.conf file.

# docker/nginx/nginx.conf

worker_processes 1;

events { worker_connections 1024; }

http {

    sendfile on;

    server {
        listen 8080;

        location / {
            proxy_pass         http://docker.for.mac.localhost:3001;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }

        location /api/ {
            rewrite ^/api/(.*)$ /$1 break;
            proxy_pass http://docker.for.mac.localhost:3000;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }
}

For best results you will have a file structure like this:

docker/
|
|-- nginx/
|   | 
|   |-- Dockerfile
|   |-- nginx.conf

Running

First, figure out what ports your servers are running on. The above config works for a frontend running on localhost:3001, and a backend running on localhost:3000. Just change the port numbers in the nginx config if your servers are running on different ports.

To build the image:

$ cd /where/i/keep/my/code/docker/nginx
$ docker build -t reverseproxy .
$ docker run -p8080:8080 --detach --name=reverseproxy reverseproxy

Then run docker logs reverseproxy. If there’s no output, you’re good, and you can navigate a browser to localhost:8080 once your webapp servers are up. If you get some errors, do your best to diagnose with the info given. I swear this config works for me. Comment with questions if something’s not working for you.

Once everything is working, you can use the localhost:8080/ base url for all frontend routes. And you can use the localhost:8080/api/ base url for all your backend routes. CORS will not be an issue, since the backend will receive HTTP requests whose origin is localhost:8080, which as far as it can tell, is it’s own hostname.

Happy Hacking!



  1. Enough to know how to completely containerize a fullstack webapp.
comments powered by Disqus