Search code examples
javascriptnode.jsjsonserverlocalhost

Javascript/Node.js server - querying dataset in JSON file to create URLs


Application desc:

I'm trying to create an HTML template which takes in an array of card objects and uses the data contained within to generate a page to display the list of cards. For each card in this list, I'm supposed to have a link that points to the URL for that specific card (e.g., http://localhost:3000/cards/that_card’s_id) and the link text should indicate the card’s name and unique ID.

Basically, I would type node server.js in my cmd bar and then open google chrome and type localhost:3000 in my address bar, and it would take me to the cards html page which displays all the links. When I click on a specific card, it should take me to another html page that just displays text showing the card name and cost of the card.

Problem:

I created a server. The problem is, when I type in localhost:3000 in my address bar, the page doesn't seem to load. I can't even test to see if my code works as the page doesn't load at all. The page just says "This page isn't working" and "localhost didn’t send any data." I was told it's because in my server code, I'm listening for request urls /cards , /cards/ and /cards? . When I try to access localhost:3000 , the request url is / , so I am trying to access a url that I am not handling.

server.js:

const pug = require("pug");
const fs = require("fs");
const http = require("http");
const url = require('url')

let cardData = require("./cards.json");
let cards = {}; 
cardData.forEach(card => {
    cards[card.id] = card;
});

//Initialize server
const server = http.createServer(function(request, response) {
    if (request.method === "GET") {
        if (request.url === "/cards") {     
              response.statusCode = 200;
              response.write(pug.renderFile("./views/cards.pug", { cards: cards }));
              response.end();
        }else if (request.url.startsWith("/cards/")) {
            const paths = request.url.split("/");
            const cardId = paths[2];
            if (cards.hasOwnProperty(cardId)) {
                const targetCard = cards[cardId];
                response.statusCode = 200;
                response.write(
                    pug.renderFile("./views/card.pug", { card: targetCard })
                );
                response.end();
                return;
            } else {
                response.statusCode = 404;
                response.end();
                return;
            }
        } else if (request.url.startsWith("/cards?")) {
            const params = request.url.split("?");
            const [_, value] = params[1].split("=");
            const limit = parseInt(value);
            if (limit < 1) {
                response.statusCode = 400;
                response.write("Invalid query");
                response.end();
                return;
            }
            const responseCards = Object.values(cards).slice(0, limit);
            response.statusCode = 200;
            response.write(
                pug.renderFile("./views/cards.pug", { cards: responseCards })
            );
            response.end();
            return;
        }
    } else {
        response.statusCode = 404;
        response.write("Unknown resource.");
        response.end();
    }
});

//Start server
server.listen(3000);
console.log("Server listening at http://localhost:3000");

cards.json:

[
    {
    "artist":"Arthur Bozonnet",
    "attack":3,
    "collectible":true,
    "cost":2,
    "flavor":"And he can't get up.",
    "health":2,
    "id":"AT_003",
    "mechanics":["HEROPOWER_DAMAGE"],
    "name":"Fallen Hero",
    "rarity":"RARE"
    },

    {
    "artist":"Dan Scott",
    "attack":3,
    "collectible":true,
    "cost":4,
    "flavor":"Is he aspiring or inspiring? Make up your mind!",
    "health":5,
    "id":"AT_006",
    "mechanics":["INSPIRE"],
    "name":"Dalaran Aspirant",
    "rarity":"COMMON"
    }
]

cards.pug:

html
    head
        title Cards
body

    div#main
        h1 List of Cards:
        each card in cards
            a(href="/cards/" + card.name) #{card.id}
            br

card.pug:

html
    head
        title #{card.name}
body

    div#main
        h1 Name: #{card.name}, Cost: $#(card.cost)

Solution

  • I'm assuming that you have a solid reason for not using express or equivalent webserver framework, and although I would strongly recommend using such a tool for all but the smallest of use-cases, I'll still attempt to point you in the right direction given your current setup.

    You have no route handler for "/": The reasons the server hangs when you go to localhost:3000 is that the request handler function doesn't have any behaviour for "GET /". You should see your response if you instead visit localhost:3000/cards

    Serving the card by id: The best approach (given the technical constraints) to serve the card by id is something like this (rest of code ommited for brevity):

    } else if (request.url.startsWith("/cards/")) {
          const paths = request.url.split("/");
          const cardId = paths[2];
          const card = cards.find((card) => card.id === cardId);
    
          if (!card) {
            response.statusCode = 404;
            response.end();
            return;
          }
    
          response.statusCode = 200;
          response.write(pug.renderFile("./views/card.pug", { card: card }));
          response.end();
          return;
        } 
    

    General Approach Again, I would seriously consider using express, it would force you to divide your code into more readable fragments and make your life much easier if your project continues to grow. By way of example this is how some of your code would look in express:

    const app = require("express")()
    
    app.get("/", (req, res) => {
      return res.sendFile("./views/home.pug")
    })
    
    app.get("/cards", (req, res) => {
      return res.sendFile("./views/cards.pug")
    });
    
    app.get("/cards/:id", (req, res) => {
      const card = getCard(req.params.id);
    
      if (!card) {
        return res.sendStatus(404)
      }
    
      return res.sendFile("./views/cards.pug", {card })
    });
    
    

    This code isn't complete - but I hope it shows you how much cleaner your server could look! I do hope it helps - and best of luck writing your app