• SPAs, CORS, and GraphQL

    The popularity of single page applications (SPAs) has grown significantly in the world of web development over the last few years. As with any architectural choice, benefits and tradeoffs exist.

    This article from Free Code Camp recently highlighted a potential performance cost associated with single page applications. I’ll do my best to accurately and succinctly restate the problem presented in the article:

    Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. A web application makes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, and port) than its own origin.

    • CORS is not an issue when it’s a “simple request”, but does introduce problems when it’s a preflighted request.

    • Preflighted requests first send a request with the OPTIONS method prior to making the actual request for data. Because the value of the Content-Type header sent by an SPA is frequently application/json, requests from the SPA will be preflighted – mean that we make two requests each time we fetch data from the API.

    According to the article,

    We can use the Access-Control-Max-Age header to cache the results of a preflight request. The next time we access the resource api.example.com/users/report/12345 from spa.example.com there is no preflight request.

    Yes, that’s true, but then remember the title — The terrible performance cost of CORS requests on the single-page application (SPA). This comes from the API that we’re are consuming and the way it’s been designed. In our example, we designed our API /users/report/:id, where :id means its a value that can change.

    The way preflight cache works is per URL, not just the origin. This means that any change in the path (which includes query parameters) warrants another preflight request.

    So in our case, to access resource api.example.com/users/report/12345 and api.example.com/users/report/123987, it will trigger four requests from our SPA in total.

    The key sentence in the foregoing quote is “This comes from the API … and the way it’s been designed.” Enter GraphQL.

    Unlike a REST API, the URL for each request to a GraphQL API remains the same regardless of the resource. Instead, a query body is POSTed to the URL as JSON, and the resolution of the query is handled by the server.

    To work with the foregoing example, let’s say we have a GraphQL API residing at api.example.com/graphql. In order to get the report with an id of 12345, our SPA would make a preflighted request with the OPTIONS method to get the approved list of actions for api.example.com/graphql. It would then make a POST to the same URL with with following query body:

    query {
      users {
        report(id: "12345") {
          id
          content
        }
      }
    }
    

    Our API would then handle the query and return the id and content for the report with an ID of 12345. While there are certainly some other improvements that can be made to the design of the API (for example, I would scope the query for a report to a given user, so our query body would say something like user(id: 1) instead of just users), the key strength of GraphQL in this example is that we only need to make one preflighted request if cache the results of the preflighted request with the Access-Control-Max-Age header, since all requests from our SPA can now be made to the same URL.

  • Vulnerability

    Seth Godin recently wrote,

    Role models are fine. But not when they get in the way of embracing our reality. The reality of not enough time, not enough information, not enough resources. The reality of imperfection and vulnerability.

    There are no movie stars. Merely people who portray them now and then.

    I always appreciate Seth Godin’s candor. Someone as successful and respected as he is could pitch their advice as a fool-proof framework for excellence and success in one’s work—indeed, many do.

    “Imposter syndrome” is a term that gets thrown around a lot amongst software developers. It’s the mindset that we are not good enough, not a “real” programmer. We could think this way for any number of reasons—lack of formal CS education, lack of experience, lack of knowledge around the cool new framework that everyone is talking about on Hacker News, lack of concern about what anyone even says on Hacker News, etc.

    It’s easy to look at other, more experienced programmers and think that they have it all figured out. Yet some of the most profound things I’ve learned have come from hearing senior devs admit they don’t have an answer for the technical or architectural problem at hand.

    My boss once told me that as I progress in my software development career, I’ll come across increasing problems for which there is no blog post, no tutorial, no Stack Overflow answer, and that that is where the most interesting growth happens.

    Although we should work against the shame and deleterious effects it entails, “imposter syndrome” shouldn’t be confused for humility. Rather, by embracing our not-enough-ness and vulnerability, we can avoid hubris, learn, grow, and make something good.

  • “Interdisciplinary Insights”

    Aside from blogging here, I’ve been trying to build my writing habit by working on a short story. I wrote some short stories while in college, and thoroughly enjoyed it.

    My best friend, Collin, writes quite a bit. He credits me with initially inspiring him to write, but he has since far surpassed me in quantity of output, and, I would argue, quality. Although he doesn’t code, he said he imagined storytelling and programming to be similar disciplines, or for there at least to be an overlap in skills and practice.

    I think his hunch is true.

    A bit of advice sometimes given to writers is to stop writing in the middle of a scene, or a point of tension in the narrative. That way you have something to work with when you return to it.

    I’m finding a similar practice has been helping me with programming lately. I’m currently working on a tool for importing legacy configurations into a new schema in a new system, and trying to use test-driven development while doing it. The whole export/import system is fairly involved, taking me more than a day to build, and starting the day with a new function or component to write without knowing exactly what you want it to do, let alone how, can be fairly daunting.

    In the spirit of true TDD, I write a failing test that expects a certain output, and then code the feature until the test passes. But, instead of spending tomorrow morning determining the “shape” of data I want and writing a new test, I ended today by writing a failing test, giving me an explicit objective to complete in the morning. I’ll admit it felt uncomfortable leaving this undone, but having something clear to accomplish in front of my when I start my day tends to be very motivating, and provides fuel when moving on to each successive task.

    Never underestimate the power of interdisciplinary insights.