Request parameters and payloads

Handling data sent over HTTP requests is a fundamental aspect of web development. This guide covers how to efficiently manage everything from GET query parameters to POST data and file uploads using Genie's built-in utilities.

Handling GET query params

Genie makes it easy to access query params, which are values sent as part of the URL over GET requests (ex: mywebsite.com/index?foo=1&bar=2 foo and bar are query params corresponding to the variables foo = 1 and bar = 2). All these values are automatically collected by Genie and exposed in the params() collection (which is part of the Router module).

Example

using Genie

route("/hi") do
  name = params(:name, "Anon")

  "Hello $name"
end

If you access http://127.0.0.1:8000/hi the app will respond with "Hello Anon" since we're not passing any query params.

However, requesting http://127.0.0.1:8000/hi?name=Adrian will in turn display "Hello Adrian" as we're passing the name query variable with the value Adrian. This variable is exposed by Genie as params(:name).

Genie however provides utility methods for accessing these values in the Requests module.

The Requests module

Genie provides a set of utilities for working with requests data within the Requests module. You can use the getpayload method to retrieve the query params as a Dict{Symbol,Any}. We can rewrite the previous route to take advantage of the Requests utilities.

Example

using Genie, Genie.Requests

route("/hi") do
  "Hello $(getpayload(:name, "Anon"))"
end

The getpayload function has a few specializations, and one of them accepts the key and a default value. The default value is returned if the key variable is not defined. You can see the various implementations for getpayload using the API docs or Julia's help> mode.

Reading POST payloads

Genie makes it easy to work with POST data. First, we need to register a dedicated route to handle POST requests. Then, when a POST request is received, Genie will automatically extract the payload, making it accessible throughout the Requests.postpayload method -- and appending it to the Router.params(:POST) collection.

Handling form-data payloads

The following snippet registers two routes in the root of the app, one for GET and the other for POST requests. The GET route displays a form which submits over POST to the other route. Finally, upon receiving the data, we display a message.

Example

using Genie, Genie.Renderer.Html, Genie.Requests

form = """
<form action="/" method="POST" enctype="multipart/form-data">
  <input type="text" name="name" value="" placeholder="What's your name?" />
  <input type="submit" value="Greet" />
</form>
"""

route("/") do
  html(form)
end

route("/", method = POST) do
  "Hello $(postpayload(:name, "Anon"))"
end

up()

The postpayload function has a few specializations, and one of them accepts the key and the default value. The default value is returned if the key variable is not defined. You can see the various implementations for postpayload using the API docs or Julia's help> mode.

Using JSON payloads

A very common design pattern, especially when developing REST APIs, is to accept JSON payloads sent as application/json data over POST requests. Genie efficiently handles this use case through the utility function Requests.jsonpayload. Under the cover, Genie will process the POST request and will attempt to parse the JSON text payload. If this fails, we can still access the raw data (the text payload not converted to JSON) by using the Requests.rawpayload method.

Example

using Genie, Genie.Requests, Genie.Renderer.Json

route("/jsonpayload", method = POST) do
  @show jsonpayload()
  @show rawpayload()

  json("Hello $(jsonpayload()["name"])")
end

up()

Next we make a POST request using the HTTP package:

using HTTP

HTTP.request("POST", "http://localhost:8000/jsonpayload", [("Content-Type", "application/json")], """{"name":"Adrian"}""")

We will get the following output:

jsonpayload() = Dict{String,Any}("name"=>"Adrian")
rawpayload() = "{\"name\":\"Adrian\"}"

INFO:Main: /jsonpayload 200

HTTP.Messages.Response:
"""
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked

"Hello Adrian""""

First, for the two @show calls, notice how jsonpayload had successfully converted the POST data to a Dict. While the rawpayload returns the POST data as a String, exactly as received. Finally, our route handler returns a JSON response, greeting the user by extracting the name from within the jsonpayload Dict.

Handling file uploads

Genie has built-in support for working with file uploads. The collection of uploaded files (as POST variables) can be accessed through the Requests.filespayload method. Or, we can retrieve the data corresponding to a given file form input by using Requests.filespayload(key) -- where key is the name of the file input in the form.

In the following snippet we configure two routes in the root of the app (/): the first route, handling GET requests, displays an upload form. The second route, handling POST requests, processes the uploads, generating a file from the uploaded data, saving it, and displaying the file stats.


HEADS UP

Notice that we can define multiple routes at the same URL if they have different methods, in our case GET and POST.


Example

using Genie, Genie.Router, Genie.Renderer.Html, Genie.Requests

form = """
<form action="/" method="POST" enctype="multipart/form-data">
  <input type="file" name="yourfile" /><br/>
  <input type="submit" value="Submit" />
</form>
"""

route("/") do
  html(form)
end

route("/", method = POST) do
  if infilespayload(:yourfile)
    write(filespayload(:yourfile))

    stat(filename(filespayload(:yourfile)))
  else
    "No file uploaded"
  end
end

up()

Upon uploading a file and submitting the form, our app will display the file's stats.