We're going to workshop our way through to a functional web application built on the Firebase platform.
We'll try to abstract away as much of the non-Firebase code as possible so that we can focus on the Firebase.
You'll need to install node, which comes bundled with the npm package manager for Node.js dependencies. Node version 8.12.0 is ideal, but any Node version 8.x should work. Run node --version
to verify that you're on the right version of Node. You can use nvm for Unix or nvm for Windows to manage your local Node version.
We'll use a Node package named npx to run some of our Node.js executables.
npx
is Node.js package runner written with--of course!--Node.js. npx
can find an executable from either the local or the global node_modules
folder. It will attempt to find the executable dependency locally, then globally. If that fails, npx will install it into a global cache and run it from there.
- Visit
https://console.firebase.google.com/
and create a project. - Name the project whatever you'd like. The options here don't matter much.
- Install
npx
withnpm install --global npx
npx firebase login
to log in with the same Google account that you used for your Firebase project
The firebase-tools
CLI can now be accessed by running npx firebase
.
npm install --global npx
to install npxgit checkout complete
to check out the completed branchnpm install
to install the front-end dependenciescd functions && npm install
to install Cloud Functions dependenciescd functions && npm test
to run Cloud Functions testsnpm run-script deploy
to deploy the Firebase app
npm install --global npx
to install npxgit checkout master
to check out the workshop starting pointnpm install
to install the front-end dependenciesnpx firebase init
- Use the arrow keys to install all of the Firebase CLI features
- Select the project that you're using for this workshop
- Accept the defaults
- You'll know you're done when you see
+ Firebase initialization complete!
in your terminal
If you accepted the defaults, then npx firebase init
will have installed the following files:
/functions
: Your Cloud Functions code/public
: The public files to be deployed on Firebase Hosting.firebaserc
: Firebase project definitiondatabase.rules.json
: Realtime Database (aka the RTDB) security rulesfirebase.json
: Firebase project configfirestore.indexes.json
: Firestore indexesfirestore.rules
: Firestore security rulesstorage.rules
: Firebase Storage security rules
We'll start the workshop with these files in their default states. The only project-specific file in here is .firebaserc
, which you can edit to point the firebase-tools
CLI to any Firebase project for which you have access.
Check out .firebaserc.dist
for an example of the file.
Run npx firebase serve
to run a local Firebase Hosting emulator. Open http://localhost:5000/ to see what you have so far.
The page you see is served from the /public
folder. We'll be overwriting these files soon, so don't get attached.
We're using the Parcel app bundler and dev server.
Parcel is mostly automated, so there isn't much to manage yourself. The Parcel commands that we'll use are within the package.json
scripts and can be called with npm run-script serve
and npm run-script build
.
Run npm run-script serve
to get your local dev server running. The terminal will tell you which port on localhost
to use to view your page. The default url is http://localhost:1234/
- Run
npm run-script build
top populate your/public
folder with the build app files. - Run
npx firebase deploy --only hosting
to deploy only Firebase Hosting. - See the "Hosting URL" output by your terminal and follow that URL to test your deploy.
- Add
/__/firebase/init.js
to your hosting URL and open that page to see your Firebase Hosting initialization. Example:https://how-to-firebase-secrets.firebaseapp.com/__/firebase/init.js
.
The /__/firebase/init.js
file is available after your first Firebase Hosting deploy. This allows for a very handy pattern where you merely reference the init.js
file in a script tag on your page and you're automatically initialized wherever you deploy your app on Firebase Hosting.
This is a bit of an advanced tricky, but let's do it!
Open up the Firebase web setup docs and scroll down to the CDN script tags. Copy the entire block and paste it into your src/index.html
file at the bottom of the <head></head>
tag.
We'll use firebase-app.js
, firebase-auth.js
, firebase-firestore.js
and firebase-functions.js
scripts. So go ahead and comment out or delete the other script tags.
Finally, notice the script tag that contains firebase.initializeApp(config)
. Replace the guts of that <script>
tag with the contents of __/firebase/init.js
. You can delete the first line if init.js
... or leave it. It's up to you!
Now add firebase.firestore().settings({ timestampsInSnapshots: true });
to the end of the script tag. You don't need to understand this setting, but omitting it will result in nasty console errors.
The final result should look something like this:
<head>
<!-- ... A BUNCH OF HEAD TAGS ARE ABOVE THIS LINE ALREADY -->
<!-- Firebase App is always required and must be first -->
<script src="https://www.gstatic.com/firebasejs/5.5.6/firebase-app.js"></script>
<!-- Add additional services that you want to use -->
<script src="https://www.gstatic.com/firebasejs/5.5.6/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.5.6/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.5.6/firebase-functions.js"></script>
<!-- Comment out (or don't include) services that you don't want to use -->
<!-- <script src="https://www.gstatic.com/firebasejs/5.5.6/firebase-messaging.js"></script> -->
<!-- <script src="https://www.gstatic.com/firebasejs/5.5.6/firebase-database.js"></script> -->
<!-- <script src="https://www.gstatic.com/firebasejs/5.5.6/firebase-storage.js"></script> -->
<script>
firebase.initializeApp({
"apiKey": "AIzaSyAAZJPjhsSQ9gbyuoWp16fYrm0qtlYCWuo",
"databaseURL": "https://how-to-firebase-secrets.firebaseio.com",
"storageBucket": "how-to-firebase-secrets.appspot.com",
"authDomain": "how-to-firebase-secrets.firebaseapp.com",
"messagingSenderId": "251294611949",
"projectId": "how-to-firebase-secrets"
});
firebase.firestore().settings({ timestampsInSnapshots: true });
</script>
</head>
Check your setup by opening up Chrome DevTools on your dev page and typing firebase.app().options
. This will output the config for your Firebase app. Just make sure that it looks right, and you're good.
Now that we have the Firebase SDK on our page, we can implement auth in just three easy steps.
File: src/components/login.js
We're going to wire up the "Log in with Google" button to Firebase Authentication.
You can find the completed code on the complete
branch of this repo: login.js
File: src/components/app.js
Firebase has a currentUser
object that represents the logged-in user's JWT.
We'll need to sync the currentUser
JWT to our app's state.
You can find the completed code on the complete
branch of this repo: app.js
File: src/components/logged-in.js
Signing out with Firebase Authentication is EASY!
You can find the completed code on the complete
branch of this repo: logged-in.js
File: src/database/add-vault.js
You won't be able to actually add a record until you've completed Task 5. This step succeeds when you get your first Missing or insufficient permissions
error.
You can find the completed code on the complete
branch of this repo: add-vault.js
File: firestore.rules
You created firestore.rules
earlier when calling npx firebase init
. firestore.rules
contains the security rules that you'll need to secure your Firestore database.
Review Firestore security rules to see how flexible they can be.
We're not going to go deep into security rules. That would be a different workshop. So just make sure that your firestore.rules
file looks like this:
service cloud.firestore {
match /databases/{database}/documents {
match /user-owned/{uid}/vaults/{vaultId} {
allow read, write: if request.auth.uid == uid
}
}
}
Then call npx firebase deploy --only firestore
to deploy your Firestore rules.
Now you can add the vaults that you wanted in Task 4!
File: firestore.rules
You created firestore.rules
earlier when calling npx firebase init
. firestore.rules
contains the security rules that you'll need to secure your Firestore database.
Review Firestore security rules to see how flexible they can be.
File: src/database/sync-vault.js
You can find the completed code on the complete
branch of this repo: sync-vault.js
File: src/database/update-vault.js
You can find the completed code on the complete
branch of this repo: update-vault.js
File: src/database/encrypt-vault.js
You can find the completed code on the complete
branch of this repo: encrypt-vault.js
File: functions/package.json
All of the Cloud Functions code lives in this folder, which is a separate NPM project with its own package.json
. We'll need to make sure that Jest is installed, and we'll want to configure a test command.
- Run
cd functions
to begin work on your Cloud Functions. npm install --save-dev jest
to get the Jest test runner.- Add a
"scripts"
attribute topackage.json
and add"test": "jest --watchAll"
to scripts. - Add an
"engines"
attribute with"node": "8"
to make sure that our functions run in the Node v8 runtime instead of the default v6.
The result should look like this:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"test": "jest --watchAll",
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"firebase-admin": "~6.0.0",
"firebase-functions": "^2.1.0"
},
"engines": {
"node": "8"
},
"private": true,
"devDependencies": {
"jest": "^23.6.0"
}
}
The rest of this file is auto-generated by npx firebase init
, so don't worry about it. The other "scripts" commands are useful but outside the scope of this workshop.
- Run
npm test
to verify that Jest gets called. Typeq
to quit.
You can find the completed code on the complete
branch of this repo: package.json
File: functions/src/encrypt.spec.js
- Download a service account for your project: service account download instructions
- Move the service account file to
functions/service-account.json
. This file will be ignored bygit
, so it won't ever make it into your source control. Guard this file carefully, because it grants admin rights to your Firebase project. - Copy the
databaseURL
value from the Firebase Admin SDK screen where you just downloaded your service account. - Update the
databaseURL
values infunctions/environments/environment.js
andfunctions/environments/environment.test.js
. - Run
npm test
to verify that the#encrypt -> setup
tests pass. - Open up
functions/utilities/test-context.js
andfunctions/jest.config.js
and read the comments at the top of each file.
The Cloud Functions runtime provides an initialized Firebase admin
app, but we don't have that in our testing environment. Therefore we need to create our own admin
app with a service account.
We're also setting up basic environment files to add to our context
object. This context
object is arbitrary. We just made it up. But most use cases of Cloud Functions will need environment variables and an admin
app, so we're starting with a robust architecture.
File: functions/encrypt.js
You can find the completed code on the complete
branch of this repo: encrypt.js
File: functions/index.js
- Import
../utilities/prod-context
as your productioncontext
. - Import
Encrypt
from./src/encrypt
- Instantiate an instance of our
encrypt
function usingEncrypt(context)
. - Export a callable Cloud Function to
exports.encrypt
using the docs as a guide. - Run
npm install
andnpx firebase deploy --only functions
to deploy. - Open up the Functions logs in your Firebase Console to confirm that the deploy succeeded.
- Use the running
localhost
version of the app to attempt to encrypt a secret. - Verify that the
encrypted
string was saved to Firestore. - Watch the Functions logs to see each call to
encrypt
succeed.
You can find the completed code on the complete
branch of this repo: index.js
File: src/database/decrypt-vault.js
You can find the completed code on the complete
branch of this repo: decrypt-vault.js
File: functions/src/decrypt.js
You can find the completed code on the complete
branch of this repo: decrypt.js
File: functions/index.js
- Import
Decrypt
from./src/encrypt
- Instantiate an instance of our
decrypt
function usingDecrypt(context)
. - Export a callable Cloud Function to
exports.decrypt
using the docs as a guide. - Run
npm install
andnpx firebase deploy --only functions
to deploy. - Open up the Functions logs in your Firebase Console to confirm that the deploy succeeded.
- Use the running
localhost
version of the app to attempt to encrypt a secret. - That clicking the 'DECRYPT' button in the UI will decrypt a record.
- Watch the Functions logs to see each call to
decrypt
succeed.
You can find the completed code on the complete
branch of this repo: index.js
File: src/database/remove-vault.js
You can find the completed code on the complete
branch of this repo: remove-vault.js