Lesson Monday

So far we've been using query parameters in URLs to pass data from one page to another. Do you recall, after submitting our form the URL of our app in the browser looks something like this? http://localhost:4567/greeting_card?sender=Lucy&recipient=George. The portion that reads sender=Lucy&recipient=George are the query parameters.

This is effective for moving small amounts of data between pages. But what if we needed this data on every page? Or what if we had a much larger amount of information? It would be unwieldy to keep adding more information in our URLs. They'd get really long! And if a user came back to the website without bookmarking their unique URL with their information in the query parameters, they'd lose all of their data!

Sessions

Thankfully, we can use a session to store persistent data from page to page within Spark. A session essentially refers to a user's time browsing your website or using your app. That is, an individual user's connection to your application's server. The server keeps basic data for each user's session. We can also access a user's session ourselves, and manually place some data in it to use later.

Adding Information to a Session

Consider a route in App.java:

    get("/", (request, response) -> {
      Map<String, Object> model = new HashMap<String, Object>();
      return new ModelAndView(model, form.hbs);
    }, new HandlebarsTemplateEngine());

In a route, the terms request and response refer to the HTTP request made in order to access the route, and the HTTP response provided by the server. We can access a user's session by looking at the request, essentially asking "Whose session made this request?" The method to do that looks like this:

request.session()

Additionally, once we have the session, we can instruct it to hold additional information. These additional pieces of information are stored in key-value pairs, similar to entries in a HashMap. The code to add information to a user's session looks like this:

request.session().attribute("a String key", someSortOfValue);

Here, we include two arguments: A String key, and whatever value we're storing alongside that key.

As you can see, the session acts like our model HashMap. It accepts a String as its key, and any object as its value. This session is unique to each user accessing your page. It works by storing information for each user in a special place on the server, and setting a cookie on each user's browser so that it can tell which session belongs to which user.

Retrieving Information from a Session

Once information has been placed in a session, we should be able to retrieve it back for as long as that user continues interacting with our application. The code for retrieving data from a session looks very familiar to the code for adding data to a session:

request.session().attribute("a String key");

We use the same request.session().attribute() chain of methods, but we only offer the key in the key-value pair as an argument. The code to store or retrieve information from user sessions resides in App.java routes.

Using Sessions with Spark

In the next few lessons we'll walk through using sessions in a To Do List application we will create together. But before we do, let's briefly walk through how sessions are integrated into Spark using a fictional example.

Let's assume we have an application of some kind that greets users by name. The user's name is stored in the user's session data. The homepage of the site is one of the areas that displays a custom message greeting a user by name. In order to display their name we must first retrieve this information from their session data, then we must add it to the model HashMap where Handlebars retrieves information to display in our template:

App.java
get("/", (request, response) -> {
  Map<String, Object> model = new HashMap<String, Object>();
  model.put("username", request.session().attribute("username"));
  return new ModelAndView(model, welcome.hbs);
}, new HandlebarsTemplateEngine());

In the get("/") route, we are doing two things:

  • We are placing a "username" key into the model with the put() method.
  • In order to provide the user's name as the corresponding value, we access the "username" attribute in the session with model.put("username", request.session().attribute("username"));.
    • The request.session() portion of this code is calling .session() upon the HTTP request made by the user's client.
    • The .attribute("username") is requesting a piece of data called "username" from the session we've just retrieved.
    • Then, the entire line of code used to retrieve the username from the user's session ("username", request.session().attribute("username")) is passed as an argument to model.put() in order to place this user's username information in our model HashMap for later use in our template.

If we didn't have a "username" attribute stored for that user's session, this code would simply add a null value to model. This sounds like it might be a bad thing, but we can actually use it to our advantage! We can include an #if statement that displays different content depending on whether or not the user's name is null, like this:

welcome.hbs
{{#partial "content"}}

<!--welcome hbs-->
{{ #if username }}
  Welcome {{ username }}
{{ else }}
  <form action="/welcome" method="post">
    <label for="username">Your name</label>
    <input id="username" name="username" type="text">

    <button type="submit">Submit name</button>
  </form>
{{ /if }}

<a href="/">Return to home page</a>
{{/partial}}

{{> layout.hbs}}


<!--welcome hbs-->

Since we don't have username defined, this statement: {{ #if username }} reads the same as {{ #if null }}, since username will be null if we do not have one defined. If we tried to do this in Java we would receive an error. Handlebars, however, reads {{ #if null }} the same as {{ #if false }}. As seen above, this allows us to show a welcome message if we have a username saved, or display a form to collect the user's name if we do not.

POST Requests

In the code above, also notice we've added an HTTP method to our form with the method="post" attribute. As we covered earlier, there are multiple types of HTTP methods. So far, we've only used GET. GET is used to retrieve resources from the website's server and display it to the user. POST, however, is used to alter resources on the website's server.

For instance, when we sign up for a website's mailing list, we are likely executing a POST request, because we're changing information on their server; we're adding our email to their email list. Another example is editing a comment on Facebook or another social media site. You're not just retrieving information, you're actually changing information.

If we don't provide a method attribute to an HTML form, it will use GET by default. We've only used GET methods so far, which is why we haven't included method attributes in our previous forms. But because the form above collects information we will later add to our server, a POST request is more appropriate than GET.

Don't worry if this feels a little unclear right now, that's totally normal. Just remember GET requests retrieve information, and POST requests change information.

Routing POST requests

The first line of the form in the example above looks like this:

<form action="/welcome" method="post">

We just recently learned that the form's action attribute must match the path of a route in App.java. For instance, since this form lists /welcome as its action attribute, the information a user provides in this form will be sent to the /welcome route in App.java when the form is submitted.

Now, the method attribute on a form must also match the corresponding route in App.java. That means the information from the form above will be sent to the route with the verb post() and the path /welcome. Like this one here:

App.java
...
post("/welcome", (request, response) -> {
  Map<String, Object> model = new HashMap<String, Object>();

  String inputtedUsername = request.queryParams("username");
  request.session().attribute("username", inputtedUsername);
  model.put("username", inputtedUsername);

  return new ModelAndView(model, welcome.hbs);
}, new HandlebarsTemplateEngine());
...

Again, notice that because this form is submitting a POST request, the corresponding route in App.java opens with the post() method not the get() method.

Inside this route we save the username from the query params into a String with String inputtedUsername = request.queryParams("username");. Then, we add the username into our session with the line: request.session().attribute("username", inputtedUsername);

After we save the username to the session, it's available in any other route. All we have to do is use the following line of code to retrieve it: request.session().attribute("username");.

Check out the Spark Documentation on Sessions to read more about interacting with sessions, and how they work. In the next lesson, we'll walk through accessing, storing information in, and retrieving sessions together by integrating them into our To Do List application.