The FullStacksDev Angular and Firebase base template
Part of the curated FullStacksDev Angular and Firebase tech stack. For solo devs and very small teams.
An opinionated full-stack starting point for building a web app, using Angular and Firebase. Aiming to be lean and useful enough so you can hit the ground running and focus on stuff that matters. With enough room for you to extend as you need.
You are free to use and customize this template as you want — build a prototype, an internal tool, a side project, or the next big thing.
Important
This is currently in beta. We're actively working on it and will be making regular updates — expect big changes and improvements until it gets to a stable release. Feel free to give your feedback and suggestions via the Issues tab.
This template gives you an empty app skeleton, working end-to-end, with the following included:
- Simple folder-based monorepo with completely separate frontend (
app
) and backend (firebase
) folders, with a VS Code workspace config to work on both folders at the same time. - A place for shared common code (especially TypeScript types) between frontend and backend (
firebase/common
). - Modern Angular features configured and used throughout (
inject
function, signals, signal inputs, signal outputs, router input bindings, control flow, etc.) - Full Firebase local development and testing using the Firebase Emulator Suite (with local persistence of emulator data).
- Firebase Hosting, Auth, Firestore, Realtime Database, Cloud Functions and Cloud Storage all set up and ready to use.
- Firebase Hosting configured with appropriate caching rules.
- Basic progressive web app (PWA) set up (manifest, service worker, icons, etc) with in-app update notification.
- Static prerendered pages set up, configured to work properly with Firebase Hosting and the PWA configuration.
- State management using NgRx Signals.
- Global auth store and auth guard.
- Login flow using Firebase Auth's Email Link.
- Angular Material and Tailwind CSS, with styling overrides to make them work well together.
- Helpers to inject Firebase services into Angular components, services, etc.
- RxFire used for Observable wrappers for Firebase access in the frontend.
- Frontend logging using consola.
- VS Code, ESLint, Prettier, etc. all set up for a consistent and clean development experience.
- Continuous integration (CI) set up with GitHub Actions (for linting, tests and builds).
- Deploy script to deploy to a "live" Firebase project (which you set up).
- Basic frontend tests, using Jasmine and ng-mocks.
- Firebase security rules test suites using the Firebase Emulator Suite, using Vitest.
For more details see the Architecture and design decisions doc.
Note
Basic familiarity with the technologies and services listed is required to make the best of this tech stack and template.
- Node.js v20.x
- TypeScript v5.5
- Angular v18.1
- Angular Material v18.1
- Tailwind CSS v3.4
- NgRx Signals v18.0
- RxFire
- Firebase
- Git for version control.
- GitHub for hosting the code and running the CI pipeline.
- VS Code as the main editor.
- pnpm as the package manager and script runner.
- Angular CLI
- Firebase CLI
- Firebase Emulator Suite for local Firebase services.
- ESLint for linting.
- Prettier for code formatting.
- Jasmine for Angular unit testing.
- ng-mocks for Angular testing and mocking helpers.
- Vitest for Firebase testing.
Full instructions on how to use this template are outlined below.
Note
This template is designed to be easily reused as part of the GitHub new repo creation flow, using GitHub's template repository feature.
What you need locally before you start:
- Node.js
- It's recommended to use nvm to manage Node.js versions locally. You can then run
nvm use
in the root of the repo to use the correct Node.js version (picked up from the.nvmrc
file). - If installing manually, see the version in the
.nvmrc
file for the correct version to install.
- It's recommended to use nvm to manage Node.js versions locally. You can then run
- pnpm
- You can install this globally with
npm install -g pnpm
.
- You can install this globally with
- Git
- You can also use the GitHub Desktop app (or similar tool) if you prefer.
- VS Code
- You're welcome to use a different editor, but the workspace settings and integrations are set up for VS Code, and work well out of the box.
- Angular CLI
- You can install this globally with
npm install -g @angular/cli
. - If you want, you can also set
pnpm
as the default package manager for Angular CLI by runningng config -g cli.packageManager pnpm
(not necessary for this project, but useful for other projects).
- You can install this globally with
- Firebase CLI
- You can install this globally with
npm install -g firebase-tools
. - You'll also need to log in using
firebase login
.
- You can install this globally with
- Java JDK version 11 or higher
- To run the Firebase Emulator Suite locally.
- Create a new GitHub repo from the base template.
- See the GitHub instructions on how to create a new repo from a template (either via the GitHub UI or the GitHub CLI).
- We recommend not selecting "Include all branches" option when creating the new repo.
- Git clone your new repo locally.
- You can find instructions for this in the GitHub UI after creating the repo.
- You can also use the GitHub Desktop app (or similar tool) if you prefer.
- Open a terminal (we recommend iTerm if on macOS) and open the following 3 panes or tabs:
- One for the root of the project —
cd
into the root of the repo. - One for the
app
folder —cd
into theapp
folder within the repo. - One for the
firebase
folder —cd
into thefirebase
folder within the repo.
- One for the root of the project —
- Run
pnpm install
in both theapp
andfirebase
folders (in the separate terminal tabs) - Run
./edit
in the root to open VS Code with the workspace.- This will open the workspace with the
app
andfirebase
folders as separate groups in the explorer. - The workspace settings come with some extension recommendations. When you first open the workspace you may be alerted to this and given the option to install them (the alert only shows if you don't already have them installed). It's highly recommended to install these extensions to get the best development experience.
- This will open the workspace with the
- Update the
window.title
in theproject.code-workspace
file.- You can open this file to edit by opening the VS Code command palette and searching for (and selecting) "Preferences: Open Workspace Settings (JSON)".
- Update the app name in a couple of places:
app/src/manifest.webmanifest
— update thename
andshort_name
fields.app/src/index.html
— update the<title>
tag.
- Run
pnpm dev
in both theapp
andfirebase
folders (in the separate terminal tabs).- This will start the separate local development servers for the frontend and backend, including the Firebase Emulator Suite.
- After the dev server processes have finished starting up, open a browser tab to
http://localhost:4200
and check that the app is running.- We highly recommend always opening the console / dev tools in the browser to check for any errors or warnings (and to view log messages in dev mode).
- Customize the
HomePageComponent
to add some project specific text and test that it all looks and works okay locally. - Run
pnpm test
in both theapp
andfirebase
folders to check that all existing tests pass.- Note: to exit out of the dev processes (from the previous step) you can press
Ctrl+C
in the terminal. - You should notice that the
HomePageComponent
test fails because you've changed the text. Fix this test to have the updated text and watch as the test suite goes green.
- Note: to exit out of the dev processes (from the previous step) you can press
- Run
pnpm build
in theapp
folder to test the production build.- Note: this is not necessary to do a deploy, but it's useful to check that the production build works as expected.
- Edit or delete the root
README.md
andARCHITECTURE.md
files as you see fit.- You're welcome to keep these in your project and adapt them to your needs. Or feel free to delete them.
- Because VS Code Workspaces don't support adding single files, you can open the files at the root of the repo (like
README.md
,ARCHITECTURE.md
edit
anddeploy
) by runningcode {filename}
in the terminal (in the root folder). This will open the file in the current VS Code window.
- Rename the
LICENSE
file toTEMPLATE_LICENSE
— you will need to keep this in as everything that comes with the base template is under this license and the original copyright notice must be kept (as per the terms of the MIT license).- This does not mean you have to open source your app though — anything you add or substantially change can be licensed as you see fit.
- Note: this does not constituent legal advice, and you should seek proper advice on how to license your own code.
- Set up your live Firebase project and add the configuration to the app:
- See the next section for instructions on how to set up your "live" Firebase project, and then come back here.
- Take the newly created "PWA" config from the step above and update the
firebaseConfig
object in theenvironment.live.ts
file. - Update the project ID in
.firebaserc
to match the ID of your live Firebase project.
- Commit your changes to a branch in your local Git repository and push to GitHub, then open a pull request (PR) from your branch (on GitHub).
- It's good to get in the habit of pushing changes up to GitHub, preferably in a separate branch first, as you work on your app.
- When you make (or update) a pull request (PR) on GitHub, or merge to the
main
branch, the CI pipeline will run the linting, tests and build processes to check that everything is okay. It's good to wait until this is green before you run the deploy locally.
- Run
./deploy
in the root to build and deploy the app and Firebase bits to the live Firebase project.- This script will prompt you for confirmation before running anything.
- It will fail if you haven't already logged into to Firebase using
firebase login
locally. - The first time you run this, Firebase will likely ask you to enable more permissions on the project. Go ahead and do this when prompted.
- Open the link to the live site (from the end of the deploy output) and check that it all works as expected.
- Celebrate! 🎉
- You've set up a new app from the base template and deployed it to a live Firebase project.
- Go to the Firebase Console.
- Click "Add project" and follow the steps to create a new project.
- Tip: call your project "{project name} LIVE" to make it stand out in the list of projects.
- Don't enable Google Analytics.
- Once the project is created, let's configure some settings and services…
- Click the cog icon next to "Project Overview" at the top of the left-hand sidebar and select "Project settings". Then set the "Environment type" to "Production" as this will be your live production Firebase environment.
- To use Firebase Functions you need to be on a paid plan (as Functions are not supported on the "no-cost" / free usage tier). Use the "Upgrade" button at the bottom of the left-hand sidebar (this will need a billing account to be set up and will walk you through setting one up if needed).
- Note: the base template is already set up to use and deploy functions but does not come with any actual functions out of the box, so it can be used on the "no cost" / free usage tier if needed, but only after some modifications — see the relevant "how-to" guide below for removing services like functions etc.
- You'll be able to set a budget alert as part of the upgrade process. Set this to an amount you're comfortable with.
- IMPORTANT: Firebase won't actually stop charging you if you go over the budget amount, so keep a very close eye on usage and make sure you understand your usage patterns (and the costs associated with them).
- Click "Authentication" (under "Build") in the left-hand menu and click the button to set up Authentication.
- Enable the "Email/Password" sign-in method, and make sure to select the "Email link (passwordless sign-in)" option as part of this set-up.
- Click "Firestore" (under "Build") in the left-hand menu and click the button to create a Firestore database.
- Keep the default database ID as suggested.
- Select the physical location for the database.
- IMPORTANT: this location cannot be changed later, and will be used by other Firebase services.
- Start in "production mode".
- Click "Realtime Database" (under "Build") in the left-hand menu and click the button to create a Realtime Database.
- Select the physical location for the database. Note that this may need to be different to the Firestore database location as you have fewer options here.
- Start in "locked mode".
- Click "Storage" (under "Build") in the left-hand menu and click the button to set it up.
- Start in "production mode".
- Note: the location is pre-selected to the one you specified for the Firestore database and can't be changed.
- Go back to the "Project settings" and register a web app entry and copy the necessary config.
- In the "Your apps" section (towards the bottom of the main page of the project settings) click the icon representing web app ("</>") and give the app a name, like "PWA".
- Select the "Also set up Firebase Hosting for this app" checkbox.
- Copy the
firebaseConfig
object shown in the second step — you'll need this to continue setting up the app. - You can ignore all the other set up instructions (as this is already set up for you in the base template) and finish the set-up.
- The Angular app is in the
app
folder. - All static prerendered pages are in the
app/src/app/website
folder. - You can start adding your app specific features in
app/src/app/*
folders and hooking up routing. - Shared code just for the frontend app is in the
app/src/app/shared
folder. - The Firebase config etc. is all in the
firebase
folder. - Firebase backend functions code go in the
firebase/functions
folder. - Shared common code (accessible by both the frontend app and Firebase functions code) is in the
firebase/common
folder- The app can use anything exported here by importing from
@common
. - The Firebase functions code can use relative imports directly to this folder.
- The app can use anything exported here by importing from
- All Firebase security rules (for Firestore, Realtime Database and Storage) are managed in their relevant files in the
firebase
folder, and are deployed with the app to the live Firebase project.- Note that these are locked down by default — you'll need to update them as you start interacting with Firestore, Realtime Database and Storage in your app.
- If you only plan to use these services from the backend functions then you can keep the rules locked down, as the Firebase Admin SDK has full access (i.e. bypasses all security rules).
- You can configure various aspects of Firebase (like a custom domain name, emails, etc.) in the Firebase Console. We provide some basic "how-to" guides below to help with this.
./edit
You'll need to run this separately in both the app
and firebase
folders:
pnpm dev
Note: the dev process for the firebase
folder will also run the Firebase Emulator Suite for you.
You'll need to run this separately in both the app
and firebase
folders:
pnpm lint
You'll need to run this separately in both the app
and firebase
folders:
pnpm test
If you want to test the production build locally. Run this in the app
folder:
pnpm build
This deploys the Angular app and Firebase bits to the "live" Firebase project. Run this in the root folder:
./deploy
For apps built on this base template there are two things you need to do to keep updated to the latest dependencies and base template:
- [Whenever you want] Update the dependencies in the
app
andfirebase
folders. - [When there's a new base template release] Perform specific tasks to match any new or updated base template bits, as specified in release notes, and at your discretion (depending on how far you've deviated from the base template, and what is relevant to your app).
You can update some or all of the dependencies in the app
and firebase
folders using the Angular CLI and pnpm
(our chosen package manager):
For the app
folder:
- First run
pnpm ng update
and follow the instructions.- This will update the Angular specific dependencies (and any other dependencies that support Angular Schematics for updates).
- You can choose to skip this step, especially if there is a major version of Angular with breaking changes (in which case you could choose to wait until the base template has been updated first). If you do skip this step, make sure you don't inadvertently update the version of any Angular etc. packages in the next step.
- Then run
pnpm update --interactive --latest
and follow the instructions.- You can select the packages you want to update.
For the firebase
folder:
- Run
pnpm update --interactive --latest
and follow the instructions.- You can select the packages you want to update.
Make sure to do all this in a branch, test locally and push to GitHub, then open a PR to trigger the continuous integration pipeline (which runs the linter, tests, etc.) and wait until it's green. You'll also want to then do a deploy to your live Firebase project to make sure everything works as expected.
Important
This is like any other dependency update process and can sometimes require changes to your code. Especially with major version updates with breaking changes. We aim to keep the base template up-to-date with the latest versions of dependencies, and if these result in changes to the code we make a new release of the base template with specific instructions on what to do. See the next section for more on this.
Tip
Sometimes, especially with major version updates, you may need to delete the node_modules
folder and pnpm-lock.yaml
file, and then run pnpm install
to rebuild the dependency tree and lockfile. This ensures the very latest dependencies (matched to the versions defined in the relevant package.json
) are used (especially subdependencies).
Tip
When updating the Angular dependencies using the Angular CLI, you may want to use https://github.com/cexbrayat/angular-cli-diff to find any other changes that could be made. Sometimes, the Angular folks will automatically make some of these changes for you (via the ng update
process), but sometimes they won't. The angular-cli-diff
is a really useful community project that can help you find any other relevant changes and make them manually. We usually do this ourselves when updating the base template and will provide instructions on what you could update, as part of our official releases of the template (see the next section).
Note
Once the base template is out of beta and released in a stable version, there are unlikely to be frequent changes, so hopefully you won't need to do this often.
Also, we won't usually make releases for simple dependency updates, or content changes.
Whenever we make a new release of the base template we will provide detailed release notes with specific instructions on what to do to update your app. These will contain specific code changes you can make (some may be optional), and it will all depend on how much you have deviated from the base template, and what is relevant to your app.
Important
We use the TEMPLATE_VERSION
file to track what the current version of the template is. You will have this in your app as well, as a reference for the version of the base template you started with (or last updated to). Use this to find the release notes (possibly multiple) to go through.
And make sure you update this TEMPLATE_VERSION
file as part of your update process.
Go to the releases page to find the release notes for the version(s) you want to update to.
We document the architecture and design decisions in a separate doc.
Feel free to deviate from these as you wish. The base template is designed to be fairly flexible so you can adapt it to your needs.
Tip
If you want to get the full "curated tech stack" experience check out the example apps for this tech stack (more info at the end of this doc).
Here we document some how-to guides to help you get started with this base template.
Note
It's recommended to familiarize yourself with the architecture and design decisions doc before diving into these guides.
Say you want to add a new static prerendered page to the website part of your app, e.g. a "Contact us" page (served at /contact
). Here's how you can do it:
- Generate a new component in the
app/src/app/website
folder:- In the
app
folder, run:pnpm ng generate component website/feature/contact-page
- In the
- Add the new component to the website routes in the
app/src/app/website/website.routes.ts
file.- Follow the example of the
'about'
route. So, add:{ path: 'contact', component: ContactPageComponent },
in thechildren
array.
- Follow the example of the
- Add an entry in the
app/prerendered-routes.txt
file.- Add a new line with
/contact
on it.
- Add a new line with
- (Optionally) Add a nav entry in the
app/src/app/website/website-shell.component.ts
file. - Check everything works locally by running the dev servers and inspecting the app locally.
- Run
pnpm build
in the top-levelapp
folder to test the production build.- Within the top-level
app
folder, you can inspect thedist/app/browser
folder to check the prerendered HTML files.
- Within the top-level
- Add an entry in the
firebase/firebase.json
file (under thehosting.rewrites
key) to serve the relevant prerendered HTML file for this new path.- Follow the example for the
/about
path. - Important: make sure this new entry comes before the
**
catch-all entry.
- Follow the example for the
- Add an exclusion for this new path in the
app/ngsw-config.json
file (under thenavigationUrls
key).- So, add:
"!/contact"
to the end of the array.
- So, add:
- Add content to this new page as you see fit.
- Fix the tests and make sure everything works as expected.
- Commit your changes and push to GitHub, opening a PR so the CI pipeline can run.
- When the CI is green, deploy the app to your live Firebase project.
- By running the
./deploy
script in the root folder, locally.
- By running the
Tip
Technically, you can make any route in the app a prerendered page — it doesn't have to be part of the 'website' feature folder. But it's a good idea to keep all the prerendered pages in one place for consistency.
You'll likely have a main feature section of your app which will be the dynamic user-specific part of the app (together with other dynamic sections like admin, account, etc.)
Tip
By "fully dynamic" we mean that all the logic, UI, etc. runs client-side (in the user's browser). The server (a static host in our case) just serves an empty shell to load the app. This is essentially a single-page app (SPA) architecture.
Say your main feature is a "dashboard", here's how you can add this to the app (as a fully dynamic and lazily-loaded feature):
- Create a new feature folder:
app/src/app/dashboard
.- Within this folder you'll want to organize things within
data
,feature
,ui
andutil
subfolders (see the architecture doc for details).
- Within this folder you'll want to organize things within
- Create a routes file:
app/src/app/dashboard/dashboard.routes.ts
.- This file will define the routes for the feature as you would in any Angular app.
- Register the parent path as a route in the
app/src/app/app.routes.ts
file.- You can follow the example of the
website
routes, setting yourpath
to/dashboard
(or whatever you prefer). - Note the use of
import('./dashboard/dashboard.routes')
here — this tells Angular to perform the lazy loading of the feature based on its routes.
- You can follow the example of the
- You'll likely want a shell component for the feature, which all child routes will be rendered within.
- Run
pnpm ng generate component dashboard/dashboard-shell
to generate this. - And register this as the component for the parent route in the
dashboard.routes.ts
file, with all child routes defined within. - This component will be the parent component for the feature, and will likely contain the main layout (with shared nav, etc.) It must have a
<router-outlet />
in it to render the child routes.
- Run
- You'll likely want to lock down everything within this feature to authenticated users only.
- You can use the
authGuard
provided by the base template, by addingcanMatch: [authGuard('authed')]
to the parent route (the one where you specified the dashboard shell component). - You could instead choose to only secure particular child routes, by adding the
canMatch
property to those routes instead, leaving others open.
- You can use the
- You'll likely want to add a nav entry for this feature on your website pages, so users can navigate to it.
- Add this in
app/src/app/website/website-shell.component.ts
.
- Add this in
- Your feature is ready for development.
The base template comes with a basic PWA set-up, including a manifest file, service worker, etc. Here's how you can configure this for your app:
- Configure the app name, colors, icons, scope, start URL, etc. in:
app/src/manifest.webmanifest
.- This follows the regular PWA manifest spec.
- Also update the
theme-color
meta tag in theindex.html
file to match.
- Update the icons in the
app/src/assets/icons
folder. - Configure the service worker in:
app/ngsw-config.json
.- This is the Angular service worker config file (docs).
- Note that we provide a sensible set-up that works well with the combination of static prerendered pages and dynamic features in the app, so you may not need to change this (and should be careful when doing so).
- Configure the in-app update notification in:
app/src/app/app.component.ts
.
Tip
You are free to use more of the PWA features like background sync, push notifications, etc. as you see fit. The base template is set up to be a useful starting point for a PWA.
You may not need the PWA bits in your app. Here's how you can remove them completely:
- Delete the
app/src/manifest.webmanifest
file. - Remove the mentions of this
manifest.webmanifest
file in theapp/angular.json
file. - Remove the manifest link in the
index.html
file. - Delete any icons you don't need in the
app/src/assets/icons
folder. - Delete the
app/ngsw-config.json
file. - Remove the
"serviceWorker": "ngsw-config.json"
bit in theapp/angular.json
file. - Remove the service worker registration in the
app/src/app/app.config.ts
file. - Update any tests that also do the service worker registration.
- Remove the in-app update notification code in the
app/src/app/app.component.ts
file. - Remove the
@angular/service-worker
dependency from theapp/package.json
file.- You'll need to run
pnpm install
in the top-levelapp
folder to remove this dependency from your localnode_modules
folder and to update thepnpm-lock.yaml
file.
- You'll need to run
You may not need all the Firebase services in your app, especially Firebase Functions which requires a paid plan.
Here's how to remove Firebase Functions from your app:
- If you want to stay fully within the "no-cost" (aka free) tier of Firebase, make sure you don't upgrade the project in the Firebase Console (for the live project).
- Update
firebase/firebase.json
:- Remove the whole
functions
top level config. - Remove the entries for the functions, pubsub and eventarc emulators.
- Remove the whole
- It's easier to just keep the
firebase/functions
andfirebase/common
folders as they are — they won't be used or deployed.
Here's how to remove a service like Firebase Storage from your app (similar steps are applicable to Firestore and Realtime Database):
- Make sure you don't enable the Storage service in the Firebase Console (when setting up your live project).
- Update
firebase/firebase.json
:- Remove the
storage
top level key. - Remove the entry for the storage emulator.
- Remove the
- Delete the security rules file:
firebase/storage.rules
. - Delete the corresponding security rules test suite file:
firebase/test/storage/storage-rules.spec.ts
. - Make sure you don't use the
injectStorage
helper function in the frontend app.
Tip
These steps are especially useful if you're using this base template to build a functional prototype or internal tool, where you don't need all the Firebase services.
Note
This guide assumes you have a domain name registered and have access to the DNS settings for this domain.
You can set up a custom domain for your Firebase app in the Firebase Console.
To do this, follow the latest docs from Firebase. As of writing, these are the high level steps we're aware of:
- Custom domain set-up in the Hosting section
- Custom domain set-up for the email sender in Authentication
- Updated action link for the email templates in Authentication
- Updated “authorized domains” in Authentication
- Updated config in
environment.live.ts
in the app to reflect the new auth domain
Some email templates (e.g. for Authentication) are customizable in the Firebase Console.
It's important that the base template is as lean and broadly useful as possible, whilst maintaining the opinionated approach to the tech stack, architecture and patterns that we are developing as part of the curated tech stacks approach in FullStacksDev.
For this reason, we carefully consider what goes into the base template and err on the side of caution. New capabilities are only added to the base template when they are proven to be broadly useful and fit within the tech stack, by first applying them to real-world projects and the example apps.
If you want to continue learning more about the FullStacksDev Angular and Firebase tech stack you can check out the premium example apps built using this template (requires a premium upgrade):
- Simple example app
- Patterns example app (coming soon)
These apps showcase the capabilities of the tech stack and give you an opinionated, pragmatic and in-depth learning experience. Each come with comprehensive documentation and learning content covering patterns, architecture decisions, design decisions, data models, tech stack capabilities, learnings and more.
You can read more about the purpose and specs of the example apps.
This template is licensed under the MIT License — see the LICENSE file for details.
Important
This template is provided "as is" and with no warranty nor liability. Please make sure you keep a close eye on any costs incurred as you'll be liable for these and anything else that arises from using this template. We recommend you review the code and architecture carefully, adapt it to your needs and thoroughly test your solutions out, before deploying to a live project, paying close attention to Firebase's pricing model.