Using Firestore with TypeScript in the v9 SDK
A nice little coding design pattern that will boost your Firestore productivity and make your code safe with TypeScript
TL;DR: Just check out this repo for an example of the code:
If you’re looking for a way to add TypeScript to the V8 firebase SDK then check out my other articles:
Let’s Code!
So I’ve been playing with the new Firebase/Firestore V9 SDK recently and have come up with a very simple and very minimal way to make it play nice with TypeScript.
The only thing we’re going to do is to create an abstraction that maps all of the possible collections in our DB, and returns a typed Firestore collection reference. Since I’m a fan of Vue and the ‘composables’ coding paradigm, I’m going to put this abstraction in a directory named composables
and in a file named useDb.ts
. The first thing we’ll do in that file is to initialize the firebase app, and get a reference to the Firestore db:
// /composables/useDb.ts// Get the imports
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'// Init the firebase app
export const firebaseApp = initializeApp({
apiKey: '### FIREBASE API KEY ###',
authDomain: '### FIREBASE AUTH DOMAIN ###',
projectId: '### CLOUD FIRESTORE PROJECT ID ###'
})// Export firestore incase we need to access it directly
export const firestore = getFirestore()
Next, we’re going to make a little function that will take a collection name and spit out a Firestore collection reference. This reference can be passed to getDoc
or getDocs
etc for read/writing data:
// /composables/useDb.ts// Get the imports
import { initializeApp } from 'firebase/app'
import { getFirestore, collection } from 'firebase/firestore'// Init the firebase app
export const firebaseApp = initializeApp({
apiKey: '### FIREBASE API KEY ###',
authDomain: '### FIREBASE AUTH DOMAIN ###',
projectId: '### CLOUD FIRESTORE PROJECT ID ###'
})// Export firestore incase we need to access it directly
export const firestore = getFirestore()// This is just a helper to add the type to the db responses
const createCollection = (collectionName: string) => {
return collection(firestore, collectionName)
}
Right now that function really doesn’t do much and is kind of pointless, but here comes the magic... We’re going to make that function a type generic and add the correct type to the collection reference that it returns:
// Get the imports
import { initializeApp } from 'firebase/app'
import { getFirestore, CollectionReference, collection, DocumentData } from 'firebase/firestore'// Init the firebase app
export const firebaseApp = initializeApp({
apiKey: '### FIREBASE API KEY ###',
authDomain: '### FIREBASE AUTH DOMAIN ###',
projectId: '### CLOUD FIRESTORE PROJECT ID ###'
})// Export firestore incase we need to access it directly
export const firestore = getFirestore()// This is just a helper to add the type to the db responses
const createCollection = <T = DocumentData>(collectionName: string) => {
return collection(firestore, collectionName) as CollectionReference<T>
}
Pretty simple. Pretty concise.
Now we’ll import all of our Types from wherever they are defined, and export a bunch of constants that run the createCollection
function with the correct collection path and type:
// Get the imports
import { initializeApp } from 'firebase/app'
import { getFirestore, CollectionReference, collection, DocumentData } from 'firebase/firestore'// Init the firebase app
export const firebaseApp = initializeApp({
apiKey: '### FIREBASE API KEY ###',
authDomain: '### FIREBASE AUTH DOMAIN ###',
projectId: '### CLOUD FIRESTORE PROJECT ID ###'
})// Export firestore incase we need to access it directly
export const firestore = getFirestore()// This is just a helper to add the type to the db responses
const createCollection = <T = DocumentData>(collectionName: string) => {
return collection(firestore, collectionName) as CollectionReference<T>
}// Import all your model types
import { User } from 'src/types/User'
import { Author } from 'src/types/Author'
import { Book } from 'src/types/Book'// export all your collections
export const usersCol = createCollection<User>('users')
export const authorsCol = createCollection<Author>('authors')
export const booksCol = createCollection<Book>('books')
You now just have one list to maintain of collection names and their types!
So how do I use this?
Well, it’s as simple as it looks! Say we wanted to add a new user to the db, we would just import usersCol
from useDb
and use that as the first argument to Firestore’s setDoc
function:
// /someOtherFile.tsimport { doc, setDoc } from '@firebase/firestore'
import { usersCol } from './composables/useDb'export const setJamiesUser = async () => {
const userRef = doc(usersCol, 'user_12345')
await setDoc(userRef, {
age: 30,
firstName: 'Jamie',
lastName: 'Curnow'
})
}
If you try this out, you’ll see that we get type hints and compiler errors if we give setDoc
the wrong type of data for the user!
Another example: getting data… Let’s say we want to get all of the documents from the books
collection in the db and we want to console.log()
each of the book’s titles:
// /anotherFile.tsimport { getDocs } from '@firebase/firestore'
import { booksCol } from './composables/useDb'export const logBookTitles = async () => {
const bookDocs = await getDocs(booksCol)
bookDocs.docs.forEach((bookDoc) => {
const book = bookDoc.data()
console.log(book.title)
})
}
Here typescript knows that bookDoc.data()
returns an object of type Book
and lets us log the title of the book. 🥳
Using it this way will also work for deeply nested keys when doing updates to documents which is absolutely awesome:
import { doc, updateDoc } from '@firebase/firestore'
import { booksCol } from './composables/useDb'export const updateBook = async () => {
const bookDocRef = doc(booksCol, 'book_12345')
await updateDoc(bookDocRef, {
'meta.created': new Date().toISOString()
})
}
In that example meta.created
is typed! Amazing!

Wrapping up
This is such a nice feature of the new V9 Firestore SDK and the Firestore team have done a fantastic job of building it 🙌.
If you want to see a minimal repo that implements this pattern then checkout and star:
Don’t forget to give this story a little clap if you found it useful — your claps keep me posting content and give me a little boost to write more each time I see one come through!
Support your creators!
If you found this story useful and would like me to keep writing useful content, please consider supporting me on Patreon 🤗
More content at plainenglish.io