Firebase Functions work in firebase deployment, but not locally - node.js

This code does not return any data in localhost, but works well when deployed in firebase. Any suggestions? (tried with both 'firebase emulators:start' & 'npm run serve').
I should get a json object containing courses.
I tried with try/catch without any result:
'npm run server' ("npm run build && firebase serve --only functions")
Error: Could not load the default credentials. Error: Unhandled
promise rejections are deprecated. In the future, promise
rejections that are not handled will terminate the Node.js process with a
non-zero exit code. UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error
originated either by throwing inside of an async function without a catch
block, or by rejecting a promise which was not handled with .catch()
'firebase emulators:start' -> no error, but an empty json: {"courses":[]}
Link to code: https://github.com/angular-university/firebase-course/blob/1-firebase-finished/functions/src/index.ts
import * as functions from 'firebase-functions';
import {db} from './init';
import * as express from "express";
const cors = require('cors');
const app = express();
app.use(cors({origin:true}));
app.get('/courses', async (request, response) => {
const snaps = await db.collection('courses').get();
const courses:any[] = [];
snaps.forEach(snap => courses.push(snap.data()));
response.status(200).json({courses});
});
export const getCourses = functions.https.onRequest(app);
_____________________________________________________________________________
init.ts
const admin = require('firebase-admin');
admin.initializeApp();
export const db = admin.firestore();
app.get('/courses', async (request, response) => {
try {
const snaps = await db.collection('courses').get();
const courses:any[] = [];
snaps.forEach((snap: any) => courses.push(snap.data()));
response.status(200).json({courses});
}
catch(err) {
console.error(err)
}
});
export const getCourses = functions.https.onRequest(app);

You need to provide the rights credentials. It works on google cloud because there are environment variables that make this automatically.
You need to set locally this environment variable with the path to your credentials file. Try this in your terminal:
export FIREBASE_CONFIG="/user/path/to/credential.json"
More info
Try following the documentation. It gives good examples on how to do this.

Related

KoaJs cant handle POST requests on CloudFunctions

I have a NodeJS Application written in KoaJS,
app.ts
const app = new Koa();
app.use(healthCheck());
app.use(bodyParser());
app.use(errorHandler());
app.use(endpoints);
export default app;
main.ts
const port = process.env.PORT || 3000;
if (!isCloudFunctions()) {
app
.listen(port, () => {
console.info(`Listening at http://localhost:${port}`);
})
.on('error', console.error);
}
export const api = (req, res) => {
app.callback()(req, res);
}
The app works well on Cloud Runs,
I can deploy the app on Cloud Functions, but on Functions the app can only handle GET requests.
If I try a POST request, I get this error
InternalServerError: stream is not readable
at getRawBody (/workspace/node_modules/raw-body/index.js:112:10)
at readStream (/workspace/node_modules/raw-body/index.js:178:17)
at AsyncFunction.module.exports [as json] (/workspace/node_modules/co-body/lib/json.js:39:21)
at executor (/workspace/node_modules/raw-body/index.js:113:5)
at parseBody (/workspace/node_modules/koa-bodyparser/index.js:100:26)
at new Promise (<anonymous>)
at bodyParser (/workspace/node_modules/koa-bodyparser/index.js:85:25)
at next (/workspace/node_modules/koa-compose/index.js:42:32)
at /workspace/webpack:/sample-explore/apps/sample-api/src/middlewares/health-check.ts:10:12
at Generator.next (<anonymous>)
I re-created the application in ExpressJS, and it works fine with both Runs and Functions
However I am really like the native async/await , compose routing of KoaJS
Does anyone know the reason why KoaJS can not handle POST requests on Cloud Functions?
The json body is automatically parsed in google cloud functions (documentation) and the koa-bodyparser middleware can't handle already parsed bodies.
More info on this issue: https://github.com/koajs/bodyparser/issues/127
Suggested fixes, from the issue thread, are to either use ctx.req.body instead of ctx.request.body, you'll need to parse it when running locally of course.
Alternatively add a middleware that will support already parsed bodies.
function hybridBodyParser (opts) {
const bp = bodyParser(opts)
return async (ctx, next) => {
ctx.request.body = ctx.request.body || ctx.req.body
return bp(ctx, next)
}
}
app.use(hybridBodyParser())

