0

so.. from this Youtube tutorial: https://www.youtube.com/watch?v=NA21dUBfJhw&list=PL4cUxeGkcC9gcy9lrvMJ75z9maRw4byYp&index=33 I got some code for a 'doto-list'. The problem is the tutorial guy only did local hosting and I'm trying to actually host it on an actual webpage through Firebase. so what I did was adding const functions = require('firebase-functions') at the top and adding exports.app = functions.https.onRequest((request, response) => { response.send("Hello from Firebase!") at the bottom and the only result I get on the actual webpage is "Hello from Firebase!". Is there any way to make the entire 'todo-list' program work on my actual webpage?

index.js

const functions = require('firebase-functions');
var express = require('express');
var app = express();

var todoController = require('./todoController');
app.set('view engine', 'ejs');

app.use(express.static('./'));

todoController(app);



exports.app = functions.https.onRequest((request, response) => {
    response.send("Hello from Firebase!");
});

todo.ejs

<html>
      <head>
        <title>Todo List</title>
        <script
        src="https://code.jquery.com/jquery-3.4.1.min.js"
        integrity="sha256- 
        CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
        crossorigin="anonymous"></script>
        <script src="./assets/todo-list.js"></script>
        <link href="./assets/styles.css" rel="stylesheet" 
         type="text/css">
      </head>
      <body>
        <div id="todo-table">
          <form>
            <input type="text" name="item" placeholder="Add new 
             item..." required />
            <button type="submit">Add Item</button>
          </form>
          <ul>

                  <% for(var i=0; i < todos.length; i++){ %>
                    <li><%= todos[i].item %></li>
                  <% } %>

          </ul>
        </div>

      </body>


    </html>

todoController.js

var bodyParser = require('body-parser');

