Getting Started
Create an app from scratch
Want to use Nitrokit to build your next idea right away?! We'll get you set up in minutes.
Intro
In here we will create an app called snippet-gpt
.
(A tool that we could use to keep track of questions that we ask chatGPT to return us code-snippets).
This tutorial will show you how to set up a brand new supabase app that already contains
- Authentication
- Saving of user data, email
- Storing avatar
- Enable RLS
It will also guide you through creating your own Angular client in this workspace by using custom generators.
At the end you will have:
- A working Angular app with authentication called
snippet-gpt
- Your app connected to supabase and ready to start developing
- Default crud logic for
flows
(which represents flows for our snippet-gpt application) - Light/darkmode working
- Only lost a few minutes if you pay attention
Creating a brand new app
We are going to create a brand new supabase app called snippet-gpt
:
Go to https://supabase.com/dashboard/projects,
Click on New project, choose your organisation and type in snippet-gpt
as the project name.
Choose a safe database password and click on Create new Project.
Now go to SQL Editor. (You might have to wait a few minutes till the project is set up completely) In the SQL Editor we want to copy-paste the next code which will do the following:
- Create a table
flows
- Set the owner of
flows
to postgres - Create a table for
profiles
. After all you want users on your app - Set the owner of
profiles
to postgres - Create a primary key for
flows
- Create a primary key for
profiles
- Set a foreign key between
profiles
andauth.users
- Set a foreign key between
flows
andprofiles
. After all a flow is connected to a profile.
CREATE TABLE public.flows (
created_at timestamp with time zone DEFAULT now() NOT NULL,
profile_id uuid NOT NULL,
name text NOT NULL,
id uuid DEFAULT gen_random_uuid() NOT NULL
);
ALTER TABLE public.flows OWNER TO postgres;
CREATE TABLE public.profiles (
id uuid NOT NULL,
updated_at timestamp with time zone,
username text,
full_name text,
avatar_url text,
website text,
email text NOT NULL,
CONSTRAINT username_length CHECK ((char_length(username) >= 3))
);
ALTER TABLE public.profiles OWNER TO postgres;
ALTER TABLE ONLY public.flows
ADD CONSTRAINT flows_pkey PRIMARY KEY (id);
ALTER TABLE public.profiles
ADD PRIMARY KEY (id);
ALTER TABLE ONLY public.profiles
ADD CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.flows
ADD CONSTRAINT flows_profile_id_fkey FOREIGN KEY (profile_id) REFERENCES public.profiles(id);
Now click the run button to execute the previously pasted SQL.
The result should be Success. No rows returned
Enable RLS for your new app
Row-Level Security (RLS) is needed to ensure that users only have access to data that belongs to them, providing an additional layer of security in database management. It restricts users' interactions with database rows based on certain conditions, typically their user ID or permissions.
In this case, RLS is required to:
- Ensure users can only perform CRUD (Create, Read, Update, Delete) actions on their own data in the
flows
table. - Restrict access to the
profiles
table, so users can only view, insert, or update their own profiles.
RLS helps protect user privacy and maintains data integrity by preventing unauthorized access to other users' data.
Go to Authentication > Policies and click on Enable RLS For the table flows
and profiles
.
Now enable the basic policies for profiles.
Go back to the SQL editor and paste the following code which will:
- enable crud actions on the table
flows
for the user that is logged in, but only if the flows belong to the user. - Enable read actions for
profiles
but only if the profile belongs to the user. - Enable insert actions for
profiles
but only if the profile belongs to the user. - Enable update actions for
profiles
but only if the profile belongs to the user.
CREATE POLICY "Enable crud for users based on profile_id" ON public.flows TO authenticated USING ((auth.uid() = profile_id)) WITH CHECK ((auth.uid() = profile_id));
CREATE POLICY "Public profiles are viewable by the user." ON public.profiles FOR SELECT USING ((auth.uid() = id));
CREATE POLICY "Users can insert their own profile." ON public.profiles FOR INSERT WITH CHECK ((auth.uid() = id));
CREATE POLICY "Users can update own profile." ON public.profiles FOR UPDATE USING ((auth.uid() = id));
Setup realtime
We want to set up realtime for the flows
table, so it interacts with our state management system without any effort.
Go to Table Editor in the supabase admin panel and click on the flows
table.
In the left upper corner there should be button stating "Realtime Off". Click on it and click Enable realtime.
Now the flows
table will support realtime.
You could also do it in the SQL editor:
Turn on realtime
In the SQL Editor, copy-paste the following SQL:
ALTER PUBLICATION supabase_realtime ADD TABLE ONLY public.flows;
Add functions
We still need to add some functions to:
- delete an old profile
- handle new users (insert them into the
profiles
table) - handle avatars
Copy-paste the following SQL in the SQL Editor:
CREATE FUNCTION public.handle_new_user() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
AS $$
begin
insert into public.profiles (id, full_name, avatar_url)
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
return new;
end;
$$;
ALTER FUNCTION public.handle_new_user() OWNER TO postgres;
CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
-- Set up access controls for storage.
-- See https://supabase.com/docs/guides/storage/security/access-control#policy-examples for more details.
create policy "Avatar images are publicly accessible." on storage.objects
for select using (bucket_id = 'avatars');
create policy "Anyone can upload an avatar." on storage.objects
for insert with check (bucket_id = 'avatars');
create policy "Anyone can update their own avatar." on storage.objects
for update using ((select auth.uid()) = owner) with check (bucket_id = 'avatars');
Generate the angular app
To generate your angular application you can run the following command: (of course replace the url and key with the credentials you can find in the dashboard of your supabase admin panel)
npx nx g @nitrokit/code-assistant-util-automation:app --name=snippet-gpt --supabaseUrl=<your-supabase-url> --supabaseKey=<your-supabase-key>
This command does a lot for us
- Creates an app
- Create a shell lib
- Creates a search lib
- Sets up authentication
- Sets up dark/light theming
- Sets up tailwind and flowbite
- Sets up a nice admin panel which is mobile friendly
- Sets up basic routing
We are all set. Now let's add generate-supabase-types:snippet-gpt
to the package.json
file so we can load the types.
We will need them to run the app. Make sure the project id set correctly added but also the output path is correct: libs/snippet-gpt/type-supabase/src/lib/types/supabase.type.ts
"scripts": {
"generate-supabase-types:influencer": "npx supabase gen types typescript --project-id=\"<your-project-id>\" --schema public > libs/influencer/type-supabase/src/lib/types/supabase.type.ts",
"generate-supabase-types:snippet-gpt": "npx supabase gen types typescript --project-id=\"<your-project-id>\" --schema public > libs/snippet-gpt/type-supabase/src/lib/types/supabase.type.ts"
}
First be sure to log in!!
npx supabase login
Now run npm run generate-supabase-types:snippet-gpt
so it will load the types from the supabase instance to your workspace.
If this takes a while it might mean that you are not logged in into supabase.
You can log in by running:
And follow the steps.
Run the project
You have succesfully created your first supabase app. Run the project with:
npx nx run snippet-gpt:serve
Log in
For this boilerplate, logging in/registering by email is the default, so when you enter your email, you should get an email. In supabase authentication > url configuration, make sure that the site URL is set to http://localhost:4200 to test locally. There are multiple ways to log in but those are not part of this workspace.
Log on with your email and wait until it shows Check your email. Click on confirm your email and you will get logged in automatically.
Add CRUD functionality for adding a flow.
Remember we created a table called flows
. The following command will take:
- the name:
flow
- The scope:
snippet-gpt
is our app name - entityPath:
flows
, the name of the table.
The command will do a lot for us:
- Creates a data-access lib for interacting with our supabase
flows
table - Creates a state-machine lib for managing our supabase
flows
table, realtime - Creates a feature lib that contains crud for
- Add
flow
- Update
flow
- List
flow
- Remove
flow
- Remove multiple
flows
- Add
- Creates a facade
- Creates a default form
- Sets up routing
- Complex model validations with vest.js
Run the following command to generate the crud logic.
Explanation: the scope is the application name, the entitypath is the table name and the primary field is the field that it will use
to set up a basic field in the form and do all mapping. The name for instance in this case, it will make sure that there is a text field
mapped to name
npx nx g @nitrokit/code-assistant-util-automation:crud-complete --name=flow --scope=snippet-gpt --entity-path=flows --primaryField=name
Go to libs/snippet-gpt/feat-shell/src/lib/components/smart/shell-entry/shell-entry.smart-component.html
to add a new menu item that points to flow
. The shell is the entry point of our application, so that's where the menu lives.
Replace the contents of:
<ng-container nitrokit-base-layout-sidebar> </ng-container>
With this:
<ul nitrokit-sidebar-items>
<li>
<a routerLink="dashboard" nitrokit-sidebar-item>
<i nitrokit-sidebar-item-icon class="fa fa-dashboard"></i>
<span nitrokit-sidebar-item-label> Dashboard </span>
</a>
</li>
<li>
<a routerLink="flow" nitrokit-sidebar-item>
<i nitrokit-sidebar-item-icon class="fa fa-lightbulb"></i>
<span nitrokit-sidebar-item-label> Flows </span>
</a>
</li>
</ul>
Now you should be able to navigate to flows and test the following:
- Create flow, should throw validation message when emtpy
- Create flow should work
- Flow should be automatically refreshed realtime
- Getting the detail of a toast should work
- Updating the toast should work
- Removing a toast should work
- Removing multiple toasts should work
- Light/dark mode should work
- Uploading an avatar should work
- Updating account data should work
Enabling toasts
The whole supabase backend is connected to a state system. Every supabase table has its own state. There are 2 types:
- RealtimeEntityStateSupabase
- PagedEntityStateSupabase
The first is realtime, the other one is paged.
Both of these should keep a toasts$
subject.
Let's make sure it is connected to our shell of our application.
In libs/snippet-gpt/feat-shell/src/lib/facade.service.ts
inject the FlowStateMachine
and expose it:
import { inject, Injectable } from '@angular/core';
import { FlowStateMachine } from '@nitrokit/snippet-gpt-state-flow';
@Injectable({ providedIn: 'root' })
export class FacadeService {
public readonly flowStateMachine = inject(FlowStateMachine);
}
In libs/snippet-gpt/feat-shell/src/lib/components/smart/shell-entry/shell-entry.smart-component.ts
Update line 73
like so:
this.toastService.registerToastStreams([this.facadeService.flowStateMachine.toast$]);
Conclusion
That's it!!
You have just set up your own steps to your successful saas or other project. Thanks for your trust!
Cheers! Brecht
Have questions?
Still have questions? Talk to support.