504 on Node.JS frontend Azure AppService calling C# API backend Azure AppService

We have a frontend using ExpressJS server and talk to a backend on .NET 5. Both frontend and backend are running on separate Azure AppService.
FE: https://my-front-end.azurewebsites.net
BE: https://my-back-end.azurewebsites.net
Whenever we try to call the backend from frontend, it will always return 504 Gateway Timeout.
We try to add a simple /hello endpoint on the FE side and we could see {"message":"Hello World!"} is printed out. But the other endpoints, for example api/vessels/3 will get 504 - Gateway Timeout
const dotenv = require('dotenv');
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const bodyParser = require('body-parser');
// import path from 'path';
dotenv.config();
const app = express();
const port = process.env.PORT || 1337; // default port to listen
app.use(cors());
app.use(function(_, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
next();
});
app.use(bodyParser.json());
app.use(express.static(process.cwd() + '/ui/build/'));
// define a route handler for the default home page
app.get('/', (_, res) => {
res.sendFile(process.cwd() + '/ui/build/index.html');
});
app.get('/hello', (_, res) => {
res.status(200).send({ message: "Hello World!" });
});
const getHeaders = (domain = 'ABC') => {
return {
'Content-Type': 'application/json',
'cp-site-domain': domain
}
};
const http = axios.create({
baseURL: process.env.API_API_URL,
timeout: process.env.REQUEST_TIMEOUT
});
app.get('/api/vessels/:orgId', async (req, res) => {
const { orgId } = req.params;
const { data } = await http.get(`/Vessels?organizationId=${orgId}`, {
headers: getHeaders()
});
res.status(200).send(data);
});
// start the Express server
app.listen(port, () => {
console.log(`server started at http://localhost:${ port }`);
});
The error log from iisnode is:
(node:9880) UnhandledPromiseRejectionWarning: Error: connect EACCES 127.0.0.1:80
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1159:16)
at TCPConnectWrap.callbackTrampoline (internal/async_hooks.js:130:17)
(node:9880) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:9880) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
There is no problem if the backend APIs are being called directly from the UI (the usual AJAX). The error only happen when the request to BE is being triggered by ExpressJS.
Do we need to configure something on NodeJS or on Azure AppService side?
I found the solution to this problem.
It turns out, the problem is related to the environment variable. On the local development, we are using dotenv that loads environment variables from a .env file into process.env.
The value of the environment variables are not loaded when we deploy the NodeJS into Azure AppService.
So, we need to add the environment variables into the AppService > Configuration and restart the app.

PATCH route/endpoint in Express not working

