The Firestore Emulator Requests Monitor allows you to see requests to your local Firestore Emulator in real-time, and drill down to the details of each request, such as method, path, and Firebase Security Rules evaluation. You can access the Requests Monitor right now from the Emulator UI if you have the latest Firebase CLI running. (If not, it's never too late to upgrade!)
Requests Monitor helps you understand your request traffic in detail, and puts Firebase Security Rules front and center (just like security should be in your production app). Ever wonder what collections are pulled in for the amazing pizza tracker feature in your app? Forgot about how that cute button is backed by 400 lines of code changes Firestore? Worried about changes to security rules breaking production apps? The Requests Monitor has answers to all of these questions, plus more!
First, start the Emulator Suite, then navigate to the Firestore tab in the Emulator UI and you'll be greeted with two views: the default "Data" view is the familiar data viewer and editor you know and love, and the "Requests" view is just a click away.
Each client request to the Firestore Emulator will be added to the table as a new row. For example, if you connect your app to the Firestore Emulator and create a new document, it will show as a CREATE request on the table in real time. This works regardless of which platform your app is on -- be it Android, iOS, web, or anything else. And if you ever forget to open this page before you make those requests, don't worry -- we've got you covered. The last few requests will be kept for you to review when you navigate to the Requests page.
CREATE
The Requests View is a great help in developing security rules. A checkmark indicates if the request has been allowed by your security rules; denials and errors are also displayed. If you follow best practices and keep your rules locked down as much as possible, you'll certainly see some denials when you develop new features, and those are the perfect opportunity to learn! To take the guesswork out of updating security rules, just click on any request to see the evaluation details view.
On this page, you'll see your current local security rules on the left, and some information about the specific request on the right. Statements that allow, deny, or error will be highlighted. You may also run into situations where the request is not yet covered by any of the allow statements and is thus denied by default. Ever wonder why a request is denied where it should be allowed, or the reverse? The panel on the right can help. You can see the existing resource, the would-be version of the document (i.e. request.resource) and other important fields in the request. If your goal is to modify the security rules to align with changes to your app, you can also use those fields as inspiration for new conditions that your rules should gate on.
resource
request.resource
While we're at it, you'll notice the security rules on the evaluation details page are not modifiable -- that's because they are snapshots at the time when the request happened. To make rules changes, just directly modify the firebase.rules file with your favorite editor or IDE and the Firebase CLI will automatically apply the changes to the Firestore Emulator. And if you make another request, it will show up in the table view as a different row with new rules and results. Sometimes, it may be helpful to compare the old and new rules and see differences in how they were evaluated.
firebase.rules
For those of you who are familiar with the Security Rules Playground in Firebase Console, you may miss the simulated requests feature here. But in the emulator world, there's no need to guess what a request should look like -- you can just simply make that request from your app using the Firestore SDK in your favorite programming language. The Request Monitor always shows you faithfully how that request is represented in Security Rules and the actual decision of your rules. Any client request is fair game -- even lists and queries that are hard to simulate in production. We think you will eventually get used to it and love this new interactive development workflow as much as we do.
While you enjoy the new Requests Monitor, just keep in mind that only client requests are shown in the table. Admin requests bypass any security rules and therefore don't appear in the list. Similarly, if your rules depend on other documents in the Firestore (e.g. get(...) or exists(...)), only the main request is shown but not the dependent fetches, even though those dependent fetches count towards your quotas and billing in production. Just remember emulated benchmarks are not an indicator of production in terms of performance or cost estimation.
get(...)
exists(...)
We've already heard some developers asking if this feature will also be available in Firebase Console. While we cannot say for sure, recording production requests will certainly create a huge challenge to your app's Firestore performance and security. Aaaand, well, you know, production is not the best place to test out changes, especially security-related changes. We recommend developing and testing locally before rolling out to production, and the Firebase Emulator Suite is always seeking ways to help.
With the Firestore Emulator and Requests Monitor, you can see your prototyping path more clearly. In fact, you have a better view into unit and integration testing as well: just make sure to keep the Monitor open and run your tests against the same (emulated) Project ID that your app connects to. You only need to deploy when you feel comfortable with your changes.
Feel free to play around with the Firestore Emulator Requests Monitor, and let us know what you think!
Are you looking for something new to learn this year? Then let me suggest TypeScript for development with Cloud Functions!
Not long ago, the Cloud Functions for Firebase team released an update to the Firebase CLI that makes it easy for you to write your functions in TypeScript, rather than JavaScript. The Firebase team encourages you to consider switching to TypeScript, but I can imagine you might be reluctant to learn a new language, especially if you're already comfortable with JavaScript. The great news is that TypeScript offers you a bunch of benefits that are easy to start using today.
The language itself offers a bunch of features that make your code easier to read and write, and less error-prone:
There's also async/await from ECMAScript 2017, which helps you write asynchronous code more easily. The primary challenge with asynchronous code is management of promises, which is crucial to get right when writing Cloud Functions, but difficult to master. TypeScript makes that much easier.
But what I really want to dive into here is an excellent tool called TSLint that can check your TypeScript code for potential problems before you deploy to Cloud Functions. The Firebase CLI will prompt you to configure TSLint when you initialize Functions in a project using TypeScript, and we strongly recommend that you opt into it.
When you opt into TypeScript and TSLint in your Firebase project structure, the Firebase CLI will add and modify some project files when you run firebase init. First let's take a look at functions/package.json. In there, you'll see the following key:
firebase init
functions/package.json
"devDependencies": { "tslint": "^5.8.0", "typescript": "^2.6.2" },
This is where node pulls in TypeScript and TSLint for development. Notice that there are "devDependencies" that are separate from the normal "dependencies" that you use in your function code. devDependencies are only stored on your machine, and are made available as tools for development. They are not deployed with your code. Also in that file, notice there are two script definitions:
"scripts": { "lint": "./node_modules/.bin/tslint -p tslint.json", "build": "./node_modules/.bin/tsc", ... }
These give you the ability to run npm run lint and npm run build on the command line from your functions directory. The first check your code with TSLint, and the second will build it with the TypeScript compiler.
npm run lint
npm run build
The next file to look at is firebase.json. This now has a predeploy hook that runs TSLint against your code, so if it has an error, the deploy will fail:
firebase.json
{ "functions": { "predeploy": [ "npm --prefix $RESOURCE_DIR run lint", "npm --prefix $RESOURCE_DIR run build" ] } }
The next file is functions/tsconfig.json. This contains the configuration for the TypeScript compiler.
functions/tsconfig.json
{ "compilerOptions": { "lib": ["es6"], "module": "commonjs", "noImplicitReturns": true, "outDir": "lib", "sourceMap": true, "target": "es6" }, "compileOnSave": true, "include": [ "src" ] }
I won't cover all the settings here, but it's important to note that the compiler will look for source TypeScript files under functions/src, and compile them to JavaScript into functions/lib, with ECMAScript 6 compatibility, as required by Cloud Functions. Cloud Functions currently runs Node 6, which means it natively understand ES6 code.
functions/src
functions/lib
Lastly, take a brief look at functions/tslint.json. This lists all the rules that are observed when TSLint checks your code. You can add new rules here, or remove the rules you don't want. I suggest leaving everything there, as the list of rules was curated by the Firebase team to help with writing functions. The "strict errors" whose values are set to true will cause compile time errors if violated. The warnings below that will just complain about possible issues, and it's the team's opinion that you should look into resolving them.
functions/tslint.json
true
Alrightey. Take a look at this function that populates a property called createdAt when a user node is created in Realtime Database. Do you see what's wrong here?
createdAt
export const onUserCreate = functions.database.ref('/users/{uid}').onCreate(event => { event.data.ref.update({ createdAt: Date.now() }) })
TSLint sees the issue, and its one of the most common mistakes made when writing functions. If you run npm run build on this code, you'll see this error in its output:
Promises must be handled appropriately
This error is triggered by the rule no-floating-promises. TSLint sees that event.data.ref.update returns a promise, but nothing is being done with it. The correct way to deal with promises for database triggers is to return it:
event.data.ref.update
export const onUserCreate = functions.database.ref('/users/{uid}').onCreate(event => { return event.data.ref.update({ createdAt: Date.now() }) })
If you're using async/await, you can also declare the function async and use await to return it:
async
await
export const onUserCreate = functions.database.ref('/users/{uid}').onCreate(async event => { await event.data.ref.update({ createdAt: Date.now() }) })
Proper handling of promises is an absolute requirement when dealing with Cloud Functions, and TSLint will point out where you're not doing so.
I do a lot of Android development, and I'm accustomed to Android Studio linting my code as I type it. This is valuable because I get instant feedback about where things could go wrong. On the Firebase team, a bunch of us use VSCode for editing TypeScript, and it will use TSLint to give you instant feedback. The TSLint extension is easy to install.
Go to View -> Extensions. Type "TSLint" into the search box on the left. Find the TSLint extension, and click the Install button. After it's installed, click the Reload button, and now your TypeScript will be marked with possible mistakes.
Here's an HTTPS function that fetches a user document from Firestore using the Admin SDK. It looks OK and works OK:
export const getUser = functions.https.onRequest((req, res) => { var uid = req.params.uid const doc = admin.firestore().doc(`users/${uid}`) doc.get().then(doc => { res.send(doc.data()) }).catch(error => { res.status(500).send(error) }) })
But when viewed in VSCode with TSLint markers, you'll see that it violates some best practices:
Notice the squiggly lines under var, uid, and doc. If you hover the mouse over the marked code, you'll see a message from TSLint:
var
uid
doc
That's TSLint using the prefer-const rule to tell you that it's better to use const instead of var to declare values that don't change. It's good for the readability of your code, and also prevents someone from making an accidental change to it later on.
const
The squiggly line under doc is TSLint using the no-shadowed-variable rule to point out that the doc parameter in the function passed to then() (a DeltaDocumentSnapshot object) is masking the doc constant in the outer scope (a DocumentReference object), making the latter completely unavailable for use. While this is not really a bug here, it can lead to confusion about which doc instance is being referred to at any given moment. Renaming either instance resolves the issue.
then()
Here's a lint-free version of the same function:
export const getUser = functions.https.onRequest((req, res) => { const uid = req.params.uid const doc = admin.firestore().doc(`users/${uid}`) doc.get().then(snapshot => { res.send(snapshot.data()) }).catch(error => { res.status(500).send(error) }) })
This makes TSLint happy, which should make you happy, because you're writing better code!
If you haven't started learning TypeScript yet, 2018 is a fine time to begin. It's easy to get started, because TypeScript is a strict superset of JavaScript, meaning that all your existing JavaScript code is already valid TypeScript. So you can simply rename your .js files to .ts and drop them into functions/src. Then, you can start using TypeScript language features as you wish. Let us know how it goes and shout out to @Firebase on Twitter.