babichjacob / sapper-firebase-typescript-graphql-tailwindcss-actions-template

A template that includes Sapper for Svelte, Firebase functions and hosting, TypeScript and TypeGraphQL, Tailwind CSS, ESLint, and automatic building and deployment with GitHub Actions
MIT License
106 stars 14 forks source link

How am I supposed to access my Firestore from the server side on Firebase? #16

Closed Evertt closed 3 years ago

Evertt commented 4 years ago

For example, if I want to preload some data from Firestore, how would I access my firestore? Are the credentials stored in some secret environment variable? Since the whole thing is hosted at firebase, I would expect that they'd know that if someone tries to access the Firestore from within a firebase function, that they should be allowed, right? Is that how it works?

edit

The past few weeks I've also been working on another hobby project and there I was able to make Firestore work on both server-side and client-side, both locally and when deploying to Vercel. The magic is in this file.

Here's an explanation of how that code works:

  1. I check whether the code is currently being run in Node or in the browser by checking process.browser
  2. If we're in the browser then I know that Firebase was loaded globally, because I inserted a <script> tag in template.html for that.
    • In this case I can just hard-code the credentials in the code, because the client-side firebase's credentials would always be discoverable anyway when anyone takes a look in the code with their browser's developer tools.
  3. If we're in Node then I require the firebase-admin package, which has a nearly identical API to the browser-version of firebase.
    • In this case I can't hard-code the credentials, because the credentials for firebase-admin are much more powerful and therefor sensitive data. So instead I rely on environment variables to give me the credentials. I've set those up in both my GitHub repository's secrets and in Vercel's secret environment variables. (See here, here and here)
  4. I initialize the Firebase app and Firestore using the correct credentials and I export those variables from the file.

The nice thing about having it this way is that I can just require store/firebase.js wherever I want and it doesn't matter if the code is run server-side or client-side, I will always get the correct version of Firebase.

Now I tried using the same approach with your template, but I can't get it to work. The environment variables don't seem to be available despite having them set up in my repo's secrets. Maybe the problem is that I haven't set any env vars up in firebase-functions yet, because I don't yet understand how that works.

edit2

If we can get this to work, it might be nice to include it in the template, don't you think? So that people have a super easy way to access their Firestore from both serverside and clientside.

And just to show off a bit, I also wrote some code that wraps a Firestore Query in a Svelte Readable store which subscribes to live snapshots of that query. The result is that I can write code like this in my components:

<script>
    import { collection } from "../store"

    export let roomReference // this will contain a Firestore document reference

    // messages is now a svelte readable store
    // and at the same time it's also a Firestore Query,
    // which means you can keep chaining on more query constraints.
    const messages = collection("messages")
        .where("room", "==", roomReference)
        .orderBy("created")
</script>

{#each $messages as message (message.id)}
    <div>
        <small>{message.author}</small>

        <p>{message.body}</small>
    </div>
{/each}

Isn't that beautiful syntactic sugar? AND it updates live whenever the data in Firestore changes.

babichjacob commented 4 years ago

So far, I've only ever used Firestore server-side so it's only accessible through the GraphQL server.

In production (i.e. when it's actually running in a Cloud Function for Firebase), I don't need to specify any credentials because they're automatically set:

import { credential, firestore, initializeApp } from "firebase-admin";

initializeApp({
    credential: credential.applicationDefault(),
});

When I run the development server locally (or even npm run prod then npm run start to run a production server locally, as described in the README), that same code will find my credentials from a GOOGLE_APPLICATION_CREDENTIALS environment variable with the value being the path to a service account secret that has the admin credentials.

Evertt commented 4 years ago

Thanks for that info, that's what I was looking for! :-D

Just out of curiosity, why would you use GraphQL as a middleman between the frontend and Firestore? I don't see any benefit in that.

babichjacob commented 4 years ago

Because I wanted an API that other people / sites can use.