I'm trying to get json from a firebase function request, here's what I'm trying:
export const listener = functions.https.onRequest(async (req, res) => {
return {foo:"bar"}
})
unfortunately this yields a timeout with no result when I go to the appropriate url in chrome, I also tried:
function getDude(){
return {dude:"dude"};
}
export const listener = functions.https.onRequest(async (req, res) => {
return Promise.all([getDude()]);
})
Same result as before.
HTTPS type functions don't return promises, so you should not declare them async. (However, all of the other types of Cloud Functions do require that you return promises for any async work they perform.)
HTTPS type functions are obliged to return a result to the client in order to terminate the function. This is as simple as using res.send(), or any of the methods described in the documentation. That's not to say you shouldn't be using promises in the function to wait for async work to complete - you just don't return one from the function.
Related
With Firebase HTTP functions, we can install express and use middlewares. Middlewares are useful (among other things) for checking pre-conditions before functions execute. For example, we can check authentication, authorization, etc in middlewares so that they don't need to be repeated in every endpoint definition.
How are developers achieving the same thing with Firebase callable functions? How are you extracting out all functionality that would typically be in chained middlewares when you have a large number of callable functions?
It seems that there's no readily available middleware framework for callable functions, so inspired by this, I rolled my own. There are some general purpose chained middleware frameworks on NPM, but the middleware I need is so simple that it was easier to roll my own than to configure a library to work with callable functions.
Optional: Type declaration for Middleware if you're using TypeScript:
export type Middleware = (
data: any,
context: functions.https.CallableContext,
next: (
data: any,
context: functions.https.CallableContext,
) => Promise<any>,
) => Promise<any>;
Here's the middleware framework:
export const withMiddlewares = (
middlewares: Middleware[],
handler: Handler,
) => (data: any, context: functions.https.CallableContext) => {
const chainMiddlewares = ([
firstMiddleware,
...restOfMiddlewares
]: Middleware[]) => {
if (firstMiddleware)
return (
data: any,
context: functions.https.CallableContext,
): Promise<any> => {
try {
return firstMiddleware(
data,
context,
chainMiddlewares(restOfMiddlewares),
);
} catch (error) {
return Promise.reject(error);
}
};
return handler;
};
return chainMiddlewares(middlewares)(data, context);
};
To use it, you would attach withMiddlewares to any callable function. For example:
export const myCallableFunction = functions.https.onCall(
withMiddlewares([assertAppCheck, assertAuthenticated], async (data, context) => {
// Your callable function handler
}),
);
There are 2 middlewares used in the above example. They are chained so assertAppCheck is called first, then assertAuthenticated, and only after they both pass does your hander get called.
The 2 middleware are:
assertAppCheck:
/**
* Ensures request passes App Check
*/
const assertAppCheck: Middleware = (data, context, next) => {
if (context.app === undefined)
throw new HttpsError('failed-precondition', 'Failed App Check.');
return next(data, context);
};
export default assertAppCheck;
assertAuthenticated:
/**
* Ensures user is authenticated
*/
const assertAuthenticated: Middleware = (data, context, next) => {
if (!context.auth?.uid)
throw new HttpsError('unauthenticated', 'Unauthorized.');
return next(data, context);
};
export default assertAuthenticated;
As a bonus, here's a validation middleware that uses Joi to ensure the data is validated before your handler gets called:
const validateData: (schema: Joi.ObjectSchema<any>) => Middleware = (
schema: Joi.ObjectSchema<any>,
) => {
return (data, context, next) => {
const validation = schema.validate(data);
if (validation.error)
throw new HttpsError(
'invalid-argument',
validation.error.message,
);
return next(data, context);
};
};
export default validateData;
Use the validation middleware like this:
export const myCallableFunction = functions.https.onCall(
withMiddlewares(
[
assertAuthenticated,
validateData(
Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
}),
),
],
async (data, context) => {
// Your handler
},
),
);
Middleware for Firebase callable functions is not possible. Callable functions force your endpoint to use a certain path, a certain type of input (JSON via POST) and a certain type of output (also JSON). Express wouldn't really help you out, given the constraints of how callables work. You can read about all the callable protocol details in the documentation. You can see that callables abstract away all the details of the request and response, which you would normally work with when using Express.
As per this community answer,
HTTP requests to callable functions don't really come "from" a URL. They come from anywhere on the internet. It could be a web site, Android or iOS app, or someone who simply knows the protocol to call the function.
If you're building a web app and you want to pass along the URL of the page making the request, you'll have to add that data into the object that the client passes to the function, which shows up in data.
So unless you workaround that by sending the URL in the data of the callable function, it will not work. And even if you do, it just would go against the principle of callable functions, so I would recommend that you use HTTP Functions for that purpose.
I was used to do the following in some of my express routes.
return res.sendStatus(200);
But the res object from NextApiHandlerType does not allow that method.
What would be the equivalent, in this case?
import { NextApiHandler } from "next";
const handler: NextApiHandler = async (req, res) => {
// DO STUFF
return res.??? // WHAT SHOULD I PUT HERE TO RETURN THE status CODE WITH THE STANDARD CODE MSG ?
};
I'm currently doing this, but it seems redundant.
return res.status(200).send("Ok");
From: http://expressjs.com/en/api.html
To anyone seeing this now, I was wondering the same thing. The docs aren't 100% clear IMO. This sends a status code and closes out the request.
res.status(200).end();
Here is what you can find in the documentation :
https://nextjs.org/docs/api-routes/response-helpers
The response (res) includes a set of Express.js-like methods to improve the developer experience and increase the speed of creating new API endpoints, take a look at the following example:
export default function handler(req, res) {
res.status(200).json({ name: 'Next.js' })
}
AFAIK, this is currently not possible inside a Next.js serverless function. I'm using "next": "10.1.3".
The closest alternative is, indeed:
return res.status(200).send("Ok");
I'm learning express from various tutorials and have an app working locally, but I'd like to better understand what each part of the code does.
I'm a bit stumped with the example in the app.route() section here:
https://expressjs.com/en/guide/routing.html
app.route('/book')
.get(function (req, res) {
res.send('Get a random book')
})
.post(function (req, res) {
res.send('Add a book')
})
.put(function (req, res) {
res.send('Update the book')
})
I can see that app is equal to express(), which is a top level function documented here.
And I can see that the .get(), post() and put() methods are chained to the route() method, which is documented here.
Where I get confused is that the docs state that the arguments for the .get(), post() and put() methods are in this format:
app.get(path, callback [, callback ...])
app.post(path, callback [, callback ...])
app.put(path, callback [, callback ...])
Why do the chained .get(), post() and put() methods not require the path argument, and instead have a singular function as an argument that returns values from the Request (aka req) and Response (aka res) object parameters?
I'm obviously missing something simple, so pointers to documentation that could help me better understand the distinctions between these methods when called straight from app, eg app.get(), and from route(), eg app.route('/book').get() would be much appreciated.
Edit: Basically, I'd like to know if there is documentation that defines the required argument format for the .get(), post() and put() methods when called from the route object returned from calling app.route("/book"), because it does not seem to be what is documented, ie path, callback [, callback ...].
app.route()
As per the docs, the app.route method:
Returns an instance of a single route, which you can then use to handle HTTP verbs with optional middleware. Use app.route() to avoid duplicate route names (and thus typo errors).
It means, app.route() takes only the path and returns the route object. Which will have all http verb methods to handle middlewares against one path, get, post, delete, post, put, patch etc.
Why?
To simply have routes which have same path but different HTTP requests. Like:
app.route('/books')
.get() // To get the list of objects
.post() // To save a new book.
Individual HTTP methods
On the other hand, express provides individual methods on app for handling HTTP requests. Like app.get(), app.post(), app.delete().
As per docs for post route: HTTP POST requests to the specified path with the specified callback functions.
Why?
For the cases where you don't have one path for multiple HTTP requests. Let's say:
app.delete('/books/:bookId/comments/:commentId', function(){});
The above route is a kind of single route and only used for deleting a specific comment on book.
I hope I was able to clear the difference.
Reference Link for the docs: https://expressjs.com/en/4x/api.html#router.route
Edit:
As no proper Docs are available listing methods provided by route object:
For more information adding github's link to the express router.
https://github.com/expressjs/express/blob/master/lib/router/route.js
Here see the below code of express's router which adds handler on all methods.
methods.forEach(function(method){
Route.prototype[method] = function(){
var handles = flatten(slice.call(arguments));
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
if (typeof handle !== 'function') {
var type = toString.call(handle);
var msg = 'Route.' + method + '() requires a callback function but got a ' + type
throw new Error(msg);
}
debug('%s %o', method, this.path)
var layer = Layer('/', {}, handle);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
}
return this;
};
});
In this file at top, it has:
var methods = require('methods');
methods: https://github.com/jshttp/methods
Thus, the required parameters for the chained methods is unlimited functions as request handlers/middlewares.
The point of chained method is that they have same path.
So you can write this:
app.route('/book')
.get(function (req, res) {
res.send('Get a random book')
})
.post(function (req, res) {
res.send('Add a book')
})
.put(function (req, res) {
res.send('Update the book')
})
instead of
app.get('/book', function (req, res) {
res.send('Get a random book')
});
app.post('/book', function (req, res) {
res.send('Add a book')
});
app.put('/book', function (req, res) {
res.send('Update the book')
});
Which means that if you i.e. change the endpoint it is changed for all methods, you cannot write typo in one method...
These two middleware functions behave differently and I cannot figure out why:
Here, the error will get trapped by try/catch:
router.get('/force_async_error/0', async function (req, res, next) {
try{
await Promise.reject(new Error('my zoom 0'));
}
catch(err){
next(err);
}
});
But here, the error will not get trapped by try/catch:
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
I thought Express wrapped all middleware functions with try/catch, so I don't see how it would behave differently?
I looked into the Express source, and the handler looks like:
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
};
so why doesn't the try/catch there capture the thrown error?
I'm going to add an answer here even though you've already accepted another one because I think what's going on here can be explained better and this will help others attempting to understand this.
In your code here:
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
Let's discuss what is going on:
First, you declared the callback as async which you had to do in order to use await in it. An async function tells the interpreter to do several important things.
1. An async function always returns a promise. The resolved value of the promise will be whatever the function returns.
2. An async function is internally wrapped with a try/catch. If any exceptions are thrown in the top level scope of the function code, then those exceptions will be caught and will automatically reject the promise that the function returns.
3. An async function allows you to use await. This is an indicator to the interpreter that it should implement and allow the await syntax inside the function. This is tied to the previous two points above which is why you can't use await in just any 'ol function. Any uncaught rejections from await will also reject the promise that the function returns.
It's important to understand that while the async/await syntax allows you to kind of program with exceptions and try/catch like synchronous code, it isn't exactly the same thing. The function is still returning a promise immediately and uncaught exceptions in the function cause that promise to get rejected at some time later. They don't cause a synchronous exception to bubble up to the caller. So, the Express try/catch won't see a synchronous exception.
But here, the error will not get trapped by try/catch
I thought Express wrapped all middleware functions with try/catch, so I don't see how it would behave differently?
so why doesn't the try/catch [in Express] there capture the thrown error?
This is for two reasons:
The rejected promise is not a synchronous throw so there's no way for Express to catch it with a try/catch. The function just returns a rejected promise.
Express is not looking at the return value of the route handler callback at all (you can see that in the Express code you show). So, the fact that your async function returns a promise which is later rejected is just completely ignored by Express. It just does this fn(req, res, next); and does not pay attention to the returned promise. Thus the rejection of the promise falls on deaf ears.
There is a somewhat Express-like framework called Koa that uses promises a lot and does pay attention to returned promises and which would see your rejected promise. But, that's not what Express does.
If you wanted some Koa-type functionality in Express, you could implement it yourself. In order to leave other functionality undisturbed so it can work normally, I'll implement a new method called getAsync that does use promises:
router.getAsync = function(...args) {
let fn = args.pop();
// replace route with our own route wrapper
args.push(function(req, res, next) {
let p = fn(req, res, next);
// if it looks like a promise was returned here
if (p && typeof p.catch === "function") {
p.catch(err => {
next(err);
});
}
});
return router.get(...args);
}
You could then do this:
router.getAsync('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
And, it would properly call next(err) with your error.
Or, your code could even just be this:
router.getAsync('/force_async_error/1', function (req, res, next) {
return Promise.reject(new Error('my zoom 1'));
});
P.S. In a full implementation, you'd probably make async versions of a bunch of the verbs and you'd implement it for middleware and you'd put it on the router prototype. But, this example is to show you how that could work, not to do a full implementation here.
This is because the call is asynchronous, take this code :
try {
console.log('Before setTimeout')
setTimeout(() => {
throw new Error('Oups')
})
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err)
}
console.log("Point of non-return, I can't handle anything anymore")
If you run it you should see that the error is triggered after Point of non-return.
When we're at the throw line it's too late, we're outside of try/catch. At this moment if an error is thrown it'll be uncaught.
You can work around this by using async/await in the caller (doesn't matter for the callee), ie :
void async function () {
try {
console.log('Before setTimeout')
await new Promise((resolve, reject) =>
setTimeout(() => {
reject(new Error('Oups'))
})
)
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err.stack)
}
console.log("Point of non-return, I can't handle anything anymore")
}()
Finally, this means that for Express to handle async errors you would need to change the code to :
async function handle(req, res, next) {
// [...]
try {
await fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
}
A better workaround:
Define a wrap function like this :
const wrap = fn => (...args) => Promise
.resolve(fn(...args))
.catch(args[2])
And use it like this :
app.get('/', wrap(async () => {
await Promise.reject('It crashes!')
}))
Neither of these really answer the question, which if I understand correctly is:
Since the async/await syntax lets you handle rejected "awaits" with non-async style try/catch syntax, why doesn't a failed "await" get handled by Express' try/catch at the top level and turned into a 500 for you?
I believe the answer is that whatever function in the Express internals that calls you would also have to be declared with "async" and invoke your handler with "await" to enable async-catching try/catch to work at that level.
Wonder if there's a feature request for the Express team? All they'd need to add is two keywords in two places. If success, do nothing, if exception hand off to the error handling stack.
Beware that if you don't await or return the promise, it has nothing to do with express - it just crashes the whole process.
For a general solution for detached promise rejections:
https://stackoverflow.com/a/28709667
Copied from above answer:
process.on("unhandledRejection", function(reason, p){
console.log("Unhandled", reason, p); // log all your errors, "unsuppressing" them.
//throw reason; // optional, in case you want to treat these as errors
});
I started learn koa.js and on koa documentation I found such demo code:
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
This middleware use async function, but code is sync. Is this required to use async function everywhere in koa or there is specific case for it?
Short answer :
You can use sync function as well, it will work perfectly.
Long answer :
Promise vs async
Async function can be replaced by synchronous functions returning Promise into a sync function (see http://2ality.com/2016/10/async-function-tips.html for more info)
Koa compose
Koa is using koa-compose to handle middlewares.
If you check compose function in
https://github.com/koajs/compose/blob/master/index.js
Code is
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1);
}))
Your middleware function fn is then bound into Promise.resolve which means that the output will be considered as a Promise even if you return non-Promise.
Sync Promise
It's not documented anywhere and would suggest you to avoid using undocumented patterns and I would do :
app.use(function(context){
// ...
return Promise.resolve();
})
As a sync design compatible with async.