Search code examples
firebaserestexpressgoogle-cloud-functionsfirebase-hosting

Error when POST request to express API with Firebase functions


I have set up a Firebase project with Functions and Hosting and built a simple API with express. I am using Firebase's local emulator suite.

I use Postman to make the following request

POST
http://localhost:5000/api/users
{
    "name": "Kobe Bryant",
    "email": "kobe@gmail.com"
}

and I receive a response of

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>Error</title>
</head>

<body>
    <pre>Cannot POST /dreamkit-client-web/us-central1/api/api/users</pre>
</body>

</html>

with status code of 404. Also, this is what the logs say

00:53:17 I [hosting] Rewriting /api/users to http://localhost:5001/dreamkit-client-web/us-central1/api for local Function api
00:53:17 I function[api] Beginning execution of "api"
00:53:17 I hosting 127.0.0.1 - - [21/Nov/2020:05:53:17 +0000] "POST /api/users HTTP/1.1" 404 185 "-" "PostmanRuntime/7.26.8"
00:53:17 I function[api] Finished "api" in ~1s

I think the issue has to do with the api/api/users, but I made sure not to have a duplicate /api because my function's source is already /api. How can I fix this issue?

firebase.json

{
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint"
    ],
    "source": "functions"
  },
  "hosting": {
    "predeploy": [
      "npm --prefix client run build"
    ],
    "public": "client/build",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/api{,/**}",
        "function": "api"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "emulators": {
    "functions": {
      "port": 5001
    },
    "firestore": {
      "port": 8080
    },
    "hosting": {
      "port": 5000
    },
    "ui": {
      "enabled": true
    }
  }
}

index.js

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const express = require('express');

//initialize firebase in order to access its services
admin.initializeApp();

const app = express();

// Init Middleware
app.use(express.json({ extended: false }));

// Define Routes
app.use('/users', require('./routes/users'));

app.get('*', (req, res) => {
  res.send('Hello from the API');
});

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

users.js

const express = require('express');
const router = express.Router();
const admin = require('firebase-admin');
const { check, validationResult } = require('express-validator');

//initialize the database and the collection
const db = admin.firestore();
const usersRef = db.collection('users');

// @route     POST users
// @desc      Register a user
// @access    Public
router.post(
  '/',
  [
    check('name', 'Please add name').not().isEmpty(),
    check('email', 'Please include a valid email').isEmail(),
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { name, email } = req.body;

    try {
      let user = await usersRef.where('email', '==', email).get();

      if (user.exists) {
        return res.status(400).json({ msg: 'User already exists' });
      }

      user = { name, email };

      const payload = await usersRef.add(user);
      const id = payload.id;

      res.json({ id });
    } catch (err) {
      console.error(err);
      res.status(500).send('Server Error');
    }
  }
);

module.exports = router;

Solution

  • I think you might have to include the "/api" prefix here:

    app.use('/api/users', require('./routes/users'));