If you have used Firebase in the past, you may have come across thissuperbase, an open source Firebase alternative that is currently in beta but is getting a lot of great feedback.
In this tutorial, we'll create a simple Ionic Angular app that will use Supabase as our cloud backend. We will be able to integrate authentication, all CRUD functions into the Supabase database instantly and also a way to see changes to our database in real time.
Please note that this tutorial used a beta version of Supabase, so it is likely that there will be many changes in the future. But it's a starting point to see if it's a better fit for your needs than Firebase.
We won't go into all the technical details and features of Supabase today, but later in a video. Let's see how fast we can build our app!
Supabase project setup
To get started, simply create an account (or sign in).superbaseand then create a new organization where we can add a project.
Choose any name for your project and the region of the database next to your users and set a password for the database.
Now we have to wait a few minutes for the database to be ready. This is a bit different from Firebase because the database used by Supabase is aPostgres-SQL-Datenbankinstead of NoSQL in Firebase.
So you need to create them too.structure of your databasein advance. I'm no SQL expert, but luckily Supabase has put together some scripts so that people like us can quickly start testing everything.
just open thesqlTab inside the menu and you will see a bunch of scripts. we will choose themchores, or you can just paste the following into the SQL editor there:
Create table todo (bigint id is generated as identity primary key by default, user_id uuid points to auth.users not null, task text check (char_length(task) > 3), is_complete boolean default false, insert_at timestamp with time zone default timezone( 'utc' ::text, now()) not null);Modify Table Tasks Enable Row Level Security;Create Policy "People can create tasks". in all to insert with check(auth.uid() = user_id); Create policy "People can view their own tod tod." on all to update with (auth.uid() = user_id); Create policy "People can see their own tod" own Remove all." on all to remove with (auth.uid() = user_id);
This will create and add a new table with columns to create new tasksRow level securitywith some guidelines so that users can only see the data they created themselves.
We can talk more about backing up your Supabase application in the future, but let's leave it at that for now.
Ionic App Settings
Now we can start with our Ionic app and after setting up the project, we are going to install it.Supabase JS client packageand generate some additional pages for our Ionic app:
ionic start devdacticSupa en blanco --type=angular --capacitorcd ./devdacticSupanpm install @supabase/supabase-jsionic g page pages/loginionic g page pages/listionic g service services/supabaseionic g guard guards/auth --implements CanLoad
To connect our Ionic app to Supabase we need two values that you can find in theIdeasTabs on Supabase and further down onAPIForbidden.
now you can copyURLand those listed belowanonymous key, which is used to connect to Supabase until a user logs in. We can put these two values directly into oursrc/environments/environment.ts:
Export untilSurroundings= {Production: INCORRECT,superbaseUrl: 'SUAURL',superbaseKey: 'TU CLAVE ANON'};
Since we've already generated some pages and a protector, we can quickly do our routing ahead of time in thesrc/app/app-routing.módulo.tswith our new pages and the protection of our internal page:
matter {per module} outside '@angular/núcleo';matter {Preload all modules,router module,routes} outside '@angular/router';matter {AuthGuardNombre} outside './guardias/auth.guardia';untilstretch:routes= [ {Absent: 'Record', loading children: () => matter('./pages/login/login.modul').So(METRO=>METRO.LoginPageModule) }, {Absent: 'List', loading children: () => matter('./páginas/lista/lista.módulo').So(METRO=>METRO.ListPageModule),can load: [AuthGuardNombre] }, {Absent: '',redirect to: 'Record',route match: 'complete' },];@per module({imported: [router module.forroot(stretch, {preload strategy:Preload all modules}) ],Export: [router module]})Export Classroom Application routing module { }
Now let's work with some data from Supabase!
Creating a Supabase CRUD service
As usual, we'll build all the necessary logic for our application inside a service. To connect to Supabase, simply callcreateCustomer()
working from scratch with the information that we add to our environment.
Automatic handling of user save data didn't work 100% for me or in general needs improvement. Loading the saved token from web storage (possibly using Ionic Storage in the future as another storage engine) is synchronous but does not wait for the data it contains.load user()
it didn't work very well.
You can also listen for authentication changes.onAuthStateChange
be notified when the user logs in or logs out. We will pass this information on to ourBehaviorIssueso that we can then use it as an observable for our watch.
Signing up and signing up is almost the same, it just requires a simple function from the Supabase JS package. What I didn't think was great was that even infailed registration keeps promise, as expectedthrow an error.
So I wrapped the calls in another promise and called bothdecide on
odecline
depending on the result of Supabase what you are doingeasier for our controllerto handle the result in the next step with no logic of its own!
start now withsrc/app/services/subase.service.tsand change to:
matter {injectable} outside '@angular/núcleo';matter {router} outside '@angular/router';matter {create customers,superbase client,of the user} outside "@supabase/supabase-js";matter {BehaviorIssue,observable} outside 'rxjs';matter {Surroundings} outside '../../umgebungen/umgebung';until ALL_DB = 'for';Export Interface No {I WAS GOING: Number;inserted_in: line;It's complete: boolesch;Task: line;Benutzer Identification: line;}@injectable({Supplied in: 'Fuente'})Export Classroom SuperbaseService { Private_No:BehaviorIssue<No[]> = nuevo BehaviorIssue([]); Private_current user:BehaviorIssue<any> = nuevo BehaviorIssue(Null); Privatesuperbase:superbase client; constructor(Privaterouter:router) { O.superbase= create customers(Surroundings.superbaseUrl,Surroundings.superbaseKey, {autoRefreshToken: TRUE,persist session: TRUE }); // Try to restore our user session O.load user(); O.superbase.authentication.onAuthStateChange((Cair,meeting) => { Se (Cair== 'REGISTERED') { O._current user.next(meeting.of the user); O.StoreAll(); O.handleAllChanged(); } Anders { O._current user.next(INCORRECT); } }); } asynchronous load user() { untilof the user= wait O.superbase.authentication.of the user(); Se (of the user) { O._current user.next(of the user); } Anders { O._current user.next(INCORRECT); } } to receive current user():observable<of the user> { go back O._current user.as observable(); } asynchronous Record(credentials: {Email,password}) { go back nuevo Promise(asynchronous (decide on,decline) => { until {Error,Data} = wait O.superbase.authentication.Record(credentials) Se (Error) { decline(Error); } Anders { decide on(Data); } }); } record(credentials: {Email,password}) { go back nuevo Promise(asynchronous (decide on,decline) => { until {Error,Data} = wait O.superbase.authentication.record(credentials) Se (Error) { decline(Error); } Anders { decide on(Data); } }); } deliver() { O.superbase.authentication.deliver().So(_=> { // Remove all active subscriptions and finish! O.superbase.get signatures().Map(sub=> { O.superbase.remove signature(sub); }); O.router.navigationByURL('/'); }); }}
There is also a cool feature that we can use after login:Get all active subscriptions and remove them!
This means that you don't need to create your own managed subscriptions in this case, and you can be sure that everything is closed when a user logs off.
Now that we have the basics of authentication, we can move on to CRUD and partInteracting with the Supabase database.
First, it usually looks like thisCreating a SQL query. All available functions and filters can be found in theSubbase documentation, but most of it is self explanatory.
We use our constant database name and query for the data, which returns a promise. Now, to keep the data local to the service without constantly reloading it, just call our BehaviorSubjectnext()
.
Also, when we add, delete, or update a row in our database, we only call the appropriate function and do not manipulate the result directly, since we are also integrating anotherhandleAllChanged()
who listensall changes in the database!
There's no live database connection like you might have used with Firebase, but by listening to the various change events, you can easily manipulate your local data to add, remove, or update.array elementinnerhalb des BehaviourSubject.
Now, if one were to use all the observables, it would basically be like having a live connection to Firebase, but with Supabase!
proceed with thesrc/app/services/subase.service.tsand add the following functions:
nget todos(): Observable < Todo[] > { return this._todos.asObservable();}async loadTodos() { const query = esperar this.supabase.from(TODO_DB).select('*'); this._todos.next(query.data);}async addTodo(task: string) { const newTodo = { user_id: this.supabase.auth.user().id, task }; // Sie könnten auf Fehler prüfen, die Länge der Aufgabe beträgt 3 Zeichen! const resultado = esperar esto.supabase.from(TODO_DB).insert(newTodo);}async removeTodo(id) { esperar esto.supabase .from(TODO_DB) .delete() .match({ id })}async updateTodo(id) , is_complete: boolean) { espera this.supabase .from(TODO_DB) .update({ is_complete }) .match({ id })}handleTodosChanged() { devuelve this.supabase .from(TODO_DB) .on('*', payload => { console.log('Todos geändert: ', payload); if (payload.eventType == 'DELETE') { // Das entfernte Element herausfiltern const oldItem: Todo = payload.old; const newValue = this._todos .value.filter(item => oldItem.id != item.id); this._todos.next(newValue); } else if (payload.eventType == 'INSERT') { // Neues Element hinzufügen const newItem : Todo = payload.new; this._todos.next([...this._todos.value, newItem]); } else if (payload.eventType == 'UPDATE') { // Ein Element aktualisieren const updatedItem: Todo = payload .new;const newValue = this._todos.value.map(item => { if (updatedItem.id == item.id) { item = updateItem; } return item; }); t his._todos.next(nuevoValor); } } ).abonnieren();}
Since we added row-level security to our database schema early on, we don't have to worry about filtering only the current user's data. This is already happening on the server side!
We have completed our authentication logic and CRUD. Now let's create some models to test things out.
Login and registration to Supabase
To quickly create a simple login, start by adding theReactiveFormsModul
for himsrc/app/pages/login/login.module.tsfor our login:
matter {per module} outside '@angular/núcleo';matter {common module} outside '@angular/common';matter {forms module,ReactiveFormsModul} outside '@angular/shapes';matter {Ionenmódulo} outside '@ionic/angular';matter {LoginPageRoutingModul} outside './login-routing.módulo';matter {login page} outside './login page';@per module({imported: [common module,forms module,Ionenmódulo,LoginPageRoutingModul,ReactiveFormsModul],explanations: [login page]})Export Classroom LoginPageModule {}
Just like we did in othercomplete examples of navigation with Ionic src/app/pages/login/login.page.ts, let's create a simple form to store user data.
When we register or log in, we use the appropriate features of our service, add a few payloads here and there, and now just handle errors within the promise by capturing the various results with /err.
Otherwise, we'd have to check the result here in the then() block, but I want to keep the controller as dumb as possible here and let the service do the logic for that. So now we can just open thesrc/app/pages/login/login.page.tsand change to:
matter {SuperbaseService} outside './../../servicios/supabase.servicio';matter {Components,OnInit} outside '@angular/núcleo';matter {form builder,form group,validator} outside '@angular/shapes';matter {AlertController,charge controller} outside '@ionic/angular';matter {router} outside '@angular/router';@Components({voters: 'Application login',template url: './login.seite.html',style urls: ['./login.page.scss'],})Export Classroom login page implemented OnInit {credentials:form group; constructor( Privatefull board:form builder, Privatealert controller:AlertController, Privaterouter:router, Privatecharge controller:charge controller, PrivatesuperbaseService:SuperbaseService) {} OnInit myth() { O.credentials= O.full board.group({Email: ['', [validator.necessary,validator.Email]],password: ['',validator.necessary], }); } asynchronous Record() { untilCharging= wait O.charge controller.create(); waitCharging.at the moment(); O.superbaseService.record(O.credentials.Wert).So(asynchronousData=> { waitCharging.deny(); O.router.navigationByURL('/List', {replace url: TRUE }); }, asynchronouserr=> { waitCharging.deny(); O.show errors('Login error',err.News); }); } asynchronous Record() { untilCharging= wait O.charge controller.create(); waitCharging.at the moment(); O.superbaseService.Record(O.credentials.Wert).So(asynchronousData=> { waitCharging.deny(); O.show errors('Successful login', 'Please confirm your email now!'); }, asynchronouserr=> { waitCharging.deny(); untilAlarm= wait O.alert controller.create({Header: 'Sign up failed',News:err.Error.News,Chaves: ['OK'], }); waitAlarm.at the moment(); }); } asynchronous show errors(title,News) { untilAlarm= wait O.alert controller.create({Header:title,News:News,Chaves: ['OK'], }); waitAlarm.at the moment(); }}
Our class doesn't have much to do other than load and error logic!
The corresponding template is also very simple, just add our form input fields so we can quickly create and register users.
keep it upsrc/app/pages/login/login.page.htmland change to:
<ion head> <ion toolbar heart="primary"> <ionic bond>debt superbase</ionic bond> </ion toolbar></ion head><ionic content> <mould (ngSend)="Record()" [form group]="credentials"> <division Classroom="input group"> <ion article> <ion input position marker="juan@doe.com" formControlName="Email"></ion input> </ion article> <ion article> <ion input Type="password" position marker="password" formControlName="password"></ion input> </ion article> </division> <ion button Type="to send" expand="Block" [Disabled]="!credentials.valid">Record</ion button> <ion button Type="I like" expand="Block" (clique)="Record()" heart="secondary">Record!</ion button> </mould></ionic content>
You should now be able to create a new user. By default, that user must verify their email address, which is why we don't sign up new users right away.
If you don't want this behavior, you can also change it in the Supabase application atAuthentication -> Settings.
Work with Supabase data
After logging in, our user gets to the list page. The idea is to get all the data from our service, which will generate all the new values in the inner BehaviorSubject that we can subscribe to from the outside.
Therefore, theArticle
The variable is now observable and we can then use the asynchronous pipeline on the view to get all the new values every time something changes.
As for the changes, we'll start by showing an alert with an input field to collect data for a new task. The other functions of removing or toggle completion of a task are simpledelivered to our servicewith the correct information about the object!
Start with the logic of the list by changing thesrc/app/pages/list/list.page.tsFor:
matter {SuperbaseService,No} outside './../../servicios/supabase.servicio';matter {Components,OnInit} outside '@angular/núcleo';matter {AlertController} outside '@ionic/angular';@Components({voters: 'Application list',template url: './lista.pagina.html',style urls: ['./lista.pagina.scss'],})Export Classroom listing page implemented OnInit {Article= O.superbaseService.No; constructor(PrivatesuperbaseService:SuperbaseService, PrivatealertaCtrl:AlertController) { } OnInit myth() { } asynchronous createAll() { untilAlarm= wait O.alertaCtrl.create({Header: 'All things new',Aperitif: [ {Name: 'Task',position marker: 'Learn Ionian' } ],Chaves: [ {Text: 'Cancel',role: 'Cancel' }, {Text: 'Add', manipulator: (Data: any) => { O.superbaseService.addAll(Data.Task); } } ] }); waitAlarm.at the moment(); } extinguish(Article:No) { O.superbaseService.removeTodo(Article.I WAS GOING); } alternatively done(Article:No) { O.superbaseService.update all(Article.I WAS GOING, !Article.It's complete); } deliver() { O.superbaseService.deliver(); }}
Within the model, we can now iterate over all available todos and print information about them. Using theglide ion element
We can also add some radio buttons on the sides to toggle complete or delete a task.
Also, we can add some logic to dynamically change colors or an icon based onIt's complete
ownership of a task.
Nothing really special, so go ahead and change it.src/app/pages/list/list.page.htmlFor:
<ion head> <ion toolbar heart="primary"> <ionic bond>my all</ionic bond> <ionic keys slot="movie"> <ion button (clique)="deliver()"> <ion icon slot="single icon" Name="go out"></ion icon> </ion button> </ionic keys> </ion toolbar></ion head><ionic content> <ion list> <ion list> <glide ion element *Para="leave all items | asynchronous"> <ion article> <ion tag>{{ all.task }}<PAG>{{all.inserted_in | data: 'brief' }}</PAG> </ion tag> <ion icon Name="check box outline" slot="movie" heart="Success" * Se="todo.is_complete"></ion icon> </ion article> <ion element options Page="movie"> <ion item option (clique)="delete all)" heart="Danger"> <ion icon Name="Garbage" slot="single icon"></ion icon> </ion item option> </ion element options> <ion element options Page="To start"> <ion item option (clique)="toggleDone(all)" [Coro]="todo.is_complete ?'Notice':'Success'"> <ion icon [Name]="todo.is_complete ?'close':'check mark'" slot="single icon"></ion icon> </ion item option> </ion element options> </glide ion element> </ion list> </ion list> <fab de ions Vertical="below" horizontal="movie" slot="Festival"> <ion-fab-flavor (clique)="createAll()"> <ion icon Name="add"></ion icon> </ion-fab-flavor> </fab de ions></ionic content>
That's it for creating basically the same kind of realtime logic you're used to in Firebase collections.
All changes to our data are sent to Supabase, and since we listen for changes to our table, we can do the rest locally without constantly reloading the entire data table.
Authentication with Guard
Currently, any user can access the listing page, so we need to guard it with a guardrail.
We basically use the same logic elsewhere, the idea is to use thiscurrent user
Observable returned by our service to check if a user is authenticated.
Since the BehaviorSubject inside the service is initialized withNull
, we need to filter this initial value,
It then fetches a user's information from storage and we can use that value to check whether or not we have a user and then either grant access or send the user back to login.
So, finish our example by changing thesrc/app/guards/auth.guard.tsFor:
matter {SuperbaseService} outside './../servicios/supabase.servicio';matter {injectable} outside '@angular/núcleo';matter {can charge,router} outside '@angular/router';matter {observable} outside 'rxjs';matter {Filter,Map,to take} outside 'rxjs/operators'@injectable({Supplied in: 'Fuente'})Export Classroom AuthGuardNombre implemented can charge { constructor(PrivatesuperbaseService:SuperbaseService, Privaterouter:router) { } can load():observable<boolesch> { go back O.superbaseService.current user.Rohr( Filter(Wert=>Wert!== Null), // Filter the initial value of the behavioral subject to take(1), // Otherwise the observable will not complete! Map(is authenticated=> { Se (is authenticated) { go back TRUE; } Anders { O.router.navigationByURL('/') go back INCORRECT; } }) ); }}
You can now login and refresh an internal page and the page will only load if the user has been authenticated beforehand!
Diploma
This simple example of Ionic with Supabase was my first attempt and so far I've really enjoyed working with it. It's still in beta so things could change, but the idea and approach is already pretty close to what Firebase offers!
Also, your Supabase project directly offers an API, which is also includedSwagger's Documents. His docs also show an interesting example of generating TS interfaces based on this hub information, such as B.Run:
npx @manifoldco/swagger-to-ts https://your-project.supabase.co/rest/v1/?apikey=your-anon-key --output types/supabase.ts
Just add your URL and key and your interfaces are ready!
One additional note: we list our service on the first page displayed in our app, hence thecreateCustomer()
will be called immediately. If you have a different type of routing, make sure you put it on the main page or call it somethinginside()
of your service directly in theapplication.component.ts.
You can also find a video version of this tutorial below.
FAQs
How do you integrate firebase in ionic? ›
- Step 1: Create a new project. ...
- Step 2: Next, navigate to the project and install the AngularFire plugin using the below command. ...
- Step 3: Create a page todoDetails under the pages folder by using the following command.
- Step 4: Create a service page under the services folder by using the following command.
- Make a production build. This might be an obvious one. ...
- Add a Service Worker. Adding a Service Worker to an Ionic app has its benefits. ...
- Native Page Transitions. ...
- Use Virtual Scroll and Infinite Scroll. ...
- Conclusion.
Connect your Ionic App with Firebase
You can either use our Ionic Angular Firebase Full App starter or you can create your own Ionic 5 app from scratch. For connecting Firebase to our Ionic app we're going to use the Angularfire2 plugin. AngularFire is The official library for Firebase and Angular.
Supabase is a serverless, open-source alternative to Firebase built on top of the PostgreSQL database. It provides all the backend services needed to create a full-stack application.
What IDE should I use for Firebase? ›A fast and simple IDE for Firebase. Retool gives you a simple integrated development environment (IDE) to quickly build internal applications on top of your Firebase data.
Can I use REST API with Firebase? ›We can use any Firebase Realtime Database URL as a REST endpoint. All we need to do is append . json to the end of the URL and send a request from our favorite HTTPS client.