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.
Related
Let's say I have an express api with an /api/load-post endpoint. That is handled by the loadPostHandler: RequestHandler
index.ts
const app = express();
app.get("/api/load-post", loadPostHandler);
loadPostHandler.ts
Can I make a fetch request from that handler?
import fetch from "cross-fetch";
export const loadPostHandler: RequestHandler = async (req, res) => {
// HANDLE BLOGPOST LOAD
res.json({ blogPost: blogPostData }) // RES JSON THE BLOGPOST DATA
await fetch("/api/updateViewcount?id=POST_ID"); // MAKE A FETCH REQUEST
};
Is this something people usually do? Or is this an anti-pattern? Not sure if this would even work.
Short answer
Yes, you can make requests in the api call handler in general, and it depends on the requirements of that api.
Longer version
Judging by your example: you want to update view count, and since there is no use of response of it, you don't need to await for the response. You can just fire it without await.
And structurally it would be better practice to move it to a separate function that make an actual call, or fire an event and handle it in a different place.
Moreover, it looks like you are calling the same api server, in that case it will be better just to call a function instead of the api call.
const updatePostViewcount = postId => {
// HANDLE BLOGPOST VIEWCOUNT UPDATE
}
export const loadPostHandler: RequestHandler = async (req, res) => {
// HANDLE BLOGPOST LOAD
// no await here because we don't need the response
// it will still run asynchronously
updatePostViewcount(POST_ID);
res.json({ blogPost: blogPostData }) // RES JSON THE BLOGPOST DATA
};
I have a standard Express app, and I'm using apollo-server-express to add a GraphQL endpoint to it:
const server = new ApolloServer({
schema: executableSchema
});
server.applyMiddleware({ app: expressApp });
I then have the rest of my Express app defining standard REST endpoints. I'd like to keep both interfaces up and running (REST + GraphQL), but reduce the duplication of logic between, say, a POST endpoint handler and a Mutation Resolver, seeing as the meat of the logic for these two is very very similar.
Is there a way to receive a Request in Express, re-format it, and somehow...forward it on the the GraphQL endpoint located on the same server, within the same Express app, without making an unnecessary HTTP call?
It's technically possible because you can execute requests against a schema directly (see here). However, you'd have to manually map your controllers to specific queries and that's unnecessarily complicated. The better option is to extract the common logic into separate modules that are then used by both your GraphQL resolvers and your REST controllers. Keeping your business logic separate from your controllers/resolvers in this way is best practice anyway -- precisely because of scenarios like this.
// post-service.js
module.exports = {
create: (content, author) => {
// this just calls another function, but in reality you might include any number
// of additional steps, for example, transforming and validating the inputs
return Post.create({ content, author })
}
}
// post-controller.js
module.exports = async (req, res, next) => {
const post = await PostService.create(req.body.content, req.user)
return post
}
// resolvers.js
module.exports = {
Mutation: {
createPost: (root, args, ctx) => {
const post = await PostService.create(args.content, ctx.user)
return post
},
},
}
As demonstrated on https://strongloop.github.io/strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/#using-es7-asyncawait, I wanted to use a wrapper for all my async Express handlers to catch any errors happening there, but in a typed version for TypeScript.
I came up with this: (wrap.ts)
import { NextFunction, RequestHandler, Response, Request } from 'express';
type AsyncRequestHandler = (req: Request, res: Response, next: NextFunction) => Promise<any>;
/**
* Catches errors and passes them to the next callback
* #param handler Async express request handler/middleware potentially throwing errors
* #returns Async express request handler with error handling
*/
export default (handler: AsyncRequestHandler): RequestHandler => {
return (req, res, next) => {
return handler(req, res, next).catch(next);
};
};
I want to build a REST API with some endpoints like PUT /users/:userId and DELETE /users/:userId. For convenience, I don't want to query the specific user with the ID userId from the database in every handler and instead store it in req using a middleware. That means I have to use a modified Request interface for the handler definition adding a user property, e.g. UserRequest.
import express, { Request } from 'express';
import wrap from './wrap';
const app = express();
app.use('/users/:userId', wrap(async (req, res, next) => {
// set req.user ...
}));
export interface UserRequest extends Request {
user: User;
}
app.put('/users/:userId', wrap(async (req: UserRequest, res) => {
// do something with req.user ...
}));
// ...
This would be possible when not using wrap, but not with this type definition of wrap. The TypeScript compiler produces the following error:
Argument of type '(req: UserRequest, res: Response) => Promise<void>' is not assignable to parameter of type 'AsyncRequestHandler'.
Types of parameters 'req' and 'req' are incompatible.
Type 'Request' is not assignable to type 'UserRequest'.
Property 'user' is missing in type 'Request'.
What is the "TypeScript way" to accomplish this?
I somehow didn't realize that the issue appears without wrap as well (as long as the strict compiler option is enabled). My solution was to extend the express.Request interface.
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.
I'm new to redux and programming in general and am having trouble wrapping my head around certain unit testing concepts.
I have some async actions in redux, which involve calls to a third party API (from the 'amazon-cognito-identity-js' node module).
I have wrapped the external API call in a promise function, and I call this function from the 'actual' action creator. So for testing I just want to stub the result of externalAWS() function so that I can check that the correct actions are being dispatched.
I'm using redux-thunk for my middleware.
import { AuthenticationDetails,
CognitoUser
} from 'amazon-cognito-identity-js';
export function externalAWS(credentials) {
//This is required for the package
let authenticationDetails = new AuthenticationDetails(credentials);
let cognitoUser = new CognitoUser({
//Construct the object accordingly
})
return new Promise ((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
resolve(result);
},
onFailure: error => {
reject(error)
}
})
}
}
export function loginUser(credentials) {
//return function since it's async
return dispatch => {
//Kick off async process
dispatch(requestLogin());
externalAWS(credentials)
.then((result) => {
dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
})
.catch((error) => {
dispatch(failedLogin(error.message, etc))
})
}
}
I don't have any test code yet because I am really not sure how to approach this. All the examples deal with mocking a HTTP request, which I know is
what this boils down to, so am I supposed to inspect the HTTP requests in my browser and mock them out directly?
It's further complicated by the fact that the second argument of authenticateUser is not even a plain callback, but an object with callbacks as it's values.
Can anyone offer some advice on whether my intention in unit testing the async function is correct, and how I should approach it? Thank you.
Edit: I'm testing in Jest.
Edit2: Request Headers
First POST request,
Second POST request
Edit3: Split the function, trying my best to isolate the external API and create something that is 'easily mock/stub-able'. But still running into issues of how to properly stub this function.
Redux thunk gives you the ability to dispatch future actions within the context of a main action that kicks off the process. This main action is your thunk action creator.
Therefore tests should focus on what actions are dispatched within your thunk action creator according to the outcome of the api request.
Tests should also look at what arguments are passed to your action creators so that your reducers can be informed about the outcome of the request and update the store accordingly.
To get started with testing your thunk action creator you want to test that the three actions are dispatched appropriately depending on whether login is successful or not.
requestLogin
receiveLogin
failedLogin
Here are some tests I wrote for you to get started using Nock to intercept http requests.
Tests
import nock from 'nock';
const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
const fakeCredentials = {
username: 'fakeUser'
token: '1234'
}
it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
})
});
it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
})
});
So I figured it out in the end.
First, I had to require() the module into my test file (as opposed to ES6 import). Then I removed the promise for now since it was adding a layer of complexity and combined everything into one function, let's call it loginUser(). It is a redux async action, that dispatches one action upon being called, and then a success or failure action depending on the result of the API call. See above for what the API call looks like.
Then I wrote the test as follows:
const CognitoSDK = require('/amazon-cognito-identity-js')
const CognitoUser = CognitoSDK.CognitoUser
//Set up the rest of the test
describe('async actions', (() => {
it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
callback.onSuccess(mockResult)
})
store.dispatch(loginUser(mockData))
expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
}))
}))
So basically once requiring the module in, I mocked it in Jest and did a mock implementation too, so that I could access the onSuccess function of the callback object.