[MUSIC PLAYING]
DAVID EAST: Cloud Functions allows
you to write server code that responds to events.
You can respond to HTTP requests and events
within the Firebase ecosystem.
This lets you respond to events and services
like Firestore, Authentication, Realtime Database, Storage,
and so many other Firebase services.
The way it works is, was a new user created
in Firebase Authentication?
Well, run some server code.
Was a document updated in Firestore?
Well, let's run some more server code.
And even though you're writing server code,
with Cloud Functions, it's all serverless,
which means there's no servers or containers for you
to manage.
Just write the code in response to events, deploy,
and let Cloud Functions handle the rest.
This even includes scaling.
Cloud Functions can scale up to the level of traffic
it receives automatically, and in version 2
of Cloud Functions, there's a new feature called concurrency.
In version 1 of Cloud Functions, a function instance
can only handle one request at a time,
and this leads to more instances of functions.
And each time an instance is created,
we have to wait for it to boot up before it can run,
and this is known as cold start.
With concurrency, a function instance
can handle multiple requests, which
means fewer instances will have to spin up
when request comes in, meaning a significant reduction
in cold start.
So whenever you're learning a new tool,
it's really important to see the development workflow,
so in this video, I'm going to show you how to set up, write,
debug, and deploy functions.
We're going to learn a few tips and best practices
along the way, but for now, let's get
started with the most important tool
that you'll use with Cloud Functions in Firebase--
the Firebase CLI.
The CLI works as a project starter,
setting up the boilerplate that you need to develop.
It also works as a local emulator
that spins up a local server that emulates a production
environment and allows you to trigger
different types of functions.
It also serves as the Deployment Manager,
allowing you to deploy all or even
individual sets of functions.
So to get started with the Firebase CLI,
you're going to need Node.js and Java installed,
and this is because the CLI itself needs Node.js,
and the local Firebase Emulator needs Java.
Check the links in the description
because the versions that you'll need for Node.js and Java
will change over time, but once you have them installed, let's
set up a functions project.
We're going to install the Firebase CLI from NPM.
Traditionally, you'll see a lot of people installing
this as a global module.
This means that you can call the Firebase CLI from anywhere
with the Firebase command, and while this
is totally fine to do, in this tutorial,
we're going to do it a bit differently.
Global modules can be difficult to manage,
and sometimes, it's easy to run into permissions errors.
So instead, we're going to install
the CLI as a local module.
I'm going to create a local folder for my project
and then initialize a package.json
file so NPM can keep track of the dependencies
I'm installing.
Now, I'll install the Firebase CLI
as a dev dependency, which lets NPM know that this is
specifically for development.
Once the CLI is installed, I can use
a feature of NPM called NPX, and this
will let you run local modules with a global-like syntax.
The first thing that you need to do
is log in with the Firebase CLI, and this command
will take you out to the browser, where you'll log in,
and then it'll bring you back here,
and you'll be authenticated.
Oh, and pro tip--
if you have to manage multiple accounts,
make sure to check out Firebase login:add and then also
Firebase login:use with the email address to switch between
accounts.
So now, let's use the CLI to boilerplate our functions
using the firebase init functions command.
First, you need to pick a Firebase project,
and if you haven't done this, make
sure to go out into the Firebase Console, create a project,
and then you can select it from here.
And then, now, it's going to ask me what language I
want to write my functions in.
I'm going to use TypeScript, which
is a language that compiles to JavaScript, but go ahead
and pick JavaScript if that's what you want.
The code is going to be very similar.
Then it's going to ask me if I want to use ESLint,
and usually, I do, but sometimes, I
run into problems when I'm creating videos using ESLint.
So for now, I'm going to say no, but in your case,
you can say yes.
So lastly, I'm going to install the dependencies that Cloud
Functions needs with NPM, and then once that's done,
then let's check out the folder structure in VS Code.
Here, we have a root project folder
with its own package.json, and we
can see that we have Firebase tools as a dev dependency.
And the dependency is located in this node modules
folder at the root, and these are the files
that NPM downloaded.
Then we have this functions folder,
and this is what the Firebase CLI created for us.
You'll notice that the folder also
has its own package.json and its own node_modules folder.
So the root project contains the modules
needed for the entire project, whereas the functions
folder only contains the modules that Cloud Functions needs
to run.
So if you need to install a node module for a Cloud Function,
make sure to install it within this folder.
So for TypeScript users, you'll see this SRC folder,
and for JavaScript users, you'll just see index.js.
And this file contains some generic boilerplate,
but I want to cover this all from scratch,
so I'm going to delete it all.
So each function has a similar structure,
so I'm going to break down the anatomy of a function.
So the first thing that you need to do
is import the Firebase functions SDK, and this
can be done two different ways because Node.js currently
allows two different module systems, CommonJS and ESM.
CommonJS has been the main module system for many years,
but the ecosystem is embracing and moving to ESM.
And this is for many reasons, but one of the main reasons
is that it unifies the module system between node
and the browser.
The next part is the function version.
Recently, Google Cloud launched a brand new generation
of Cloud Functions that makes it easier to scale
and dramatically reduces cold start times.
If you are unsure what any of this means, don't worry.
For now, all you need to know is that you
can use either V1 or V2, depending on what you need.
So not everything in version 1 is supported in V2 just yet,
but you can mix and match both versions.
For now, I'm going to target V2, but we'll
be writing another function with V1 as well.
The next part is the function name.
Every function is defined by exporting a named variable.
This has different syntax across CommonJS and ESM.
It's important to note that, in Cloud Functions version 2,
function names are restricted to lowercase letters, numbers,
and dashes.
Then we have the event type.
Every function is based on an event
type, meaning what kind of event do you want to listen to.
One of the most common events are HTTPS events.
When your Cloud Function is deployed,
you'll get a URL that will allow you to trigger this function.
When an event is triggered, where
do you put your custom logic?
In the function callback.
Every callback provides a set of arguments
that you can use to handle the event.
In the case of HTTPS functions, you
receive a request and a response as arguments.
This allows you to inspect the request that comes in
for important information, like query parameters and headers,
and if you ever used Express.js before,
then you'll be right at home here.
Let's do something simple right now,
but stay tuned for a complete video on Express.js and Cloud
Functions.
We'll add the link to the description when it's out.
So for now, let's pretend that we're
building an e-commerce site.
Whenever a user sends a request to a certain path,
we'll look up some information and then return a response back
to the browser.
The first thing that we need to do is get the name of the item
from the request.params.
This comes back as an array, so we're
going to grab the first element.
Then next, we need to grab the message,
so ideally, I'd look this up in a database,
but for now, I'm going to hardcode this as an object
and look up items by its keys.
Then I'll look up the message using the request name
as a key.
And TypeScript, it's not going to this
because it knows it's not type safe, meaning
TypeScript doesn't know what the values of the keys
are going to be.
So I'm going to make this type here
to make TypeScript happy called Indexable, and that lets
TypeScript know that the object has keys that are strings,
and that they hold string values.
So now, I'll send this message back as a little bit of HTML.
Now, before we see this function in action,
let's review the parts.
We have the import, the version, name, event type, and callback,
and this API is similar for both Cloud Functions V1 and V2.
In V2, however, you can get a little more concise
by importing the event provider you want, HTTPS in this case,
and then importing individual type functions.
I prefer to write V2 functions this way,
but do whatever you feel is best.
So to see this function in action,
let's run the Firebase Emulator Suite.
To run the Firebase Emulator Suite,
you'll need to run a command from the Firebase CLI.
And to make things a bit easier, we added these commands
as NPM scripts within the package.json
inside the Functions folder.
The NPM scripts are helpful aliases for longer commands.
For plain JavaScript users, you'll
only need to run npm run serve.
But if you are a TypeScript developer,
I recommend running the watch command
to recompile your TypeScript for any changes
because the emulator will pick up file changes
and automatically update.
So run npm build watch within the Functions folder,
and then let's open up another terminal tab,
and we'll run the emulator command, npm run serve.
So this command will automatically do two things.
It will spin up the local emulator for functions
and print a URL for testing in the terminal.
It will also spin up a local UI that prints out
logs from functions.
I'm going to copy this URL and then enter it in the browser.
And when I try to load the page for a lamp,
it breaks, and I don't even know why, so let's debug.
So instead of writing a bunch of console.logs,
I want to debug with breakpoints.
So you can debug Node.js processes with the Chrome
DevTools, and you can set this up with the emulator
by passing in a flag.
So first, I'm going to drop a debugger statement inside
of the function callback.
Then I'm going to go into the package.json
within the functions folder and add a new NPM script named dev.
This is almost a copy of the serve command,
except I'm going to pass the flag --inspect-functions.
So let's run this command, and then at the end,
we're going to see a section about a debugger
with this kind of long port.
Well, don't worry about that, but we
can connect to it with a special URL in Chrome
called Chrome Inspect.
So at the bottom of this page, we
have a list of remote targets, and we
can click this to inspect the debugging target.
And so when I click it, it opens up a full page
of the Chrome DevTools.
So now, I'm going to send a request to slash lamp,
and it stops the process at the debugger statement.
I'm going to hover over the name variable,
and wow, look at that, it is a leading slash.
And you know what, that doesn't match up with my object keys,
so I know I need to fix this bug by stripping out the leading
slash.
Now, when I trigger this function again,
we get the message, but hold on a second.
The debugger statement didn't stop the process.
So what happened?
Well, whenever you make a code change,
it will create a new process, so you
will need to go back to the Inspect tab
and then open up a new instance of the DevTools.
Let's deploy this function.
Like I mentioned before, deployment
is handled with the Firebase CLI.
You issue the firebase deploy --only functions command,
and the CLI will take care of the rest.
One important thing to note is about pricing.
Your Firebase project will need to be
on the Blaze plan, which needs to have a billing account.
However, even though you will have a billing account,
you will still have access to the free tier.
We recommend that you do all of your development
in the local emulator for Cloud Functions, which won't incur
any functions billing metrics.
All right, let's deploy.
So in the terminal, I'm going to run the NPM script
npm run deploy within the functions folder.
And this will check to make sure that your functions
code doesn't have any obvious errors,
check to make sure that you have all the necessary permissions
enabled, and then, finally, deploy.
Once it's done, you'll get a URL printed out in the console,
and this will look different between V1 and V2
for HTTPS functions.
V1 will look something like this,
with the function name in it, and V2 uses a run.app domain.
All right, so with functions covered,
let's take a look at the functions triggered by events
in the Firebase ecosystem.
So let's say that this amazingly built site manages inventory
with SKUs, or stock keeping units.
Every time a SKU is added, it will
have a price in the United States dollars or USD,
so let's write a Cloud Function that
runs whenever a SKU document is created and then converts
its cost to euros.
So let's remember the anatomy of a function.
We need the import for version 1.
Then we need the name and the event type.
Now, for providers like Firestore,
they have their own special part of the function anatomy,
a builder.
We need to tell Firestore at what document path
we want to trigger the update.
The curly brace SKU acts like a route parameter
that can be triggered for all documents in the inventory
collection.
Now that the document is built, we can specify the event type,
and in this case, that's onCreate.
So whenever a new stock keeping unit, or SKU, is created,
we'll run some kind of logic to convert it from USD to euros.
So within the function callback, we
are provided with an argument of the document snapshot,
and this snapshot contains the data
and other important metadata about the document.
So using the snapshot, we can get the data
and then convert it to euros.
But how do we update the existing document?
Well, this snapshot contains a reference
to the document which has methods for updating it.
But there's a problem with this code.
The update is asynchronous.
If the function shuts down before the operation completes,
the new data will never be saved.
We need to let Cloud Functions know that our code has
completed, so that's why, with background triggers,
you always need to return a promise from the function.
A promise is a way of representing
an asynchronous operation.
It allows you to know when the operation has
completed or errored out.
So many functions within the Firebase SDK return
promises, which makes it easy to integrate into Cloud Functions.
All right, so how do we run this trigger locally?
Well, first, we need to make sure
that we have the Firestore Emulator set up.
So to configure the Firestore Emulator,
open up a new terminal tab and then
run firebase init emulators.
Select Firestore in the wizard with Spacebar.
Then hit Enter.
Select all the default answers, unless you
know that you need something otherwise, but for most people,
the defaults work fine.
So once that's done, run the command firebaseemulators:start
--only firestore.
So I want to run this background function,
but how do I trigger it?
Because I don't have any UI ready to create a SKU,
so I have two options.
So the Firestore Emulator has a local UI
where you can manually enter in data to trigger a background
function.
And we have a whole video on that topic,
and I'll add a link into the description.
And while the Emulator Suite is great for building
a local development environment, repeatedly entering data
in manually can get a bit cumbersome.
So there's a tool that provides an easier way for rapidly
testing of any function type, and that's
what was called the Function Shell.
The Function Shell is for quick invocations of a function,
and it's basically a command line repl for Cloud Functions.
As you might expect, there is an NPM script
for running the Function Shell, npm run shell.
So I'll make sure that the emulator for Firestore
is open in one tab, and then I'll
also run the shell in another.
The shell automatically connects to the emulator
if it's running.
Once the shell is up and running,
I can enter in the name of any function
and call it just like a regular JavaScript function.
This triggers the function, but we're running into a problem,
and this happens when we call Firestore's update method
inside the Cloud Function.
And that's because you can only call update on an existing
document, and triggering a function within the repl
will not create the document.
It only passes the parameters to the function.
So in order to make my function work with the repl,
I'm going to switch from using Update to using Set,
and then I'm going to merge the whole object back.
And I'll also provide an option to indicate
that this is a merge.
So now, I'm going to tear down the Function Shell
and spin it back up, and I'll trigger the function again,
and it works.
We can even see the result in the emulator UI.
Make sure to check out the documentation
for testing with Cloud Functions because there's so much
more that you can do here.
You can load data from a file when triggering functions,
and there's even an entire unit testing library
for making sure that your functions work properly
across code changes.
So one important thing, before you
go creating Cloud Functions with other APIs,
like Stripe, Algolia, SendGrid, or Google Translate,
make sure to check out Firebase Extensions.
Extensions are prepackaged functions
that you can configure with just UI,
so fill out a few text boxes, and just like
that, you have autocomplete with Algolia,
email triggers with SendGrid, and payments with Stripe,
or automatic translations with Google Translate.
So that was your complete guide to getting
started with Cloud Functions, so stay tuned because we have
so many more videos that we're going to follow up
with, such as event types with authentication and storage,
customizing functions instances, which allows you to allocate
warm function instances for better performance.
We're going to talk about configuring environment
variables and secrets.
So subscribe if you want to stay up to date,
but that's all for now, and I will see you in the next video.
[MUSIC PLAYING]