Nitrokit Nitrokit - Ship faster with AI - No more heavy lifting, build Angular apps at NITROSPEED!

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 and auth.users
  • Set a foreign key between flows and profiles. 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
  • 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.

Next
Forms