Hi I've written the following route for an api endpoint which isn't working. When I test with Postman and my code, it's simply a 404 not found error.
router.patch("/favorite", async (req, res) => {
user = await User.findById(req.body.id)
if (user == null) {
return res.status(404).json({ message: 'Cannot find user' })
}
if (req.body.putArr != null) {
res.user.favPokemon = req.body.putArr;
}
try {
const updatedUser = await res.user.save();
console.log(res.user.favPokemon);
console.log(updateUser);
res.json(updatedUser);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
What am I missing/what error do I have in my code? For reference, here's my mongoDB setup for users:
Edit: Apologies for not specifying the endpoint. To be more clear, the end point and code calling this is:
const favThis = async (e) => { // Patch method to favorite or unfavorite a pokemon
debugger;
e.preventDefault();
try {
console.log(putArr);
const newUser = {userID, putArr};
await axios.patch("http://localhost:5000/users/favorite", newUser);
} catch(err) {
err.response.data.msg && setError(err.response.data.msg)
}
};
, so it's http://localhost:5000/users/favorite. I have other endpoints working fine such as http://localhost:5000/users/login and http://localhost:5000/users/register, and inside server.js I have app.use("/users", require("./routes/users"));
Additionally, server.js is simply
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
require("dotenv").config();
// set up express
const app = express();
app.use(express.json());
app.use(cors());
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`The server has started on port: ${PORT}`));
// set up mongoose
mongoose.connect(
process.env.MONGODB_CONNECTION_STRING,
{
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
},
(err) => {
if (err) throw err;
console.log("MongoDB connection established");
}
);
// set up routes
app.use("/users", require("./routes/users"));
app.use("/todos", require("./routes/todo"));
Edit 2:: I notice now that when I test on Postman, it's an infinite loop and the call is hung. I also get the following warnings in my console:
(node:36447) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
and
(node:36447) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Based on the warning you have failing code inside a promise which is not being caught. Perhaps in this line user = await User.findById(req.body.id).
Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai

Removing Firebase Realtime Database node using Cloud Function

I can successfully get a query param and post it into my realtime database at a path defined by the query param itself, using this code:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.emptyHat = functions.https.onRequest(async (req, res) => {
// Grab the group parameter.
const group = req.query.group;
// Push the new message into the Realtime Database using the Firebase Admin SDK.
const snapshot = await admin.database().ref('/group').push({list: group});
// Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
res.redirect(303, snapshot.ref.toString());
});
If the query param was 'test', the result would be a new entry at /test/{firebaseID}/{'list':'test'}
When I tried to modify it to remove the node named in the query parameter I get errors.
(I'm trying to remove that top level /test node
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.emptyHat = functions.https.onRequest(async (req, res) => {
// Grab the group parameter.
const group = req.query.group;
// Remove the node at the location 'group'.
functions.database().ref('/group').remove();
// Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
//res.redirect(303, snapshot.ref.toString());
});
Error message in the logs:
at exports.emptyHat.functions.https.onRequest (/srv/index.js:95:13)
at cloudFunction (/srv/node_modules/firebase-functions/lib/providers/https.js:49:9)
at /worker/worker.js:783:7
at /worker/worker.js:766:11
at _combinedTickCallback (internal/process/next_tick.js:132:7)
at process._tickDomainCallback (internal/process/next_tick.js:219:9)
Your second function is ignoring the promise returned by the Firebase API. This makes it unlikely to work in Cloud Functions. Cloud Functions HTTP triggers require that you only send a response only after all the asynchronous work is complete. After your function generates a response, it will terminate and clean up any async work still in progress. That work might not complete.
Your second function should be more like your first one, and use the promise returned by remove() and wait for it to complete before sending the response:
exports.emptyHat = functions.https.onRequest(async (req, res) => {
const group = req.query.group;
await admin.database().ref('/group').remove();
// send the response here.
});

Expected catch() or return [promise/catch-or-return] and Each then() should return a value or throw [promise/always-return]

I am getting two errors in line 11 and 12. that
Expected catch() or return [promise/catch-or-return] and Each then() should return a value or throw [promise/always-return]
i am trying to deploy a ExpressJS API to Firebase Hosting
const functions = require('firebase-functions');
const express = require('express');
const app = express();
app.get('/api', (req, res) => {
var qu = req.param('q');
const DuckDuckScrape = require("duck-duck-scrape");
const ddg = new DuckDuckScrape();
var search = ddg.search(qu, -1, "en-us");
search.then((data) => {
res.send(data)
}) ;
});
exports.app = functions.https.onRequest(app);
Please help me out by solving the issue.
That looks like an error from some sort of lint program, not from the actual Javascript interpreter. If that's the case, then you can probably make the linter happy with this:
const functions = require('firebase-functions');
const express = require('express');
const app = express();
const DuckDuckScrape = require("duck-duck-scrape");
app.get('/api', (req, res) => {
const qu = req.param('q');
const ddg = new DuckDuckScrape();
ddg.search(qu, -1, "en-us").then(data => {
res.send(data);
}).catch(e => {
console.log(e);
res.sendStatus(500);
});
});
exports.app = functions.https.onRequest(app);
This covers a couple possible lint issues. First, there's a .catch() so there's no missing error handling. Second, there's no promise left hanging around that the linter thinks you might use later and thus it complains about no return value from the .then().
If you still get the second warning, I'd probably find the config and turn off that warning because it's overzealous here as no return value is need from the .then(), but you could also just add a return null; or change to return res.send(data) if you really just wanted the warning to go away. I personally don't add unnecessary code just to make irrelevant lint warnings go way. I'd rather disable the faulty warning.

Resources