Skip to content

Instantly share code, notes, and snippets.

@JamieCurnow
Last active Aug 2, 2021
Embed
What would you like to do?
Using Firestore with Typescript - including update helper
/**
* This Gist is part of a medium article - read here:
* https://jamiecurnow.medium.com/using-firestore-with-more-typescript-8058b6a88674
*/
// import firstore (obviously)
import { firestore } from "firebase-admin"
// Here's the helper type for paths:
type PathImpl<T, K extends keyof T> =
K extends string
? T[K] extends Record<string, any>
? T[K] extends ArrayLike<any>
? K | `${K}.${PathImpl<T[K], Exclude<keyof T[K], keyof any[]>>}`
: K | `${K}.${PathImpl<T[K], keyof T[K]>}`
: K
: never
type Path<T> = PathImpl<T, keyof T> | keyof T
type PathValue<T, P extends Path<T>> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Rest extends Path<T[K]>
? PathValue<T[K], Rest>
: never
: never
: P extends keyof T
? T[P]
: never
type UpdateData<T extends object> = Partial<{
[TKey in Path<T>]: PathValue<T, TKey>
}>
// Import or define your types
// import { User } from '~/@types'
interface User {
name: string
email: string
address: {
line1: string
line2: string
postcode: string
verified: false
timeAtAddress: {
days: string
months: string
hours: string
}
}
}
interface Post {
something: boolean
somethingElse: boolean
}
// This helper function pipes your types through a firestore converter
const converter = <T>() => ({
toFirestore: (data: Partial<T>) => data,
fromFirestore: (snap: FirebaseFirestore.QueryDocumentSnapshot) => snap.data() as T
})
// This helper function exposes a 'typed' version of firestore().collection(collectionPath)
// Pass it a collectionPath string as the path to the collection in firestore
// Pass it a type argument representing the 'type' (schema) of the docs in the collection
const dataPoint = <T>(collectionPath: string) => firestore().collection(collectionPath).withConverter(converter<T>())
// Construct a database helper object
const db = {
// list your collections here
users: dataPoint<User>('users'),
userPosts: (userId: string) => dataPoint<Post>(`users/${userId}/posts`)
}
// export your helper
export { db }
export default db
/**
* Some examples of how to use:
*/
const example = async (id: string) => {
// firestore just as you know it, but with types
const userDoc = await db.users.doc(id).get()
const { address } = userDoc.data()
return address.line1
}
const createExample = async (userId: string) => {
await db.userPosts(userId).doc().create({
something: false,
somethingElse: true
})
}
const updateExample = async (id: string) => {
const updates: UpdateData<User> = {
"address.line1": '',
name: ''
}
await db.users.doc(id).update(updates)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment