How to use Firebase Firestore as your database
By Flavio Copes
Learn how to use Firebase Firestore as your database: create collections and documents, read and write data from JavaScript, and set up security rules.
I had the need to create a storage for some data for my membership Club, the place where I teach programming.
I wanted my users to be able to manually say “I completed this course”, by clicking a button.
Basically, I wanted to store a particular object for every user.
Setting up Firebase
I decided to use Firebase for this, and in particular the Firestore database they provide.
The free tier for it is generous, with up to 1GB of data stored and 10GB of network transfer per month. Way exceeding my estimates for what I need!
Open the Firebase website at https://firebase.google.com/
Firebase is a Google product, so once you are logged in to Google, you are essentially also logged in to Firebase.
I created a new Firebase project by clicking “Create a project”, I gave it a name, accepted the terms, and that was it. The project overview page opened.
From there I registered a web app: I clicked the “Web” icon next to iOS and Android, and I entered the app name.
Firebase immediately gave me the access keys I needed, along with some sample code to paste in the page.
Right after that, Firebase prompted me to add some security rules for the database.
You can choose 2 things by default: open to everyone, or closed to everyone. I started open to everyone, something they call test mode.
That’s it! I was ready to go, by creating a collection.
What’s a collection? In the Firestore terminology, we can create many different collections and assign documents to each collection.
A document can then contain fields, and other collections.
It’s not much different than other NoSQL databases, like MongoDB.
I highly recommend watching the YouTube playlist on the subject, it’s very well done.
So I added my collection from the Firestore page in the console, which I called users.
I wanted to identify each user using a special string, which I call id.
The frontend code
We’re now getting to the JavaScript part.
A note before the code. When I built this in 2019 I loaded two <script> tags from the Firebase CDN (version 7) and used the old namespaced API, firebase.initializeApp() and firebase.firestore(). That API is gone from the current SDK. Since version 9 the Firebase JS SDK is modular: you import single functions and the bundler drops what you don’t use. The 12.x line is the current one as I update this post (September 2026), so I rewrote the snippets below with the modular API. The logic is exactly what I shipped back then, only the calls changed.
Install the SDK with npm:
npm install firebase
Then import what you need:
import { initializeApp } from 'firebase/app'
import {
getFirestore,
doc,
getDoc,
setDoc,
updateDoc
} from 'firebase/firestore'
If you don’t have a bundler and want to load it from the CDN like I did back then, use a module script. The CDN needs the full version number in the URL (12.19.0 is the latest 12.x release as I write this, check the web setup docs for the current one):
<script type="module">
import { initializeApp } from 'https://www.gstatic.com/firebasejs/12.19.0/firebase-app.js'
import {
getFirestore,
doc,
getDoc,
setDoc,
updateDoc
} from 'https://www.gstatic.com/firebasejs/12.19.0/firebase-firestore.js'
</script>
Module scripts run after the document is parsed, so you don’t need the DOMContentLoaded event listener I used with the old script tags.
Then I added the Firebase configuration:
const firebaseConfig = {
apiKey: "MY-API-KEY",
authDomain: "MY-AUTH-DOMAIN",
projectId: "MY-PROJECT-ID"
}
const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
Now, I created a script to populate the user IDs from a list I had in my backend, using a simple loop:
const list = [/*...my list...*/]
list.forEach(item => {
setDoc(doc(db, 'users', item), {})
})
..and I ran it once, to populate the database. I basically programmatically created a document for each user.
This is very important, because once I created a document, it meant I could restrict the permissions to only update those documents, and disallow adding new ones or deleting them (something we’ll do later)
Ok so now I had some complex logic to identify the user ID and the course ID, which I won’t go into because it’s not related to our task here.
Once I gathered that, I could get a reference to the object:
const id = /* the user ID */
const course = /* the course ID */
const docRef = doc(db, 'membership', id)
Great! Now I could get the document from Firebase:
getDoc(docRef).then(snapshot => {
if (snapshot.exists()) {
const data = snapshot.data()
document.querySelector('button')
.addEventListener('click', () => {
data[course] = true
updateDoc(docRef, data)
})
} else {
//user does not exist..
}
})
My logic was actually much more complicated because I have other moving parts, but you get the idea!
I initialize the document data by calling snapshot.data() and when the button is clicked, which I assume it’s the button to say “I completed the course”, we associated the true boolean value to the club identifier.
Later on, on subsequent loadings of the courses list page, I can initialize the page and assign a class if the course was completed, like this:
for (const [key, value] of Object.entries(data[course])) {
const element = document.querySelector('.course-' + course)
if (element) {
element.classList.add('completed')
}
}
The permissions problem
I started Firebase in test mode, remember? Which makes the database open to everyone - everyone with the access keys, which are public and published in the code shipped to the frontend.
So I had to do one thing: decide the level of permission allowed.
And I stumbled upon a pretty important problem.
Using the Firebase Console, under Rules, we can trim the permission. Initially this was the default rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
I changed the rules to read, update, so one can only update a document, not create new ones:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, update;
}
}
}
But I was not able to prevent people to use the Firebase API, now freely available in the browser, to play around and list all the other documents in the collection - getting access to other people’s files.
While this didn’t handle any sensitive data, it was not possible to ship this code.
Moving the code from frontend to the backend via a custom API
The permission issue was a road blocker.
I thought about removing all the code I had, but eventually I figured out that I could hide all the API access from the browser completely and use a Node.js service to run the Firebase API.
This is also a common method to hide private/secret keys required by services: hide them behind a server you control.
Instead of calling the Firebase from the browser, I created a set of endpoints on my own server, for example:
- POST to
/courseto set a course as completed - POST to
/datato get the data associated to the user
and I access them using the Fetch API:
const options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ id, course, lesson })
}
const url = BASE_URL + '/lesson'
fetch(url, options).catch(err => {
console.error('Request failed', err)
})
All the logic with button clicks and so on remains in the client-side code, of course, I just moved the Firebase logic away.
On the Node.js server side, I installed the official firebase package using npm install firebase. The same modular imports work in Node (the package ships both ESM and CommonJS builds, so require() works too):
import { initializeApp } from 'firebase/app'
import { getFirestore, doc, getDoc, updateDoc } from 'firebase/firestore'
I set up an Express server to use CORS and I initialized Firebase:
const firebaseConfig = {
apiKey: process.env.APIKEY,
authDomain: process.env.AUTHDOMAIN,
projectId: process.env.PROJECTID
}
const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
Then the code is exactly like the one I used in the frontend, except it now fires on HTTP endpoint calls. This is the code that returns a specific document from our collection
const getData = async (id) => {
const snapshot = await getDoc(doc(db, 'membership', id))
const data = snapshot.data()
if (!data) {
console.error('member does not exist')
return
}
return data
}
app.post('/data', cors(), async (req, res) => {
const id = req.body.id
if (id) {
res.json(await getData(id))
return
}
res.end()
})
and here’s the API to set a course as completed:
const setCourseAsCompleted = async (id, course) => {
const docRef = doc(db, 'membership', id)
const snapshot = await getDoc(docRef)
const data = snapshot.data()
if (!data) {
console.error('member does not exist')
return
}
if (!data[course]) {
data[course] = {}
}
data[course]['done'] = true
await updateDoc(docRef, data)
}
app.post('/course', cors(), (req, res) => {
const id = req.body.id
const course = req.body.course
if (id && course) {
setCourseAsCompleted(id, course)
res.end('ok')
return
}
res.end()
})
That’s basically it. There’s some more code required, to handle other logic, but the gist of Firebase is this one I posted. Now I am also able to add a user for my server-side service, and limit all other access to the Firebase API and strengthen the security on it.
Want me to talk about your product? You can sponsor this site.
Related posts about services: