Medieval Ordeal Image

I had a Go, CORS and Single Page App Ordeal So You Don’t Have To

Microservices are awesome, until you need to serve them up in a Single Page Application (SPA). That’s when you need to tame the CORS (cross-origin resource sharing) beast so your fancy new microservices can actually be used by front-end developers.

Don’t Have a CORS Ordeal

I just spent days on this one, so I thought I’d relay how my final solution works. In my case, I have a swagger-first approach to API development, where I feed a YAML file into go-swagger and then go-swagger generates a REST server framework. This works great with lots of microservices that don’t have any problem communicating using HTTP.

The problem we encountered was using Angular to make a single-page web application that would then use the APIs directly in the browser. That’s when CORS comes into play. I couldn’t get the headers to work right in Go using the go-swagger framework.

young troubled woman using laptop at home
Photo by Andrea Piacquadio on Pexels.com

Making CORS Work!

I have a swagger-first, also known as an OpenAPI standards, approach to API development. This is where I feed a YAML file into and then go-swagger generates a REST server framework. This works great with lots of microservices that don’t have any problem communicating using HTTP.

I could access my microservices externally on a server-to-server basis using api-umbrella as a API gateway. I even think I may have been able to solve my CORS problem with api-umbrella configuration, but I didn’t see it.

I had this problem with Angular, but anyone can have a CORS ordeal when you connect any new REST service to a JavaScript SPA.

My Toolchain

We all get attached to the chain of events that goes from a source code change to finished deployments. I needed my solution to work with a simple API gateway, like api-umbrella. Fancy solutions like AWS API Gateway don’t work for me due to lock-in and cost issues.

In a nutshell, here are the steps I use to build a microservice:

  1. Hand-write the swagger (OpenAPI v2) file in YAML
  2. Use go-swagger to generate the REST server
  3. Update the code to implement handler functions
  4. Test in VS Code
  5. Use Drone CI/CD to generate Docker images in a private registry
  6. Use docker-compose to orchestrate service startup on a private host, in a private VPC, and in a private datacenter
  7. Deploy api-umbrella as a the API gateway between public URI’s and the backend host

The Journey To Victory

After quite a journey, I finally came upon the solution at the swagger docs. The real trick in taming CORS, though, is to handle the “pre-flight” checks a web client makes before the actual REST method is invoked.

The solution for us is to create an OPTIONS method and write the response methods for them in Go using the go-swagger framework.

man in red crew neck sweatshirt photography
Photo by Andrea Piacquadio on Pexels.com

Make a swagger YAML file

This sample OpenAPI (swagger) YAML file is a very simple representation of a single endpoint REST server with two methods.

To get acquainted with this swagger definition, please note that I’ve used references rather heavily throughout the file. For example, if you look at the GET /coordinate path definition, you’ll see a reference to #/responses/CoordinateResponse.

Look in the responses section of the file, and you’ll see where CoordinateResponse includes both a JSON response body and header definitions.

Another handy definition in the responses section of the swagger file is CORSResponse, which is used to define the OPTIONS /coordinate response.

In my go-swagger environment, this YAML file generates the core HTTP services and frameworks for servicing inbound requests.

Notice how GET /coordinates has an authentication specification and OPTIONS /coordinates has no authentication. I need this because when Angular (or any SPA) causes the web browser to make an outbound call to a CORS-compliant REST server that is not in current origin, the browser expects to receive CORS OPTIONS response without any authentication.

By the way, you might be able to get around a CORS issue as an API consumer by including a local API proxy that doesn’t need to use CORS when make a server-to-server HTTP call. But, this technique requires that the API consumer include a proxy in their front-end code.

I also used a separate “cors” tag which helped organize and separate my “preflight” CORS options.

Next, I needed to write a preflight CORS handler function in the Go server. That is done by creating Go functions that conform to the go-swagger function conventions, draw from the OpenAPI (swagger) file.

Add CORS Header to Secure Response

Almost done. Next I need to modify my GetCoordinate handler to add the WithAccessControlAllowHeaders modifier.

Thank You!

cup of aromatic cappuccino with thank you words on foam
Photo by wewe yang on Pexels.com

I hope you liked my “little” story on how I solved my CORS problem.

I’m sure I didn’t get it right, so let me know how I could have done it better!

Leave a Reply