JavaScript Express Basics Parameters, Query Strings, and Modularizing Routes Card Template

Jonathan Grieve
MOD
Jonathan Grieve
Treehouse Moderator 85,148 Points

Flashcard template not working

Have there been any breaking changes to Express.js since this course was released? I don't understand why, but my query strings are not working. I can open up the server but as soon as I try to type the URL with the query string it crashes the app.

http://localhost:3000/cards/1?side=question

_http_outgoing.js:491
    throw new Error('Can\'t set headers after they are sent.');
    ^

Error: Can't set headers after they are sent.
    at validateHeader (_http_outgoing.js:491:11)
    at ServerResponse.setHeader (_http_outgoing.js:498:3)
    at Array.write (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\finalhandler\index.js:285:9)
    at listener (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\on-finished\index.js:169:15)
    at onFinish (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\on-finished\index.js:100:5)
    at callback (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\ee-first\index.js:55:10)
    at IncomingMessage.onevent (C:\xampp\htdocs\jgdm-100daysofcode\nodejs\express_site\node_modules\ee-first\index.js:93:5)
    at emitNone (events.js:106:13)
    at IncomingMessage.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)

Code as below.

card.pug
extends layout.pug

block content
    h1 Flash Cards

    section#content
        h2= text

        if hint
            p 
                i Hint: #{hint}
        else
            p
                i Hint: (No Hint available)
app.js
const express = require('express');

//import body-parser middleware
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");



//Express launch function
const app = express();
//set the view engine to use pug for templating
app.set("view engine", "pug");


//use bodyParser and cookieParser middleware - third party.
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());

const indexRouter = require("./routes");
const cardsRouter = require("./routes/cards");

app.use(indexRouter);
app.use('/cards', cardsRouter);

/*MIDDLEWARE*/

/* app.use((req, res, next) => {
    console.log("Hello");
    next();
 });

 app.use((req, res, next) => {
    console.log(", World");
    next();
 }); */

//404 Middleware!
app.use((res, req, next) => {
    const err = new Error('Page Not Found!');
    err.status = 404;
    next(err);
});

//error handler middleware
app.use((err, req, res, next) => {
    // do something
    //res.status(err.status)   
    res.locals.error = err;
    res.status(err.status);
    res.render('error');
    next();
    //res.end();

 });

//Set up the development server listen method - port number
app.listen(3000, function(){
    console.log("server currently running on Heroku");

});
index.js
const express = require("express");
const routes = express.Router();

//list data
const names = [
    "Jack Bauer",
    "Jill Green",
    "John Joe",
    "Jonah Whale",
    "Jamaal LH",
    "Joe Fisher"
]


/*ROUTES*/

//serve the home route
routes.get('/', (req, res) => {

    const name = req.cookies.username;

    //basic response with the send method
    if(name) {        
        res.render('index', {name, page_title: "Flash Card App"});
    } else {
        res.redirect('/hello');
    }
     //res.end();
 });

//serve the register route
routes.get('/register', (req, res) => {

    //basic response with the send method
    res.render('register', {page_title: "Flash Card App: Register of users", names});
    //res.end();

 });

//serve the 4th route which will be a post route 
routes.get('/hello', (req, res) => {

    //store cookie
    const name = req.cookies.username;

    //perform actions based on setting of cookie
    if(name) {
        res.redirect('/');
    } else {
        res.render('hello', { page_title: "Flash Card App: Hello Route"});
    }

    //res.end();

 });

 routes.post('/hello', (req, res) => {

    const name = req.body.username;

    //basic response with the send method,
    res.cookie('username', name);
    res.redirect('/');

    //console.log(req.body);
    //res.end();

 });

 routes.post('/goodbye', (req, res) => {

    //res.clearCookie(req.cookies.username);
    res.clearCookie('username');
    res.redirect('/hello');
 });

 //export index routes to app.js
 module.exports = routes;
cards.js
const express = require("express");
const routes = express.Router();

const { data } = require("../data/flashcardData.json");
const { cards } = data;

 //serve the cards route
 routes.get('/:id', (req, res) => {
   const { side } = req.query;
   const { id } = req.params;
   const text = cards[id][side];
   const { hint } = cards[id];

   const templateData = { text, hint };
   res.render('card', templateData);
});


 //export cards routes to app.js
 module.exports = routes;
Blake Larson
Blake Larson
12,574 Points

What is cards[id][side] returning if you log it (if it gets that far)? Probably best off logging cards and req.query.side before declaring anything to make sure everything is right and the problem is in the route block.

I can't see the json data, but is there supposed to be a conditional for if side is 'question' text = cards[1][0] else cards[1][1] or am I see this wrong?

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

Yea I probably should be logging things a bit more. I’ll look at that.

Changing to req.query.side doesn’t seem to have an effect either.

I don’t think we’re supposed to be making any modifications to the JSON. Or anything that’s coded in the video which is what makes me think the method has changed a bit over the years.

Blake Larson
Blake Larson
12,574 Points

