Server-side code with Firebase Functions

Krzysztof Rzymkowski
Pragmatists
Published in
4 min readApr 7, 2020

--

This is a second part post in a series about Firebase. Last time, we discussed setting up hosting, firestore and authentication. Now it’s time to introduce server-side code.

Sooner or later, you’ll come across several features that are not feasible to implement in the browser. Whether it’s sending an email or third-party integration, you’ll need to keep a few secrets. You’ll want to run some code in the server, instead of the client’s browsers. Firebase Cloud Functions allow you to do just that.

Let’s start with scaffolding functions code by running firebase init and selecting functions:

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. 
◯ Database: Deploy Firebase Realtime Database Rules
◯ Firestore: Deploy rules and create indexes for Firestore
❯◉ Functions: Configure and deploy Cloud Functions
◯ Hosting: Configure and deploy Firebase Hosting sites
◯ Storage: Deploy Cloud Storage security rules
◯ Emulators: Set up local emulators for Firebase features

Pick typescript:

? What language would you like to use to write Cloud Functions? 
JavaScript
❯ TypeScript

Then confirm the defaults.

Now we also have a functions directory:

functions/
├── node_modules
├── package.json
├── package-lock.json
├── src
│ └── index.ts
├── tsconfig.json
└── tslint.json

A new section also appeared in firebase.json. It defines the commands used to build the function before deployment.

Open functions/src/index.ts. There is some commented out sample code. Let’s change it to something that actually does something:

import * as functions from 'firebase-functions';export const serverTime = functions
.region("europe-west1")
.https.onRequest((request, response) => {
response.json({serverTime: new Date()})
});

This is going to be our first cloud function. request and response parameters are the familiar Express.js Request and Response objects.

While we’re at it, we’ll change the default us-central1 region to the one we want. Oddly, the project’s default region is not automatically taken into account here.

If we run firebase deploy the functions will become available at https://europe-west1-your-project-id.cloudfunctions.net/serverTime

Running functions locally

As we picked Typescript, we need to transpile before we can run our function’s code. In one terminal, run the typescript compiler in watch mode:

npm --prefix functions run build -- --watch

Then in another terminal:

firebase serve

In output, you’ll see the URLs of all your locally running cloud functions:

✔  functions: Using node@8 from host.
✔ functions: Emulator started at http://localhost:5001
i functions: Watching "your-dir/functions" for Cloud Functions...
i hosting: Serving hosting files from: webapp/build
✔ hosting: Local server: http://localhost:5000
✔ functions[serverTime]: http function initialized (http://localhost:5001/your-project-id/europe-west1/serverTime).

Now you can run your cloud function by visiting http://localhost:5001/your-project-id/europe-west1/serverTime

Change something in functions/src/index.ts and refresh. You should see your changes right away.

Integrate functions and hosting

Functions are available at a totally different URL. They are hosted in the cloudfunctions.net domain. We would like to call the relative endpoint from the frontend — like /server-time. Let’s add a Hosting configuration to proxy requests to the function:

{
"hosting": {
...
"rewrites": [
{
"source": "/server-time",
"function": "serverTime"
},

{
"source": "**",
"destination": "/index.html"
}
]
}
...
}

Note that the match-all rewrite rule ("source": "**") must come last. It’s needed in order for client-side routing to work (not used in our example, though).

We can test this setup locally. Just restart firebase serve and visit http://localhost:5000/server-time. Note that the post is 5000 (not 5001 like before). This is the port on which local Hosting emulation runs. Let’s firebase deploy and see if it works in the cloud as well. Check at https://your-project-id.web.app/server-time

Call functions from React

So now, we can make clean relative calls from the React frontend code. Change webapp/src/App.tsx to:

import React, {useEffect, useState} from "react";export function ServerTime() {
const [time, setTime] = useState();
useEffect(() => {
fetch("/server-time")
.then(resp => resp.json())
.then(json => setTime(json.serverTime))
}, []);
return <div>
Server time: {time}
</div>
}

This would be enough to work after deployment, but not locally. To make this work in create-react-app dev mode, we need to add one line to webapp/package.json. The proxy configuration:

"proxy": "http://localhost:5000/",

Now in yet another console run:

npm --prefix webapp start

You can change the response in functions/src/index.ts and see the changes in the React app after refresh on http://localhost:3000. Neat, ha!

Running all three local development modes (function typescript compiler, firebase serve, create-react-app) in separate consoles can be cumbersome. I’d suggest starting all of them from a script:

#!/bin/bash
npm --prefix functions run build -- --watch &
npm --prefix webapp start &
firebase serve &
wait

Sum up

We’ve set up local environment as well as deployment of a React app with server-side code, authentication, persistence, update notifications, access restrictions and hosting. All while still on a free plan.

Firebase lets you get started really fast. It’s a perfect platform for simple side projects. It’s nice that you don’t need to pay for the infrastructure before you actually acquire a user base large enough to make you some money.

The corresponding GitHub project is available at https://github.com/Pragmatists/firebase-blog

--

--