var data = [{item: 'get milk'}, {item: 'walk dog'}, {item: 'kick 
some coding ass'}];
var urlencodedParser = bodyParser.urlencoded({extended: false});

module.exports = function(app) {

app.get('/todo', function(req, res){
    res.render('todo', {todos: data});

});

app.post('/todo', urlencodedParser, function(req, res){
    data.push(req.body);
    res.json(data);
});


app.delete('/todo/:item', function(req, res){
    data = data.filter(function(todo){
        return todo.item.replace(/ /g, '-') !== req.params.item;
    });
    res.json(data);
});

};

todo-list.js

$(document).ready(function(){

    $('form').on('submit', function(){

        var item = $('form input');
        var todo = {item: item.val()};

        $.ajax({
          type: 'POST',
          url: '/todo',
          data: todo,
          success: function(data){
            //do something with the data via front-end framework
            location.reload();
          }
        });

        return false;

    });

    $('li').on('click', function(){
        var item = $(this).text().replace(/ /g, "-");
        $.ajax({
          type: 'DELETE',
          url: '/todo/' + item,
          success: function(data){
            //do something with the data via front-end framework
            location.reload();
          }
        });
    });

  });

edit: Turns out that as long as I delete response.send("Hello from Firebase!"); and just use exports.app=functions.https.onRequest(app);, it works... which means response.send("Hello from Firebase!); was making this code not work. Any idea why?

Mike Kim
  • 63
  • 8
  • 1
    This line of code isn't going to work: `app.listen(5000, '127.0.0.1')`. You can't listen on some port in Cloud Functions. If you want to run an express app in Cloud Functions, you should use this as an example: https://github.com/firebase/functions-samples/tree/master/authorized-https-endpoint – Doug Stevenson Jun 11 '19 at 15:38
  • I deleted: app.listen(5000, '127.0.0.1'). still not working. I read the documentation but it was super long and it wanted me to completely do over the whole thing and I remember hosting a JavaScript file on Cloud Functions in the past just by adding a couple lines of code such as: const functions = require('firebase-functions'); and exports.app = functions.https.onRequest((request, response) => { response.send("Hello from Firebase!"); }); and I don't know why this worked just fine in the past(like last month) but not anymore. – Mike Kim Jun 11 '19 at 16:31
  • One thing I noticed is that in the code that I wrote in my question, the Cloud Functions works fine and shows the result "Hello from Firebase!" and I didn't get any error message. it's just that the "todo" program wasn't showing on the webpage (probably because something wasn't embedded while Firebase part was working correctly.) – Mike Kim Jun 11 '19 at 16:51
  • 1
    Take a careful look at the example I pointed you at to see how to attach an express app to a function. – Doug Stevenson Jun 11 '19 at 16:56
  • I looked at it. it included like a dozen files. which specific file are you referring to? is it index.js? – Mike Kim Jun 11 '19 at 17:00
  • The documentation doesn't show how to specifically host the program I wrote in my question. it just shows how to host something completely different with so many other commands like require('firebase-admin'), require('cookie-parser'), require('cors) etc etc. the code in the document is basicallly requiring way more things and doing way more things than what I'm trying to do. It makes no sense that just to run my simple code, I have to run a dozen other things and require a dozen other things. – Mike Kim Jun 11 '19 at 17:04

1 Answers1

5

Your original setup does not work because the express application (created using express() call) is never started. An express application must call app.listen(portNum) to start the server and start accepting incoming connections. This, however, is not how cloud functions operate. There is no 'start a server and wait for a request from client' approach. Instead in cloud functions approach, when the request is received, the JS file/project gets spawned, and a request is internally triggered. The idea of your app continuously listening for an incoming connection simply doesn't apply.

Now coming to the approach which does work!

exports.app=functions.https.onRequest(app);

If you notice, in your original code, functions.https.onRequest was accepting a function which is invoked with two parameters request, and response. These objects basically are instances of http.IncomingMessage and http.serverResponse classes respectively. NodeJS internally represents all http requests using a pair of objects like this. Coincidently, when an express application receives a http request from client, it also starts off with these two objects. Based on these two, a pair of much richer Express Request and Response objects are created, and propagated through the middleware chain.

Now when you pass the app, an Express Application, to the functions.https.onRequest, the firebase system will basically consider your app to be a function which takes a http.IncomingMessage object, and a http.serverResponse object as parameters. It's an undocumented feature of expressJS that invoking the application (stored in app reference here) as a function providing IncomingMessage and ServerResponse instances as 1st and 2nd params respectively, behaves like the http request was received by underlying server. The request and response is run through the initial processing, as well middleware chain exactly as any request received directly by express' server would have been. This can be used in the rare scenarios where the user needs a mix of multiple frameworks or otherwise cannot explicitly start the server, but can access the request and response objects by other means. Like in this scenario.

Indeed, every time you invoke your cloud function, firebase functions framework is going to call express application stored in app reference as function, and your middlewares do the rest.

d_shiv
  • 1,670
  • 10
  • 8
  • Thank you for the detailed answer! now this is working properly. and the only problem is that CSS embedding isn't working while everything else is working but if I run it through BASH command which is node index.js after adding app.listen(portNum), everything works perfectly including CSS embedding. But I guess it's a separate issue. – Mike Kim Jun 11 '19 at 18:25
  • Did the `{functionUrl}/assets/todo-list.js` file load in browser? You can check that in sources tab of developer tools. If the js file loaded, css file should too, they are both served in same manner by `express.static` middleware. Although you should consider loading the static resources from elsewhere (a CDN perhaps, even Github works for a few requests). In this approach your cloud function is executed once for each of the assets. – d_shiv Jun 11 '19 at 18:37
  • If it didn't load, look at the error/response in 'network' tab of developer tools. At least the request must have fired, may be the cloud functions provider limits number of parallel requests or something like that. – d_shiv Jun 11 '19 at 18:43
  • so I just checked.. and it turned out ./assets/todo-list.js file didn't load either. In the 'inspect' console, it says "todo:1 Refused to apply style from 'http://localhost:5000/assets/styles.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled." – Mike Kim Jun 11 '19 at 18:54
  • Hmm, looks like the `express.static` middleware didn't work very well. Might be some limitation on server. The error you posted explains that you got an html response. May be navigate to the 'network' tab and refresh the page. Locate the call for `assets/styles.css` and `assets/todo-list.js` in the list of calls. Click on the row, and see the actual response in `preview` / `response` tab that opens on right. The response might have some clue as to what went wrong. In the mean time, you can include these files from Github or copy paste it directly in your ejs file to get things going! – d_shiv Jun 11 '19 at 19:00
  • hmm so I did what you said and for styles.css, the preview said "FAILED TO LOAD RESPONSE DATA". And for todo-list.js, the response said "This request has no response data available." – Mike Kim Jun 11 '19 at 19:52