Home >Web Front-end >JS Tutorial >NgSysV.Creating a simple Svelte Information System with Googles Firestore
This post series is indexed at NgateSystems.com. You'll find a super-useful keyword search facility there too.
Last reviewed: Nov '24
Most webapps exist purely to create and access shared information. Consider, Amazon's https://www.amazon.co.uk/ website. The essential purpose of this system is to let you browse a central collection of product details, place orders and monitor progress on delivery. To make this work, Amazon must:
This post is all about the "database" technology used to achieve these aims.
Warning - this is a long post because database reads and writes in Svelte draw you remorselessly into using SvelteKit's client-server architecture. Previously, your code ran exclusively "client-side" in your web browser. Now you'll also be running code on the local server launched by npm run dev. This has consequences...
I've looked at ways of splitting the post up but they don't work. To make matters worse, the Javascript you'll use contains many new features. So, I'm sorry - you're just going to have to suck it up.
But look on the bright side. Once you're through this, things will start to get easier. Take it slowly. Use chatGPT where you feel I've not explained something clearly. You'll find the bot particularly helpful when you need advice on JavaScript syntax. Relax. This is going to be fun!
There are innumerable ways of storing shared data on the web. This post series uses Google's Firestore system because it suits beginners. It requires minimal setup and fits comfortably within the structure of a Svelte webapp.
You'll need to perform four initial steps:
Firebase is Google's umbrella term for many different services you might use to mount a simple project on the web. The services for a given account are managed via Google's "Firebase console" at https://console.firebase.google.com/. They include both a "Storage" service that enables you to upload files into the Google Cloud and a "Firestore Database" service. A database differs from a file in that it possesses a configurable structure. It enables you to access and update discrete elements of the configured data set.
If you have a Gmail address, you're already covered because this automatically counts as a Google account. If you don't, follow the instructions at Create a Google Account to get one.
Launch the Google Firebase console and log in with your Google Account (noting that, if you're logged into Gmail with this, you're already logged into the Firebase Console as well). Now click the "Create a Project" box to launch the process.
Google will want you to supply a name for your project (I suggest you use the project name you're using in VSCode), and will propose an extension that makes this a unique "Project Identifier" within the Firebase world. So for, example, my version of the "Svelte-dev" project used in this post series is known to Google as "Svelte-dev-afbaf".
As an aside, since the Project Identifier will ultimately form part of the default live URL for your webapp, and since Google lets you edit its initial "uniqueness extension" proposal, you might be tempted to try to change this to something meaningful. However, I suggest you forget this idea. Firstly, you'll find it difficult to pick an identifier that suits both parties. Secondly, in my experience, these "default URLs" aren't ever indexed by Google. A "custom URL" purchased at minimal cost and linked to your default URL when you go live is by far the best way to get a memorable URL.
Now click "Continue" to reveal a "Google Analytics" registration page. You can safely ignore this here as it is relevant only to performance issues on live apps. Use the slider bar to decline it and click the "Create Project" button to continue.
The lights now dim a little as Google registers your project. Finally, once you've clicked one more "Continue" button, you'll find yourself in your project's Firebase Console window. Here's a screenshot of the Firestore tab for a "svelte-dev" project:
It's worth giving yourself a moment to familiarise yourself with this page because it is a little complicated. The basic structure consists of a "tools menu" on the left that determines what gets displayed in the main panel on the right. The problem is that the menu is "adaptive" and maintains a "Project shortcuts" section that remembers where you've been. Consequently, the menu seems to look different every time you open the console! Once you've grasped this feature, however, things work well. Note that the complete set of tools is hidden within the "Build", "Run" and "Analytics" submenus of their parent "Product Categories" menu item. The "Build" set contains everything you need at present.
Before you proceed further, note the following:
Firebase needs to know what your webapp will be called:
Select "Firestore Database" from the "Build" stack in the tools menu to obtain the Firebase console view shown below:
Once you've clicked the "Create Database" button, the console will want you to:
Set your database "Name and Location". "Name" is the identifier for the database and will only be relevant if you plan to create several different databases in your project. Leave this empty, for now, so that Google uses its "default" setting. "Location" specifies where your database will be physically located. The pull-down list of options available here is possibly your first sight of the scale of the Google Cloud service. Its server farms are available right around the globe. You will probably want to select a server close to your location. For example, I generally use "europe-west2 : Heathrow" since I am based in the UK. Pages elsewhere in the Google Cloud console enable you to specify performance and availability characteristics, but you don't need to look at these for now.
Secure your database with "Rules". The screen here offers you a choice between setting initial "production" and "test" "rules". This only makes sense, of course, if you know what "rules" are in the first place - and this isn't the right time for me to explain them. Unless you know better, I'd like you to check the "test mode" option here. Be assured, I'll come back to this later when I talk about "authorisation" (and, oh boy, is there a lot to talk about!).
Once you're through these two stages, the Cloud Firestore page opens in the Firebase Console. What now?
This section aims to answer the following questions:
For our immediate purposes, a database is a set of tables containing rows of values for named data "fields". So, for example, a Customer Order" database might contain
The important thing is that such an arrangement is structured with consistent standards for the naming and formatting of data content
In Firestore, tables are called "collections" and rows within them are called "documents". Documents stored within a collection aren't all required to have the same fields, but field names and content are expected to follow consistent patterns throughout the collection.
An important feature of a Firestore document is that it should have a unique identifier or "key". Sometimes there will be a field such as "Email Address" within each document that you can use to provide a "natural" unique key. If not, Firestore can be asked to create an artificial key automatically.
Database design is probably the most challenging part of system development and, once again, I will duck this because the issues involved will only become clear when you've had some practical experience. However, when you have a moment, you will find it useful to check out the Cloud Firestore Data model page.
In this post, I plan to show you how to create a single products collection in your default Firestore database. This will contain simple documents containing a product number field with a key automatically generated by Firestore.
On the Firestore page on the Firebase console, click the "Start collection" button and enter the name "products" in the "Collection ID" field of the popup that now appears.
Now use the data entry page to create a test products document containing a "productNumber" field with a numeric value of "1" and a "productDetails" field with a text value of "Product 1".
Now sign off the document by first clicking the "Auto Id" button and then "Saving" it Here's what the console should look like now:
If you wanted to add further documents, you would click "add document" at this point, but that's not necessary in this case - you only need a single document to demonstrate your webapp's ability to read it.
You're finished here for now, but note that the console's "panel view" lets you edit or delete the document you've just created. If you've got in a complete mess you can even delete the entire collection and start again.
Here's where things start to get really interesting!
Google provides a library of Javascript functions to let you read and write Firestore documents. Such libraries are referred to as "APIs" (Application Program Interfaces). Have a look at the following code that shows how the firebase/firestore library would be used to read all the documents in svelte-dev's products collection:
import { collection, query, getDocs, orderBy } from "firebase/firestore"; import { initializeApp } from "firebase/app"; import { getFirestore } from "firebase/firestore"; const firebaseConfig = { apiKey: "AIzaSyCE933 ... klfhFdwQg1IF1pWaR1iE", authDomain: "svelte-dev-afbaf.firebaseapp.com", projectId: "svelte-dev-afbaf", storageBucket: "svelte-devt-afbaf.appspot.com", messagingSenderId: "1027 ... 85697", appId: "1:1027546585697:web:27002bf ..... b0f088e820", }; const firebaseApp = initializeApp(firebaseConfig); const db = getFirestore(firebaseApp); const productsCollRef = collection(db, "products"); const productsQuery = query(productsCollRef, orderBy("productNumber", "asc")); const productsSnapshot = await getDocs(productsQuery); let currentProducts = []; productsSnapshot.forEach((product) => { currentProducts.push({productNumber: product.data().productNumber}); }); return { products: currentProducts } // accessed in +page.svelte as data.products
Concentrate on the section that starts const productsCollRef = collection(db, "products");. This uses Firestore API calls to load a sorted copy of all documents within the products collection into the State currentProducts variable.
First, the collection and query functions, drawn from the Firestore Client API library, are used to point Firebase at the products collection and define a query to run on it. Then the query is launched by a getDocs API call.
I won't attempt to describe the mechanics of this sequence of Firestore API calls. Treat these as a piece of "boiler-plate code" - code - the sort of thing that you write once and, ever afterwards, simply copy. Since you'll find you need a whole library of templates to cover the full array of Firestore "read", "update" and "delete" operations, you might find it useful to look at Post 10.1 - Firestore CRUD command templates. If you'd like to check out Google's own description of the API, you'll find links to these at the end of Post 10.1.
"CRUD" here is short for "create", "read", "update" and "delete".
The getDocs result is returned as an array of documents conventionally called a "snapshot". However, note that the getDocs function call is preceded by an await keyword.
The await keyword is needed here because, by default in Javascript, instructions referencing external data sources that may take an unpredictable time to complete are handled asynchronously. The "await" keyword essentially (though this is a gross simplification) enables you to override this arrangement. When you have more time, you might find it useful to have a look at A simple guide to the Javascript fetch() API and the "await" keyword
But right now, returning to our code snippet above, look at the section starting with the const firebaseConfig statement.
The firebaseConfig declaration initialises an object containing the configuration details needed to connect your web app to your specific Firebase project. It includes various keys and identifiers that Firebase uses to locate and authenticate your app. You'll find the settings for your particular webapp in "Project Overview/Project settings" on the Firebase console. The firebaseConfig settings in the code samples below have been "obfuscated" because they are unique to my project and aren't to be passed around lightly (more about this shortly). When trying the sample code below, you'll need to copy in the firebaseConfig settings from your own project.
With firebaseConfig initialised, the webapp can initialise the db variable required by the query's const productsCollRef = collection(db, "products"); statement:
import { collection, query, getDocs, orderBy } from "firebase/firestore"; import { initializeApp } from "firebase/app"; import { getFirestore } from "firebase/firestore"; const firebaseConfig = { apiKey: "AIzaSyCE933 ... klfhFdwQg1IF1pWaR1iE", authDomain: "svelte-dev-afbaf.firebaseapp.com", projectId: "svelte-dev-afbaf", storageBucket: "svelte-devt-afbaf.appspot.com", messagingSenderId: "1027 ... 85697", appId: "1:1027546585697:web:27002bf ..... b0f088e820", }; const firebaseApp = initializeApp(firebaseConfig); const db = getFirestore(firebaseApp); const productsCollRef = collection(db, "products"); const productsQuery = query(productsCollRef, orderBy("productNumber", "asc")); const productsSnapshot = await getDocs(productsQuery); let currentProducts = []; productsSnapshot.forEach((product) => { currentProducts.push({productNumber: product.data().productNumber}); }); return { products: currentProducts } // accessed in +page.svelte as data.products
Finally, you may be wondering where these API functions come from. The answer is that they are imported from locations in your project by the three statements at the top of the code block:
const firebaseApp = initializeApp(firebaseConfig); const db = getFirestore(firebaseApp);
Here "modular libraries" are being accessed to supply functions for your code. Named functions such as collection are extracted from a parent module to fulfil the references required later in the code.
But then this leads to the question "And how do modular libraries get into my project in the first place?" The answer, of course, is that you have to put them there, and the tool you use to do this is faithful old npm.
Back in a VSCode svelte-test terminal session (terminate the dev server if necessary with a couple of ctrl-C keystrokes) and run the following instruction'
import { collection, query, getDocs, orderBy } from "firebase/firestore"; import { initializeApp } from "firebase/app"; import { getFirestore } from "firebase/firestore"; const firebaseConfig = { apiKey: "AIzaSyCE933 ... klfhFdwQg1IF1pWaR1iE", authDomain: "svelte-dev-afbaf.firebaseapp.com", projectId: "svelte-dev-afbaf", storageBucket: "svelte-devt-afbaf.appspot.com", messagingSenderId: "1027 ... 85697", appId: "1:1027546585697:web:27002bf ..... b0f088e820", }; const firebaseApp = initializeApp(firebaseConfig); const db = getFirestore(firebaseApp); const productsCollRef = collection(db, "products"); const productsQuery = query(productsCollRef, orderBy("productNumber", "asc")); const productsSnapshot = await getDocs(productsQuery); let currentProducts = []; productsSnapshot.forEach((product) => { currentProducts.push({productNumber: product.data().productNumber}); }); return { products: currentProducts } // accessed in +page.svelte as data.products
After a minute or two (the installation involves a sizeable download), you'll be poised to run code that downloads a Firestore database collection. But, you still don't know how to embed this in a Svelte webapp. So, on to the next question...
This has been a long haul but, hang in there, you're nearly finished.
Currently, in the