Public Learning

project-centric learning for becoming a software engineer

Week 10: A Recap

Proving Murphy’s Law

Last week started like I had my own personal plague following me in addition to the rampant SARS-CoV-2. My big Windows PC spontaneously felt the urge to enter a bluescreen boot loop with no way to fix the obviously failing state of the machine. I would’ve probably left it at that for the time being since I’m developing solely on a Ubuntu laptop these days. But I also got called in for support in my former job and in order to provide it, I needed Windows software. So my Monday was spent reinstalling and reconfiguring Windows 10 (which is as boring as it is time-consuming) and then on Tuesday I helped fixing a failing backup system (whose setup I did, so I couldn’t even blame anyone but myself) and half of the week was seemingly over before I could fully focus on 200 OK again.

Rebel Without A CORS

CORS in Action cover Enough whining, though, because I also got stuff done. The first thing was implementing CORS (Cross-Origin Resource Sharing) support. A fascinatingly large amount of advice found on sites like Stack Overflow concerning Node.js and CORS is to simply drop in an additional Express middleware package: app.use(cors()).
That certainly solves the most glaring issue (that of a browser rejecting a request to the Node/Express server because it collides with the same-origin policy), but it neither explains what is necessary for a server to support CORS requests nor is it a best practice, because it ignores what that package does under the hood and how that might lead to undesired effects. So my goal was to implement CORS functionality for 200 OK myself, and in order to do so, I had a quick read through the book CORS In Action (Manning, 2014), a perfect primer on CORS handling both in the browser and on the server. And it turned out that CORS is actually a rather simple concept to implement with a few gotchas.

At its core, CORS is all about a single HTTP request and response header. The browser automatically adds a Origin header to each request whose target URL does not comply with its same-origin policy: meaning that the scheme, host and port of the requested URL (e.g. https://200ok.app/api) is not the same as the URL from which the request originates (e.g. an HTML page served by https://static.200ok.app/pages/).
Only if the response sent by the server includes that origin in its response header Access-Control-Allow-Origin (either explicitly or by using a wildcard * to allow all origins) will the browser allow any client-side code to process the response. So the simplest way to add CORS support to any server is to simply send that wildcard header back. That’s what the Express CORS middleware package does by default.

But there is more to it than just that (there always is). There are simple CORS requests and non-simple requests. For the latter, the browser sends a so-called preflight request to determine whether making that CORS request is actually supported by the server. There is a set of criteria to determine whether a request is not simple, the most common ones being that it is is not using a GET, POST or HEAD method (and instead something like PUT or DELETE), it has a Content-Type header that is set to something different than the content types typically used in HTML pages or forms or it has other headers that are not usually part of a request (including any custom request headers following the X-custom-header format).
A preflight request uses the OPTIONS method and will include either the Access-Control-Request-Method header (to ask if the intended method for the actual response will be allowed) or the Access-Control-Request-Headers header (to ask if the included headers in the actual request are fine) or both. The server responds with its respective set of headers (aptly named Access-Control-Allow-Methods and Access-Control-Allow-Headers), signaling the browser whether it is okay to send the non-simple CORS request. If that is the case, the request is made as described above.

Since 200 OK should not care about the request origin as well as the requested headers, the only meaningful check has to be with regards to the requested HTTP method: users are supposed to define custom behavior for certain endpoints, so the list of allowed methods should reflect that. As a result, my CORS middleware handles all OPTIONS requests and populates the response with the correct header values. This is simple enough, so the actual code clocks in at less than 50 lines total, making the result of my deep dive into CORS look pretty unimpressive.

Frontend Matters

I have also decided on a radical course change for my frontend portion. My intention was to create a React SPA, therefore using a full modern MERN stack for the project. I had even bought the Fullstack React book used to teach React in Launch School’s Capstone program. But I had a few nagging doubts about that choice:

  1. Learning more complex React than I used in my prep phase, plus learning Redux for state management would be a considerable effort. That goes double for figuring out the whole tooling part that is so prevalent when using React.
  2. React is not known for a tiny footprint and it felt wrong to use it for what is supposed to be a very slick UX: there is one button, labelled Create your API, that will provide the means to use the semi-intelligent API mode without any further configuration. Rendering one button with one MB of JavaScript is everything that is wrong about the modern web.
  3. There is also something to be said about separating concerns. I already made some efforts to be able to create endpoints and route behavior for API calls made by a React frontend, but handling both frontend requests and actual user-created API requests in the same backend introduced some slightly ugly functionality. Keeping those calls made by the frontend in its own place seems to be the better practice.

The better alternative that solves all the aforementioned issues is to make a separate server application that serves dynamically created (and template-powered) HTML pages and a few slim JS files via Express. It alleviates the pressure of having to learn advanced React. It also allows for a one-button page that will be at maximum a few dozen KB of HTML, CSS, images and vanilla JS - as it should be. I am not convinced that React is the correct answer to the problems my frontend part is supposed to solve, even as ubiquitous as it seems to be in today’s web world.
Finally, I will create a completely separate Node.js/Express application whose purpose is to create and serve the static assets as well as provide an API for the frontend to allow API creation and configuration. This is exciting because it means writing an Express app that is in most ways different from the main backend application. Deepening my knowledge of Node and Express means working on things that I really want to deal with in my future job: I enjoy backend work way more than anything frontend-related and starting with the new application was a true joy.

Putting It Together

My Sunday was subsequently spent with beelining towards that almost mystical one button saying Create your API. And it is with great pleasure that I can report a big success: I am finally able to see the whole system at work. The frontend application serves a single page with the button. Upon clicking it, a new API database entry is created, together with a name (following Docker’s funny approach of combining an adjective with a notable historic scientist/computer person) and an authentication key (which will be used to register the API toa user account). Then, any REST operation that follows the happy path approach of having resources and sub-resources works under the provided subdomain.
There is still lots of stuff missing (debugging requests, defining custom behavior, sorting and pagination, among many others), but I am now at a point where I feel like I am just plugging holes instead of building the boat, so to speak. I am also very close to be able to provide a working version to a small circle of testers, which will provide some much needed external feedback. It goes without saying that I am deeply excited for the weeks to come.

Summary

😄

Time spent this week: 35 hours