Designing Web APIs using Google Firebase Functions: Achieving True Routing

Some recommended reading prior to jumping into the exercise:

Question: Which of the following API layouts look more clear to you?

Option A:

/createUser?uid={UID}
/getUser?uid={UID}
/updateUser?uid={UID}
/addFriend?uid={UID}&toAdd={TO_ADD_UID}
/sendMessageToUser?uid={UID}&sendTo={SEND_TO_UID}

Option B:

/users/:uid
/users/:uid/create
/users/:uid/update
/users/:uid/addFriend/:toAddUid
/users/:uid/sendMessage/:sendToUid

If you answered Option A, this article might not be very useful to you ¯\_(ツ)_/¯ If you chose Option B and you love Firebase as much as I do (or are considering using Firebase Functions to replace some of your server-side code), read on!


Notice: All code snippets in this article are written in the latest version (2.3.4 ATTOW) of Typescript. This means you must transpile the code in the snippets to Javascript before deploying them to Firebase Functions.

Checkout Allan Yoshio Hasegawa’s awesome post on setting up a Firebase Cloud Functions project in Typescript! Allen outlined the folder structure I like to follow for FCF projects.


Motivation for routing

Firebase is currently one of the best tools for rapid development in the serverless space, and with the addition of Firebase Cloud Functions it is becoming a very enabling and promising stack to build on.

One thing I had trouble with right off of the bat when I began using Firebase Functions was the fact that I needed to write my routes in a function first manner (see Option A above), a single prefixed endpoint where routing was disregarded (often called a verb-route or verb-endpoint).

Take the following route (a verb-route) using a basic query-parameter syntax as an example:

/getUser?uid={UID}

This is easily achieved with Firebase functions, and is the style used in the Firebase Functions tutorials. You would deploy this function using the following index.ts:

Simple Firebase Function. functions.https.onRequest actually interfaces with ExpressJs.

But does it really conform with the OpenAPI standard for web APIs? Is this maintainable in terms of project structure? I would answer, no, and simply cry in agony at the sight of a large API designed entirely in this manner. For example…


Suppose you had this awesome foobar object that also had friends that it would send a message to through some superSecure protocol and also had a biz attached to it. One of the hardest things in programming is naming and who in their right mind can name a method like that? My best shot was:

/foobarSendMessageToFriendThroughSuperSecureProtocolWithBiz

Yuck. You can see how this can start to become a habit all over your API (and I didn’t even include the query parameters). We don’t want this. It’s not reusable and it lacks the benefits of routing like path matching and path parameters as well as wildcards and a whole bunch more.

/foobar/sendFriendMessage/:friendId/superSecure/:bizId

Nice….ish. Don’t make this platform a reality, please. Anyways, back to where we were.


Let’s make it better!

We don’t like /getUser?uid={UID} so then what is a good alternative?

A more clean approach for our getUser endpoint would be the following endpoint:

/users/:uid

Much better. Why? This URL schema is much more maintainable and reusable when designing a web API and can be more self documenting than its query-parameter counterpart (see Option B above).

With Express, we can easily define this route endpoint like so:

This api implementation is much cleaner.

This is what the actual Firebase URL that was generated for the above snippet looks like (the host url is localhost because I am running these functions locally using the Firebase CLI so don’t try to hit these routes yourself):

http://localhost:5002/simple-chat-efd5f/us-central1/api

Even though it looks like we only have one endpoint running, it’s actually the gateway to all of the routes we define in Express.

I ran the following request in a REST client:

GET http://localhost:5002/simple-chat-efd5f/us-central1/api/users/1

The response:

You requested user with UID = 1

Woop! We have proper routing now :)

This is a much cleaner and more maintainable approach to using Firebase Cloud Functions and can be expanded to separating your routes into different files so that you can achieve high modularity.

Read on to see how you can place your routes like /api/users into its own folder, otherwise jump to the bottom of the page for the final note.


Organization is key

If you’re reading this, I assume you’re as much of a neat-freak as I am! Most experienced Express developers won’t really need this part of the tutorial but I figured I’d include it anyways to keep the coverage level high.

Okay, we want to break out the /api/users endpoint into its own folder/file/both, that should be simple.

Let’s assume your Firebase Functions project has a simple project structure (as outlined by the Typescript setup post linked above):

.
+-- public/
+-- functions/
| +-- node_modules/
| +-- src
| | +-- index.ts
+-- database.rules.json
+-- firebase.json

Right now, all of our routes are being defined in index.ts (consider this file your api center-piece and is where all sub-routes will be defined, this is because of how Firebase Functions deployment works). But we want to start breaking that apart so that the file doesn’t explode in size. Let’s go ahead and create a folder for our users sub-route.

Lets introduce some new folders and file(s) into our project:

.
+-- public/
+-- functions/
| +-- node_modules/
| +-- src
| | +-- index.ts
| | +-- api/
| | | +-- users/
| | | | +-- index.ts

+-- database.rules.json
+-- firebase.json

Now any folder in the api folder is considered it’s own sub-route. Choose any directory structure you’d like, as long as you have an index.ts file that does not conflict with the one in the root of thesrc folder.

Now, lets open up functions/src/api/users/index.ts :

Simple /users route with only one match, emulating our getUser method.

Cool, we now have the router for our /users sub-route! Feel free to add as many sub-routes as you’d like to the /users sub-route, I just provided what we’ve been using in the example as a starting point (getUser). Let’s register it in the root of the API (the index.ts sitting in /functions/src).

Nice! Let’s fire up this API and see what url Firebase Functions generates for our endpoints.

http://localhost:5002/simple-chat-efd5f/us-central1/api
Is there something wrong? Where’s the /users route? :(

It’s there! Remember what I said before, the exports.api = ... is where we tell Firebase Functions hey FF, I have a route named /api, send all those requests to my Express app instance, please and thank you so the route should be there as long as you didn’t mess anything up along the way.

Lets run make the following request and see what the response is:

GET http://localhost:5002/simple-chat-efd5f/us-central1/api/users/123

Checkout the response:

You requested user with UID = 123

Nice! And our route looks perfect, /api/users/123 .


You’ve gotten this far so I’d bet you’ll be writing awesome OpenAPI conforming web APIs with the awesome Firebase Cloud Functions platform in no time!

Remember, we always prefer easy-to-read routes that are reusable through a properly laid out tree of endpoints. A nice example of a beautiful well designed API is Twitter’s API.

This is my first Medium post so comments and criticism are appreciated! Let me know if there’s other stuff you think I should look into in regards to Firebase, and share some of your experiences if you’ve set up a similar style API on FCF! I’m always excited to do fun things and learn something new, especially with Firebase.

This is the first of a a series I’m starting here on medium called Firebase for the Fearless where I’ll share information and exercises on how you can utilize the Firebase platform to develop beautiful and scalable modern applications fast!

> Cheers, @be
https://atbe.me