Are you are getting the right return from req.query.side? Have you used a zero or one instead of question? I'm guessing it's formatted like [['question text', 'answer text], ['question text', 'answer text'], ['question text', 'answer text']] in the data file, no?

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

Right... I'm getting "question" logged out from req.query.side which I think is what we're after.

The log for cards[id][side] is logging undefined so that seems to be where the problem lies. I changed the line back to const { side } = req.query; removes that undefined log.

Truth is I'm utterly lost.

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

I have at least now verified my confused rambling about the code being out of date is nonsense by downloading Andrew's code. Now to look in and seek out the bug!

Sean T. Unwin
Sean T. Unwin
Treehouse Moderator 28,638 Points

You're getting the data object from the .json file, I assume. Is cards being set? Is there a match in there for cards[1].question (assuming the json is an array), for example the key is definately 'question`?

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

Yes, I don't think there's any question of the data being correctly hooked up. My console logs come up trumps for the side and question data. Everything just seems to fall apart when I try to input route parameters and query strings in the URL .

Truly I think all that part is actually done correctly. Where I think it's falling apart is there is one get route "register" that doesn't actually appear in the course. I have an array called "data" in the index route as well as it's own file called data.js but that file is redundant and isn't actually being used.

The idea of me doing this is so I can expand it when the course is done and to prove to myself i can practice all of this. But it's like one problem substituting for another at the moment. :)

Blake Larson
Blake Larson
12,574 Points

I'm pretty sure the the cards[id][question] return is undefined because that statically comes out to cards[1]['question']. That's not going to return a valid value which is trying to be used in the pug file as text which is gonna cause a runtime error. If you write that line as

const text = cards[id][question] ? 'This is a placeholder for a .json file' : 'No text returned';

The text in the card.pug should be no text returned.

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

That's not what I get though when I log that output. So confusing!

I'd link you guys to a Repo for this so you could have a proper look but it's linked to a Repo I'm doing for #100DaysOfCode so I'm reluctant to do that.

This is frustrating though because if it wasn't for this, I'd have a functional App! 🤣

Blake Larson
Blake Larson
12,574 Points

Statically set the text then.

const text = 'Static Text';

If that doesn't render then it has to be something with the data because everything else in the route makes sense. I still don't know why cards[id] is an array of arrays and an array of objects.

Sean T. Unwin
Sean T. Unwin
Treehouse Moderator 28,638 Points

Should const templateData = { text, hint }; be const templateData = { text: text, hint: hint };, meaning to assign the key values to the appropriate variable?

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

Hi guys.

I want to thank you both for sticking around the thread with me.

Let's see if we can nip this in the bud. I've created a separate repository with my latest changes. I've finished following the course up to the last section, so it doesn't serve static files yet beyond Pug and JS.

Hopefully the answer is within. :)

https://github.com/jg-digital-media/express_site

Don't forget to npm install obviously.

Blake Larson
Blake Larson
12,574 Points

Yeah try

const { question } = cards[id];

const template = { text: question, hint };

You were referencing a 2d array before. I'm guessing the hint value is correct when you logged it? If not you could try to use JSON.parse(cards) because with the cards are not going through the request so the body parser middleware wouldn't do that automatically. Hopefully that works, I'm on mobile and will try to use the code when I i get home if it's still a problem. Seeing the json file makes me think this will work though, and this is for a hard coded query string with a question. When requesting the answer it would have to be different.

2 Answers

Blake Larson
Blake Larson
12,574 Points

So i ran it and this worked for just getting the id, text, and hint rendered dynamically with no errors.

routes.get("/:id", (req, res) => {
  const { side } = req.query;
  const { id } = req.params;

  let text = "";
  let hint;

  if (side === "question") {
    text = cards[id].question;
    hint = cards[id].hint;
  } else {
    text = cards[id].answer;
  }

  const templateData = {
    id,
    hint,
    text
  };

  res.render("cards", templateData);
});

but the middleware has to change to

//error handler middleware
app.use((err, req, res, next) => {
  res.locals.error = err;
  const status = err.status || 500;
  res.status(status);
  res.render("error");
});

I kinda gutted that route but now you can add the the display values in the dataTemplate. I typed in answer as the query and that also worked. It was a weird middleware glitch that couldn't inject the id. Weird..

Sean T. Unwin
Sean T. Unwin
Treehouse Moderator 28,638 Points

Nice catch. I'm at work so haven't been able to look at the repo.

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

I have a very horrible feeling I've led you both down a wild goose chase for nothing.

If you look at the layout.pug file you'll find an attempt to link a stylesheet directly into the pug template. There is a stylesheet there in the project but it shouldn't be there as that's not the way you serve those assets. Anyway I removed that line so the header is not trying to send that anymore.

The app is now working flawlessly. I've just spent the last 2 hours trying to work out why the question/answer and hint text wasn't showing up after Blake's edit. Now I know why.

I'm so sorry!

layout.pug
<!DOCTYPE html>
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        meta(http-equiv="X-UA-Compatible", content="ie=edge")
        title= page_title

        // Stylesheets
        //link(rel="stylesheet" type="text/css" href="./style.css")
    body

        include includes/header.pug  
        if name 
            h2 Welcome, #{name}

            form(action="/goodbye" method="post")
                button.submit Goodbye!
        p
        a(href='/cards') Begin     

        block content

        include includes/footer.pug
Blake Larson
Blake Larson
12,574 Points

That's funny I was wondering why I was getting 0 and style.css when I tried to log the id. Was confusing. Either way it worked!

Jonathan Grieve
Jonathan Grieve
Treehouse Moderator 85,148 Points

Believe me so was I. It was staring me in the face and screaming from the console! Live and learn, as they say!