Hosting Node.js Express website on Firebase wth ES modules

This page explains how to configure, deploy and host a website with Express on Firebase. Note that we'll use ECMAScript with modules (import from and export).

I strongly recommand to understand the following concepts before reading this guide.

The guide presented on this page has been tested with the following versions:

In the following, I assume that Node.js and npm are already installed. In my case, I used Volta with which you can select a Node engine when switching between projects.

Create a Firebase project

The first thing to do is to create a Firebase project. Note that to deploy functions, you need a paid plan (Blaze). In the the Firebase console page of your project, add a web app.

Create a folder on your local machine and install firebase inside:

npm install firebase

If Firebase CLI tools are not installed yet, run the following command:

npm install -g firebase-tools

Login to your Firebase account:

$ firebase login

Run the following command to configure your firebase project:

firebase init hosting
firebase init functions

Your project is configured. Let's add Express.

Express

You don't need to install Express since it's already installed with Firebase. But, you'll have to modify the configuration in the functions/package.json. First, since we use ES modules, add the following line in functions/package.json:

"type": "module",

Check the node version in functions/package.json and update to match your Node.js installation (node -v to get your version):

"engines": {
    "node": "18"
},

In the file firebase.json rewrite the following URLs to the myApp function:

  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/**",
        "dynamicLinks": true,
        "function": "myApp"
      },
    ]
  },

Now, create the myApp function that will manage the route /example/. Router. Replace the file /functions/index.js with the following content:

// Import Firebase functions
import functions from "firebase-functions";

// Import express
import express from "express";
const app = express();

import {exampleRouter} from "./example.js";
app.use("/example", exampleRouter);

export const myApp = functions.https.onRequest(app);

You probably noticed that the above code loads a module named example.js. Create the file functions/example.js with the following content:

// Import the router
import express from "express";
const router = new express.Router();

// Route /example/
router.get("/", (req, res) => {
    res.send(`<h1>Example page</h1>`);
});

// Export the router
export const exampleRouter = router;

Run the following command to start your server:

firebase serve

The command should display sometihing like:

✔  hosting[fir-express-87878]: Local server: http://localhost:5000

This is the URL of your local server. Go to http://localhost:5000/example/. If you see the following page, everything is fine:

Firebase and express page example

I notice that the firebase serve command does not always detect changes, especially in files package.json and firebase.json. If you encounters issues, do not hesitate to stop and restart the firebase serve command.

Multiple routes

Since we use modules, it's now easy to create a module for each route. Let's create an API that will manage dynamic routeing Create the file functions/api.js with the following content:

// Import the router
import express from "express";
const router = new express.Router();

// Route /api/
router.get("/", (req, res) => {
    res.send(`<h1>Root of /api/</h1>`);
});

// Route /api/:id
router.get("/:id", (req, res) => {
  res.send(`<h1>/api/${req.params.id}</h1>`);
});

// Export the router
export const apiRouter = router;

Update the file functions/index.js with the new route:

// Import Firebase functions
import functions from "firebase-functions";

// Import express
import express from "express";
const app = express();

// /example/
import {exampleRouter} from "./example.js";
app.use("/example", exampleRouter);

// /api/
import {apiRouter} from "./api.js";
app.use("/api", apiRouter);

// Export
export const myApp = functions.https.onRequest(app);

Now, if you enter http://localhost:5000/api/ in your browser, you should get the following page:

New route in Firebase and Express routing with ES modules

More fun: you can also manage dynamic routing. Go to http://localhost:5000/api/hello, you should see:

New route in Firebase and Express dynamic routing with ES modules

Finally, if you go to http://localhost:5000/, you'll see that static pages are still served:

Static page hosting with Express and Firebase routing

Conflicts

Of course, you probably wonder what happen is the same URL can be serve by functions and static hosting? Firebase first check in the public folder. If the page does not exist, functions are called. Create the folder and file public/api/index.html with the following content:

<h1>Static page</h1>

Go to http://localhost:5000/api/:

Firebase serve static page before calling functions

This is the static page that is served to the client.

Download

You can download the final project on Github:

Project on GitHub.

See also


Last update : 10/15/2023