Playing Next Lesson In
seconds

Let's Learn AdonisJS 7 #2.10

Sessions & Flashing Messages

In This Lesson

Learn HTTP sessions in AdonisJS for maintaining user state across requests. We'll also learn about flash messaging and the basics of working with it.

Created by
@tomgobich
Published

Sessions are a temporary data storage mechanism that allows your application to maintain state across multiple requests for a specific user. They're essential for features like authentication, user preferences, and even shopping carts.

When a user first visits your site, AdonisJS creates a session for them and stores a session ID in a cookie. On subsequent requests, the browser sends this cookie, allowing AdonisJS to retrieve the session data associated with that user.

Request 1 → Create Session → Set Cookie → Response
Request 2 → Read Cookie → Retrieve Session → Response
Request 3 → Read Cookie → Retrieve Session → Response

Setting Setting State

Sessions are ephemeral by default; they exist for the duration of the user's visit and can be cleared when the browser closes (depending on configuration).

You can set data on a session using the session.put() method within your controller:

import type { HttpContext } from '@adonisjs/core/http'

export default class ChallengesController {
  // ...
  
  async store({ request, response, session }: HttpContext) {
    const data = await request.validateUsing(challengeValidator)

    challenges.push({ id: challenges.length + 1, ...data })

    session.put('success', 'Challenge created successfully')

    return response.redirect().toRoute('challenges.index')
  }

  //...
}
Copied!
  • app
  • controllers
  • challenges_controller.ts

The session.put() method takes a key and a value. You can store primitives like strings and numbers, objects, or even arrays.

Accessing Session State

Session data can then be accessed throughout your request and views via the HttpContext. Within our controllers and middleware, we can use session.get().

async index({ session, view }: HttpContext) {
  const successMessage = session.get('success')
  
  return view.render('pages/challenges/index', { 
    challenges,
    successMessage 
  })
}
Copied!
  • app
  • controllers
  • challenges_controller.ts

We don't need to access our session within controllers though, session is also made directly available as an EdgeJS global, allowing easy access to a similar API within our views. One key note, though, is that the session in our views is a read-only store. Meaning, we can't alter the store with it.

@layout()

  <div>
    @if (session.has('success'))
      <div class="alert alert-success">
        {{ session.get('success') }}
      </div>
    @endif
    
    <div class="hero">
      <h1>Challenges</h1>
  
      @include('partials/challenge/available_points')
  
      <a href="{{ route('challenges.create') }}" class="button">Create a new challenge</a>
    </div>

    @challenge.grid() 
      @each(challenge in challenges)
        @!challenge.gridItem({ challenge }) 
      @endeach
    @end
  </div>

@end
Copied!
  • resources
  • views
  • pages
  • challenges
  • index.edge

Now, there's an issue with what we just did. If we refresh our page, our session is still signalling that we've just created a challenge because the message is persisted in our session for the duration of our session or until we instruct our session to forget it.

What Are Flash Messages?

Flash messages are a special type of session data in that they only last for a single request. This makes them perfect for one-time notifications like what we have above. Unlike session.put(), where the data persists until you manually delete it, when we use session.flash(), the data is automatically forgotten on the next request.

import type { HttpContext } from '@adonisjs/core/http'

export default class ChallengesController {
  // ...
  
  async store({ request, response, session }: HttpContext) {
    const data = await request.validateUsing(challengeValidator)

    challenges.push({ id: challenges.length + 1, ...data })

    session.flash('success', 'Challenge created successfully')

    return response.redirect().toRoute('challenges.index')
  }

  //...
}
Copied!
  • app
  • controllers
  • challenges_controller.ts

If you'll recall back to our lesson on validation, flash messaging is how request validations provide errors and old form data to us. In that, we learned we can access flash messages using flashMessages.get('success'), which would return our message. Since we're familiar, let's jump straight in because the starter kit is already set up to show our success flash message!

Let's remind ourselves of our layout:

<!DOCTYPE html>
<html lang="en-us">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>
      AdonisJS - A fully featured web framework for Node.js
    </title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @stack('dumper')
  </head>
  <body>
    @include('partials/header')
    <main>
      @include('partials/flash_alerts')
      {{{ await $slots.main() }}}
    </main>
  </body>
</html>
Copied!
  • resources
  • views
  • components
  • layout.edge

Note the flash_alerts partial! If we check out this partial next:

<div class="flash-container">
  @if(flashMessages.has('error'))
    @alert.root({ variant: 'destructive', autoDismiss: true })
      @!alert.description({ text: flashMessages.get('error') })
    @end
  @end
  @if(flashMessages.has('success'))
    @alert.root({ variant: 'success', autoDismiss: true })
      @!alert.description({ text: flashMessages.get('success') })
    @end
  @end
</div>
Copied!
  • resources
  • views
  • partials
  • flash_alerts.edge

We'll see that it's checking to see if our flash message store has an "error" or "success" value. If so, it's using alert components to render out the flash message on our page! So, let's check it out and give our page a refresh.

Hmm... nothing changed. Ah! Because we never instructed our session to forget our previous success value.

Forgetting Session State

Forgetting state in our session is pretty straightforward; we just need to call session.forget() from our HttpContext (remember it's read-only in our views) and provide the key we want to forget. We could also use session.clear() to remove all data from our session.

export default class ChallengesController {
  async index({ session, view }: HttpContext) {
    session.forget('success')
    
    return view.render('pages/challenges/index', { challenges })
  }
}
Copied!
  • app
  • controllers
  • challenges_controller.ts

Now, if we refresh once more, our success message should be gone. Okay, we don't need that anymore, let's remove it.

export default class ChallengesController {
  async index({ view }: HttpContext) {
    return view.render('pages/challenges/index', { challenges })
  }
}
Copied!
  • app
  • controllers
  • challenges_controller.ts

Great, let's create a new challenge really quickly. When we do, we should see our success flash message display. However, this time, if we refresh, our flash message should be gone and won't display again until another challenge is created. Perfect!

Small note, you may find yourself in a situation where you need to keep your flash messages for one more request. This can be easily done using session.reflash(). There's also session.reflashOnly() and session.reflashExcept() to include or omit specific keys.

export default class ChallengesController {
  async index({ session, view }: HttpContext) {
    session.reflash()

    return view.render('pages/challenges/index', { challenges })
  }
}
Copied!
  • app
  • controllers
  • challenges_controller.ts

With this added, now if we create a new challenge, we'll get redirected to our index page. Our index page will reflash it's flash messages, meaning they'll be kept for one more request. So, if we refresh our page, we'll see our success message yet again. This is particularly handy when handling errors manually!

Join the Discussion 0 comments

Create a free account to join in on the discussion
robot comment bubble

Be the first to comment!