0

I'm trying to wrap my brain around templates and flash messages using EJS and connect-flash along with express and express-session, but I can't seem to get it to work the way I want or expected.

When I define a flash message I console.log it, and I can see it just fine. But when I try to print it in my template, req.flash is an empty object [object Object].

The flash message in console.log after failed login attempt:

flash(): { error: [ 'Brugernavn eller adgangskode er ikke korrekt.' ] }

But when I try to loop through flash in my ejs template with something like

<% flash.forEach(function (message) { %>
    <p><%=message%></p>
<% }) %>

The above returns an error on

TypeError: C:\path\to\my\stuff\auth-test\views\login.ejs:7
    [snippet with line indicator, not important]
flash.forEach is not a function

server.js

const myExpress = require('express')
const myApp = myExpress()
const myBodyParser = require('body-parser')
const flash = require('connect-flash')
const mySession = require('express-session')
const myLogger = require('morgan')
const myFs = require('fs')
const myPath = require('path')
const myEjs = require('ejs')
const myLRU = require('lru-cache')
const myAccessLogStream = myFs.createWriteStream(myPath.join(__dirname, 'logs', 'access.log'), { flags: 'a' })
const myPort = process.env.PORT || 1337

const myAuthCheck = function (myRequest) {
    if (!myRequest.session || !myRequest.session.authenticated) {
        return false
    }
    else {
        return true
    }
}

myApp.engine('ejs', require('express-ejs-extend'))
myApp.set('view engine', 'ejs')

myEjs.cache = myLRU(100)

myApp.use(flash())
myApp.use(mySession({ secret: 'meget hemmelig streng', resave: true, saveUninitialized: true }))

myApp.use(myBodyParser.urlencoded({'extended': 'true', limit: '50mb'}))
myApp.use(myBodyParser.json())
myApp.use(myBodyParser.json({ type: 'application/vnd.api+json', limit: '50mb' }))

myApp.use(myLogger('combined', { stream: myAccessLogStream }))

var hourMs = 1000 * 60 * 60
myApp.use(myExpress.static(__dirname + '/public', { maxAge: hourMs }))

require('./routers/frontendRouter')(myApp, myAuthCheck)

myApp.listen(myPort)

console.log('Serveren kører på http://localhost:' + myPort)

frontendRouter.js

(pay notice to the .post and .get routes for '/login')

const fetch = require('node-fetch')

module.exports = function (myApp, myAuthCheck) {

    myApp.get('/', function (myRequest, myResponse) {
        myResponse.render('home', { title: 'Hello, World!', data: 'Something wicked this way comes!' })
    })

    myApp.get('/test', function (myRequest, myResponse) {
        myResponse.render('article', { title: 'Dette er en test', data: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolor veniam voluptatibus vel omnis nostrum maiores, placeat! Nam ea asperiores, sint, aut reprehenderit eos, facere ipsum in, ratione sunt ab fugiat?'})
    })

    myApp.get('/profile', function (myRequest, myResponse) {
        if (!myAuthCheck(myRequest)) {
            myResponse.render('403', { title: 'Adgang forbudt', status: 403 })
        }
        else {
            myResponse.render('profile', { title: 'Profil' })
        }
    })

    myApp.post('/login', function (myRequest, myResponse) {
        if (myRequest.body.username && myRequest.body.username === 'admin' && myRequest.body.password && myRequest.body.password === '1234') {
            myRequest.session.authenticated = true
            myResponse.redirect('/profile')
        }
        else {
            myRequest.flash('error', 'Brugernavn eller adgangskode er ikke korrekt.')
            console.log('flash():', myRequest.flash())
            myResponse.redirect('/login')
        }
    })

    myApp.get('/login', function (myRequest, myResponse) {
        myResponse.render('login', { title: 'Log ind', flash: myRequest.flash() })
    })

    myApp.get('/logout', function (myRequest, myResponse) {
        delete myRequest.session.authenticated
        myResponse.redirect('/')
    })

}

login.ejs

<% extend('layout') %>
<section>
    <h1><%=title%></h1>
    <form action="" method="post">
        <p><input type="text" name="username" placeholder="Brugernavn"></p>
        <p><input type="password" name="password" placeholder="Adgangskode"></p>
        <% flash.forEach(function (message) { %>
            <p><%=message%></p>
        <% }) %>
        <button type="submit">Log ind</button>
    </form>
</section>

EDIT:

I've console.log()'ed req.flash() both on the sending and receiving end now:

myApp.get('/login', function (myRequest, myResponse) {
    console.log('flash():', myRequest.flash())
    myResponse.render('login', { title: 'Log ind', flash: myRequest.flash() })
})

And the result on the receiving end is

flash(): {}

Brian Emilius
  • 717
  • 1
  • 8
  • 31

1 Answers1

2

The value returned from flash() is an object:

{ error: [ 'Brugernavn eller adgangskode er ikke korrekt.' ] }

So to handle and show errors, you'd use something like this:

<% (flash.error || []).forEach(function(message) { %>
    <p><%=message%></p>
<% }) %>

Or a bit more explicitly (by only passing error flash messages to the template):

// code
res.render('template', { errors : req.flash('error') || [] });

// template
<% errors.forEach(function(message) { %>
  <p><%=message%></p>
<% }) %>

To solve the issue of the object being empty in GET /login, make sure that you don't call req.flash() anywhere in between setting it and getting it (like, for instance, by console.log'ing it). Once you call req.flash(), all stored messages are deleted (very undocumented, but that's how req.flash() behaved in Express 2.x, which connect-flash is trying to mimic).

robertklep
  • 198,204
  • 35
  • 394
  • 381
  • The object is still empty on the receiving end, this wont work until that issue is resolved. I do see your logic though. – Brian Emilius May 07 '17 at 10:52
  • 1
    @BrianEmilius see my edit. In short: don't call `req.flash()` more than once if you want to retrieve stored messages. – robertklep May 07 '17 at 10:57
  • It was console.log that messed it up all along! Thank you for this answer, it saved my day :) It should really be part of the documentation to be honest. – Brian Emilius May 07 '17 at 11:00
  • @BrianEmilius I completely agree! It's very confusing behaviour if you don't know the exact background. – robertklep May 07 '17 at 11:01