Apollo GraphQL without JavaScript (with Ruby on Rails)

I am a big fan of progressive enhancement, as a way to both take advantage of newer and more advanced technologies, while remaining reliable and accessible with tried and true strategies.

One challenge that is often raised when it is proposed is that it can be more work, and everyone is always under pressure to deliver more, more quickly. While this is true, if progressive enhancement is baked into the development and delivery process, it can be a marginal initial expenditure, for much more reliable code*.

When I did research into how others have tackled progressive enhancement with React and GraphQL, and I saw two main trends in the examples I found

  1. Using a framework like Remix
  2. Having a node server

Both of these are valid, but as we had years worth of features built in Rails I wasn't ready to overhaul everything. This article by Kitty Giraudel https://kittygiraudel.com/2020/01/21/apollo-graphql-without-javascript/ gave me a great place to start to add a lightweight layer between our existing code that would easily enable forms to work with javascript turned on or off, and with no additional code in new features (beyond one PR to set it up)

I HIGHLY recommend you start with Kitty's article, as they give a great overview of forms and the surrounding environment (and this builds on top of it instead of replacing it)

The changes to Kitty’s strategy that I built to work with a Rails backend (where there is already GraphQL and Apollo set up) were:

  1. Creating a new route in Rails (in routes.rb)

    post("/graphql", to: "graphql#post_from_form")
  2. The javascript serialization (from the form-serialize library https://www.npmjs.com/package/form-serialize) uses a different notation to what Rails expects. Specifically, when you have an array “foo” with objects with key “var” inside, form-serialize will serialize the first item in the array as foo[0][var], whereas the Rails syntax is implicit as foo[][var] which doesn’t guarantee order or indicate when one of the array items has been deleted. Since the form-serialize notation is more explicit, I used that to submit the data, and then converted it to what Rails expected.

    def update_javascript_form_serialize_format_to_rails_format(parameters) parameters.each do |param_name, param_value| # Check if the variable is a Hash with keys that are all numbers, (eg supposed to be an array) if param_value.is_a?(Hash) && param_value.keys.all? { |k| /\A[-+]?\d+\z/.match(k) } parameters[param_name] = param_value.values end end parameters end
  3. Add the code for the endpoint created in step 1 (essentially from https://kittygiraudel.com/2020/01/21/apollo-graphql-without-javascript/#layering-a-custom-graphql-middleware, converted to Rails) This function allows us to share the same GraphQL endpoint between form submission with javascript and without javascript.

    def post_from_form # Note - depending on permissioning and authorization system # you may need to make adjustments to allow or restrict errors here # Get the data and success and failure paths from the from the request body query = params.delete("__mutation__") success_path = params.delete("__successPath__") failure_path = params.delete("__failurePath__") form_parameters = update_javascript_form_serialize_format_to_rails_format(params.to_h) # Pass the mutation and the variables to Apollo result = graphql_execute(query: query, variables: form_parameters) if result["errors"] flash[:error] = result["errors"] redirect_to(failure_path) else redirect_to(success_path) end end
  4. Finally, I set up a test in Cypress to verify that this workflow worked without JavaScript. (Future blog post incoming!)

With these simple additions on top of Kitty's suggestions (all done in 1 PR), it enables a smooth workflow to enable progressive enhancement without additional steps in each new feature built.

Please reach out to me if you have any questions about this technique!

* I’ll also say that bugs caused by javascript failing to load can be some of the most infuriating and hard to reproduce bugs I’ve ever had to chase down. (JavaScript has failed in my experience mostly due to archaic browsers causing files to be truncated, and poor internet reception causing partial downloads)