Search code examples
javascriptnode.jsexpressconnect-flash

EJS and connect-flash doesn't work as expected


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(): {}


Solution

  • 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).