If youā€™re working with URLs in Elixir, you might find yourself needing to add or update query string parameters. For instance, suppose you have an existing URL like https://example.com/path?foo=1 and want to change the value of foo to 42 or add a new parameter like bar=2. In this post, we'll walk through a function that handles both cases, leveraging Elixir's built-in URI module.

Understanding query strings in Elixir

Elixirā€™s URI module provides functions for handling URLs and their components. Specifically, we can:

  • Parse the URL into a struct with URI.parse/1.
  • Decode the query string into a map with URI.decode_query/1.
  • Modify the map to add or update parameters.
  • Encode the updated map back into a query string with URI.encode_query/1.
  • Reconstruct the updated URL with the new query parameters.

Letā€™s go step-by-step and see how this comes together.

Writing the function

To keep things clean, letā€™s create a URLHelper module that contains our function add_or_update_param/3. This function will take an existing url, a key (the parameter name), and a value (the parameterā€™s new value). Hereā€™s how it works:

defmodule URLHelper do
  def add_or_update_param(url, key, value) do
    uri = URI.parse(url)

    # Decode the existing query string to a map, or start with an empty map if nil
    query_map = URI.decode_query(uri.query || "")
    updated_query_map = Map.put(query_map, key, value)

    # Encode the updated map back into a query string
    updated_query_string = URI.encode_query(updated_query_map)

    # Return the updated URL with the new query string
    %{uri | query: updated_query_string}
    |> URI.to_string()
  end
end

This function follows these steps:

  1. Parse the URL: We parse the given URL string into a URI struct. The URI.parse/1 function returns a struct that breaks down the URL into its parts, including the query string.

  2. Decode the Query String: The query string is decoded into a map using URI.decode_query/1. If there is no query string, we initialize an empty map to start with.

  3. Update or Add the Parameter: Using Map.put/3, we add the new parameter or update an existing parameter with the provided key and value.

  4. Re-encode the Query Map: We use URI.encode_query/1 to convert the updated map back into a query string format.

  5. Rebuild the URL: Finally, we update the URI structā€™s query field with our new query string and convert the struct back to a URL string with URI.to_string/1.

Examples

Letā€™s see some examples of this function in action:

url = "https://example.com/path?foo=1"

# Update the value of an existing parameter
updated_url = URLHelper.add_or_update_param(url, "foo", "42")
# => "https://example.com/path?foo=42"

# Add a new parameter if it doesn't already exist
updated_url = URLHelper.add_or_update_param(url, "bar", "2")
# => "https://example.com/path?foo=1&bar=2"

In the first example, we updated the value of foo to 42. In the second example, since bar was not present in the original query string, it was added with the value 2.

Additional Tips

This function handles the essential cases for adding and updating parameters, but you could extend it to handle other situations, such as:

  • Removing a parameter if the value is nil or empty.
  • Accepting multiple key-value pairs at once for batch updates.

For most cases, though, this function will give you the flexibility you need without much overhead.

Wrapping Up

Adding or updating query parameters is a common requirement in web applications, and with Elixir, it's straightforward. By using Elixir's URI module, we can efficiently parse, modify, and reconstruct URLs. With this approach, you can easily manage query strings in any URL, making your application more robust and flexible.